From 75cdefd9664df92719f679b047214a626760a6ae Mon Sep 17 00:00:00 2001 From: xuhy <3313886187@qq.com> Date: 星期一, 07 四月 2025 17:46:24 +0800 Subject: [PATCH] 初始化 --- ruoyi-ui/.eslintignore | 10 ruoyi-ui/src/components/ThemePicker/index.vue | 173 ruoyi-ui/src/utils/scroll-to.js | 58 ruoyi-ui/src/layout/components/index.js | 5 ruoyi-admin/src/main/resources/application-test.yml | 238 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java | 16 ruoyi-ui/src/assets/icons/svg/international.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java | 76 ruoyi-ui/src/assets/icons/svg/druid.svg | 1 ruoyi-ui/bin/build.bat | 12 ruoyi-ui/src/assets/icons/svg/eye.svg | 1 ruoyi-ui/src/views/monitor/cache/list.vue | 241 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java | 16 ruoyi-ui/src/assets/styles/index.scss | 182 ruoyi-ui/src/views/register.vue | 209 ruoyi-system/src/main/java/com/ruoyi/system/query/SysUserQuery.java | 29 ruoyi-ui/package.json | 91 ruoyi-ui/src/api/system/dict/type.js | 60 ruoyi-ui/src/assets/icons/svg/system.svg | 2 ruoyi-applet/src/main/resources/META-INF/spring-devtools.properties | 1 ruoyi-common/src/main/java/com/ruoyi/common/core/utils/HttpUtils.java | 311 ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java | 570 ruoyi-ui/src/assets/icons/svg/tree.svg | 1 ruoyi-ui/src/assets/icons/svg/bug.svg | 1 ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue | 94 ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java | 119 ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java | 73 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java | 484 ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java | 75 ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm | 590 ruoyi-ui/src/assets/icons/svg/pdf.svg | 1 ruoyi-ui/src/views/system/dept/index.vue | 340 ruoyi-generator/pom.xml | 40 ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm | 76 ruoyi-ui/build/index.js | 35 ruoyi-ui/src/assets/icons/svg/monitor.svg | 2 ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java | 63 ruoyi-admin/src/main/resources/application-prod.yml | 237 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java | 79 ruoyi-ui/.env.development | 11 ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java | 121 ruoyi-ui/src/utils/jsencrypt.js | 30 ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml | 117 ruoyi-ui/src/assets/icons/svg/job.svg | 1 ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java | 92 ruoyi-system/src/main/java/com/ruoyi/system/query/SysRoleQuery.java | 17 ruoyi-ui/src/assets/icons/svg/github.svg | 1 ruoyi-ui/src/store/modules/user.js | 101 ruoyi-ui/src/api/monitor/cache.js | 57 ruoyi-ui/src/components/Editor/index.vue | 272 ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java | 163 ruoyi-ui/src/store/index.js | 25 ruoyi-ui/src/assets/icons/svg/date.svg | 1 ruoyi-ui/src/views/system/menu/index.vue | 452 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java | 18 pom.xml | 233 ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java | 122 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java | 99 ruoyi-ui/src/assets/icons/svg/guide.svg | 1 ruoyi-ui/src/api/system/user.js | 135 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java | 89 ruoyi-ui/src/views/tool/build/RightPanel.vue | 946 doc/若依环境使用手册.docx | 0 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java | 61 ruoyi-ui/src/assets/icons/svg/validCode.svg | 1 ruoyi-ui/src/assets/404_images/404.png | 0 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessModuleUpdateBO.java | 19 ruoyi-ui/src/assets/icons/svg/lock.svg | 1 ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml | 89 ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java | 30 ruoyi-common/src/main/java/com/ruoyi/common/utils/OrderNos.java | 164 ruoyi-ui/src/api/menu.js | 9 ruoyi-ui/src/components/Screenfull/index.vue | 57 ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java | 392 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java | 18 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java | 168 ruoyi-common/src/main/java/com/ruoyi/common/utils/WxAppletTools.java | 77 ruoyi-common/src/main/java/com/ruoyi/common/utils/IDCardUtils.java | 18 ruoyi-ui/src/assets/icons/svg/user.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java | 144 ruoyi-ui/src/assets/icons/svg/tree-table.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java | 28 ruoyi-ui/src/components/Crontab/index.vue | 430 ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java | 205 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java | 166 ruoyi-ui/src/assets/icons/svg/cascader.svg | 1 ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java | 94 ruoyi-ui/src/views/components/icons/element-icons.js | 3 ruoyi-common/src/main/java/com/ruoyi/common/enums/BillTypeEnum.java | 32 ruoyi-ui/src/views/tool/gen/genInfoForm.vue | 299 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java | 89 ruoyi-generator/src/main/resources/vm/sql/sql.vm | 22 ruoyi-ui/src/assets/icons/svg/checkbox.svg | 1 ruoyi-ui/src/assets/icons/svg/form.svg | 1 ruoyi-ui/src/assets/icons/svg/edit.svg | 1 ruoyi-applet/src/main/resources/mybatis-config.xml | 25 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java | 120 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java | 29 ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java | 343 ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java | 68 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java | 87 ruoyi-ui/src/assets/styles/element-variables.scss | 31 ruoyi-ui/src/utils/dict/DictMeta.js | 38 ruoyi-common/src/main/java/com/ruoyi/common/enums/ProcessCategoryEnum.java | 51 ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java | 69 ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TestController.java | 30 ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CommonController.java | 163 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxException.java | 55 ruoyi-ui/public/robots.txt | 2 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java | 171 bin/run.bat | 14 ruoyi-applet/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java | 51 bin/package.bat | 12 LICENSE | 20 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java | 80 ruoyi-system/src/main/java/com/ruoyi/system/dto/TerminateContractDTO.java | 27 ruoyi-ui/src/views/system/user/profile/userInfo.vue | 75 ruoyi-ui/src/store/modules/tagsView.js | 228 ruoyi-common/src/main/java/com/ruoyi/common/utils/MultipartFileUtil.java | 222 ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java | 34 ruoyi-ui/src/layout/components/Sidebar/index.vue | 57 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java | 47 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysPostController.java | 123 ruoyi-applet/src/main/resources/application.yml | 4 ruoyi-generator/src/main/resources/vm/java/service.java.vm | 61 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java | 169 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java | 141 ruoyi-ui/src/assets/icons/svg/drag.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java | 20 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java | 46 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java | 19 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/Watermark.java | 9 ruoyi-ui/bin/package.bat | 12 ruoyi-ui/src/layout/components/Navbar.vue | 200 ruoyi-applet/src/main/resources/logback.xml | 93 ruoyi-ui/src/directive/index.js | 23 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java | 61 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java | 46 ruoyi-ui/src/components/RightPanel/index.vue | 106 ruoyi-ui/src/layout/components/Sidebar/Logo.vue | 93 ruoyi-ui/src/utils/generator/html.js | 359 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java | 216 ruoyi-ui/src/components/DictData/index.js | 49 ruoyi-ui/src/api/system/menu.js | 60 ruoyi-ui/src/views/monitor/online/index.vue | 122 ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java | 117 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java | 130 ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java | 18 ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java | 106 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java | 174 ruoyi-ui/src/assets/icons/svg/theme.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java | 31 ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java | 402 ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java | 57 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java | 179 ruoyi-admin/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java | 51 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUserApplet.java | 263 ruoyi-ui/src/assets/icons/svg/radio.svg | 1 ruoyi-ui/src/assets/icons/svgo.yml | 22 ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml | 105 ruoyi-ui/src/store/modules/permission.js | 133 ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java | 110 ruoyi-generator/src/main/resources/vm/java/domain.java.vm | 105 ruoyi-system/src/main/java/com/ruoyi/system/vo/SysOperLogVO.java | 24 ruoyi-ui/src/api/type/type.js | 44 ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue | 106 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTaskListBO.java | 52 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessStartBO.java | 57 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java | 46 ruoyi-ui/src/components/ImageUpload/index.vue | 226 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java | 47 ruoyi-applet/pom.xml | 185 ruoyi-ui/.gitignore | 23 ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml | 93 ruoyi-ui/src/assets/icons/svg/input.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/code/SubmitTemplateReg.java | 40 ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml | 102 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java | 73 ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java | 52 ruoyi-ui/src/components/Hamburger/index.vue | 44 ruoyi-ui/src/store/modules/dict.js | 50 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java | 82 ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java | 99 ruoyi-common/src/main/java/com/ruoyi/common/constant/WxConstant.java | 22 ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java | 138 ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java | 148 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java | 101 ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java | 83 ruoyi-ui/src/assets/icons/svg/eye-open.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/constant/DictConstants.java | 50 ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java | 27 ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java | 56 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java | 48 ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml | 127 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java | 261 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java | 96 ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java | 39 ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm | 135 ruoyi-ui/src/assets/icons/svg/redis.svg | 1 ruoyi-ui/src/plugins/index.js | 20 ruoyi-ui/src/assets/styles/btn.scss | 99 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java | 179 ruoyi-ui/src/api/monitor/server.js | 9 ruoyi-common/src/main/java/com/ruoyi/common/constant/AmountConstant.java | 15 ruoyi-ui/src/assets/icons/svg/documentation.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java | 279 ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml | 234 ruoyi-ui/src/assets/icons/svg/code.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java | 24 ruoyi-ui/src/assets/icons/index.js | 9 ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java | 125 ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml | 57 ruoyi-ui/src/plugins/modal.js | 83 ruoyi-ui/src/utils/dict/DictConverter.js | 17 ruoyi-ui/src/assets/images/dark.svg | 39 ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java | 111 ruoyi-ui/src/api/tool/gen.js | 76 ruoyi-ui/src/components/RuoYi/Doc/index.vue | 21 ruoyi-ui/src/views/tool/build/index.vue | 768 ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java | 27 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java | 360 ruoyi-common/src/main/java/com/ruoyi/common/core/exception/ServiceException.java | 74 ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java | 58 ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml | 359 ruoyi-ui/src/components/RuoYi/Git/index.vue | 21 ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java | 86 ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue | 149 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java | 11 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessCreateBO.java | 21 ruoyi-ui/src/assets/icons/svg/phone.svg | 1 ruoyi-ui/src/layout/components/Settings/index.vue | 260 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCache.java | 117 ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java | 373 ruoyi-ui/.editorconfig | 22 ruoyi-ui/src/store/getters.js | 19 ruoyi-ui/src/views/tool/swagger/index.vue | 15 ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java | 74 ruoyi-admin/src/main/resources/template/1_yzj_租赁合同.xml | 11322 +++++++ ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java | 126 ruoyi-ui/src/utils/errorCode.js | 6 ruoyi-ui/src/assets/icons/svg/qq.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java | 165 ruoyi-ui/src/views/system/user/profile/userAvatar.vue | 182 ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java | 126 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java | 63 ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java | 113 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java | 39 ruoyi-applet/src/main/java/com/ruoyi/web/aspect/StateAspect.java | 45 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java | 124 ruoyi-ui/src/assets/icons/svg/logininfor.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java | 454 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java | 100 ruoyi-ui/src/assets/logo/logo.png | 0 ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java | 28 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/TTenantResp.java | 70 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/AccessTokenRespBody.java | 28 ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml | 34 ruoyi-ui/src/utils/request.js | 152 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java | 59 ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm | 505 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java | 79 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTemplatePageBO.java | 12 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java | 129 ruoyi-ui/src/components/SvgIcon/index.vue | 61 ruoyi-common/src/main/java/com/ruoyi/common/exception/state/StateErrorCode.java | 19 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java | 93 ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml | 202 ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java | 15 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java | 232 ruoyi-ui/src/assets/icons/svg/row.svg | 1 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java | 182 ruoyi-ui/src/layout/components/IframeToggle/index.vue | 24 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java | 159 ruoyi-ui/src/store/modules/app.js | 66 ruoyi-ui/src/views/dashboard/RaddarChart.vue | 116 ruoyi-ui/src/views/system/user/index.vue | 670 ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java | 372 ruoyi-ui/src/assets/icons/svg/online.svg | 1 ruoyi-ui/src/views/tool/build/IconsDialog.vue | 123 ruoyi-ui/src/views/components/icons/index.vue | 87 ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java | 67 ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java | 70 ruoyi-ui/src/views/system/user/authRole.vue | 117 ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java | 101 ruoyi-ui/src/assets/icons/svg/component.svg | 1 ruoyi-ui/src/assets/icons/svg/clipboard.svg | 1 ruoyi-ui/src/views/tool/gen/editTable.vue | 234 ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/TaskUtil.java | 83 ruoyi-common/src/main/java/com/ruoyi/common/config/FileUploadConfig.java | 23 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java | 38 ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java | 73 ruoyi-system/src/main/java/com/ruoyi/system/dto/SysRoleDTO.java | 28 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java | 43 ruoyi-ui/src/components/IconSelect/requireIcons.js | 11 ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Utils.java | 58 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java | 233 ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue | 100 ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm | 474 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java | 40 ruoyi-ui/src/views/tool/gen/importTable.vue | 120 ruoyi-admin/src/main/java/com/ruoyi/web/core/config/FileUploaderConfig.java | 67 ruoyi-ui/vue.config.js | 134 ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/QRCodeUtil.java | 124 ruoyi-generator/src/main/resources/generator.yml | 10 ruoyi-ui/src/assets/icons/svg/server.svg | 1 ruoyi-generator/src/main/resources/vm/js/api.js.vm | 44 README.md | 4 ruoyi-admin/src/main/resources/banner.txt | 24 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseModel.java | 107 ruoyi-ui/src/directive/module/clipboard.js | 54 ruoyi-ui/src/assets/styles/mixin.scss | 66 ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java | 92 ruoyi-ui/src/layout/index.vue | 111 ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java | 114 ruoyi-ui/src/assets/icons/svg/message.svg | 1 ruoyi-ui/src/plugins/download.js | 72 ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java | 45 ruoyi-ui/bin/run-web.bat | 12 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java | 107 ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java | 50 ruoyi-ui/src/views/monitor/job/log.vue | 295 ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java | 50 ruoyi-ui/src/views/dashboard/PieChart.vue | 79 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java | 163 ruoyi-ui/src/views/error/401.vue | 88 ruoyi-ui/src/store/modules/settings.js | 42 ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java | 1000 ruoyi-ui/src/components/TopNav/index.vue | 194 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java | 247 ruoyi-ui/README.md | 30 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java | 304 ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js | 25 ruoyi-ui/src/layout/components/Sidebar/Link.vue | 43 ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java | 79 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java | 260 ruoyi-ui/src/assets/icons/svg/star.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java | 547 ruoyi-ui/src/views/system/dict/data.vue | 402 generator/pom.xml | 50 ruoyi-ui/src/settings.js | 44 ruoyi-ui/src/views/index_v1.vue | 98 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java | 39 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/RespBody.java | 19 ruoyi-common/src/main/java/com/ruoyi/common/basic/PageInfo.java | 70 ruoyi-ui/public/favicon.ico | 0 ruoyi-ui/src/views/monitor/server/index.vue | 207 ruoyi-ui/src/views/components/icons/svg-icons.js | 10 ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java | 614 ruoyi-ui/src/utils/dict/index.js | 33 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java | 30 ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml | 122 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java | 115 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java | 322 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/TimeRangeQueryBody.java | 45 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java | 131 ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtil.java | 79 ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java | 68 ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java | 69 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java | 16 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java | 61 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java | 614 ruoyi-ui/src/assets/styles/element-ui.scss | 92 ruoyi-common/src/main/java/com/ruoyi/common/core/controller/FileController.java | 140 ruoyi-common/src/main/java/com/ruoyi/common/redis/service/RedisService.java | 273 ruoyi-ui/src/utils/generator/js.js | 235 ruoyi-ui/src/views/monitor/cache/index.vue | 144 ruoyi-system/src/main/java/com/ruoyi/system/dto/SysUserUpdateStatusDTO.java | 22 ruoyi-ui/src/utils/generator/render.js | 126 ruoyi-admin/src/main/resources/mybatis-config.xml | 25 ruoyi-common/src/main/java/com/ruoyi/common/config/MailProperties.java | 32 ruoyi-ui/src/views/dashboard/mixins/resize.js | 56 ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java | 214 ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java | 218 ruoyi-ui/src/views/dashboard/LineChart.vue | 135 ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java | 18 ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java | 110 ruoyi-quartz/pom.xml | 40 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserDecodeData.java | 52 ruoyi-ui/src/views/system/dict/index.vue | 347 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java | 203 ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java | 167 ruoyi-ui/src/assets/icons/svg/build.svg | 1 ruoyi-ui/src/views/system/user/profile/index.vue | 91 ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java | 33 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java | 112 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java | 83 ruoyi-generator/src/main/resources/vm/java/controller.java.vm | 115 ruoyi-ui/src/api/system/dept.js | 52 ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java | 112 ruoyi-ui/src/assets/icons/svg/icon.svg | 1 ruoyi-ui/src/layout/components/TagsView/index.vue | 332 ruoyi-ui/src/assets/icons/svg/time.svg | 1 ruoyi-ui/src/assets/icons/svg/example.svg | 1 ruoyi-ui/src/views/system/post/index.vue | 309 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java | 19 ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java | 56 ruoyi-ui/src/assets/icons/svg/number.svg | 1 ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java | 70 ruoyi-ui/src/assets/styles/sidebar.scss | 227 ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java | 26 ruoyi-ui/src/api/system/post.js | 44 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessUpdateBO.java | 22 ruoyi-ui/src/components/IconSelect/index.vue | 104 ruoyi-applet/src/main/resources/application-test.yml | 231 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java | 24 ruoyi-ui/src/assets/icons/svg/slider.svg | 1 ruoyi-ui/src/utils/generator/icon.json | 1 ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java | 274 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java | 84 ruoyi-ui/src/assets/icons/svg/excel.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java | 113 ruoyi-ui/src/assets/401_images/401.gif | 0 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java | 16 ruoyi-ui/src/components/PanThumb/index.vue | 142 ruoyi-ui/src/utils/index.js | 390 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java | 339 ruoyi-ui/src/assets/icons/svg/link.svg | 1 ruoyi-ui/src/assets/icons/svg/email.svg | 1 ruoyi-ui/src/views/system/role/index.vue | 605 ruoyi-common/src/main/java/com/ruoyi/common/enums/TaskEventType.java | 146 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java | 102 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java | 322 ruoyi-ui/src/assets/icons/svg/people.svg | 1 ruoyi-ui/src/components/Crontab/hour.vue | 114 ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java | 521 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java | 126 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java | 86 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java | 79 bin/clean.bat | 12 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java | 1679 + ruoyi-ui/src/api/login.js | 59 ruoyi-ui/src/assets/icons/svg/log.svg | 1 ruoyi-ui/src/assets/icons/svg/nested.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java | 94 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessAgreeBO.java | 36 ruoyi-ui/src/assets/icons/svg/switch.svg | 1 ruoyi-ui/src/permission.js | 56 ruoyi-ui/src/views/tool/gen/basicInfoForm.vue | 60 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java | 123 ruoyi-ui/src/views/monitor/druid/index.vue | 15 ruoyi-framework/pom.xml | 64 ruoyi-system/src/main/java/com/ruoyi/system/dto/TDeptUpAndDownDTO.java | 22 ruoyi-ui/src/assets/icons/svg/chart.svg | 1 ruoyi-ui/src/components/DictTag/index.vue | 92 ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java | 52 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCacheTemplate.java | 34 ruoyi-ui/src/views/login.vue | 219 ruoyi-ui/src/api/system/config.js | 60 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java | 237 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java | 698 ruoyi-ui/src/views/redirect.vue | 12 ruoyi-ui/src/utils/dict/DictOptions.js | 51 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java | 19 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java | 132 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/SHA1.java | 36 ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java | 61 ruoyi-ui/src/views/tool/build/DraggableItem.vue | 100 ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Util.java | 79 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletPhoneEncrypteData.java | 19 ruoyi-applet/src/main/resources/i18n/messages.properties | 38 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java | 16 ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java | 135 ruoyi-ui/src/views/error/404.vue | 233 ruoyi-ui/.env.production | 8 ruoyi-ui/.env.staging | 10 ruoyi-common/src/main/java/com/ruoyi/common/filter/SmCryptoFilter.java | 88 ruoyi-ui/src/main.js | 86 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java | 72 ruoyi-ui/src/assets/404_images/404_cloud.png | 0 ruoyi-ui/src/assets/icons/svg/select.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java | 393 ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java | 16 ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java | 257 ruoyi-ui/src/utils/dict/Dict.js | 82 ruoyi-system/src/main/java/com/ruoyi/system/vo/UserInfoVo.java | 175 ruoyi-ui/src/router/index.js | 183 ruoyi-ui/src/assets/icons/svg/education.svg | 1 ruoyi-ui/src/assets/styles/transition.scss | 49 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java | 115 ruoyi-admin/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java | 61 ruoyi-ui/public/index.html | 208 ruoyi-common/src/main/java/com/ruoyi/common/core/utils/Constants.java | 143 ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java | 17 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxUtils.java | 175 ruoyi-ui/src/components/iFrame/index.vue | 36 ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java | 59 ruoyi-ui/src/directive/dialog/drag.js | 64 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java | 115 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java | 63 ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml | 47 ruoyi-common/src/main/java/com/ruoyi/common/utils/WebUtils.java | 170 ruoyi-ui/src/assets/images/login-background.jpg | 0 ruoyi-common/src/main/java/com/ruoyi/common/utils/TimeConverter.java | 19 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java | 66 ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MyFileUtil.java | 128 ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt | 1 ruoyi-system/src/main/java/com/ruoyi/system/bo/DeployBO.java | 15 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java | 102 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java | 157 ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java | 78 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java | 61 ruoyi-ui/src/assets/icons/svg/table.svg | 1 .gitignore | 53 ruoyi-ui/src/assets/icons/svg/date-range.svg | 1 ruoyi-ui/src/layout/mixin/ResizeHandler.js | 45 ruoyi-common/src/main/java/com/ruoyi/common/resp/AccessTokenRespBody.java | 28 ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java | 89 ruoyi-applet/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java | 61 ruoyi-ui/src/components/SizeSelect/index.vue | 56 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java | 68 ruoyi-ui/src/layout/components/AppMain.vue | 75 ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java | 52 ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java | 163 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java | 167 ruoyi-ui/src/assets/icons/svg/redis-list.svg | 2 ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml | 159 ruoyi-ui/src/components/Crontab/week.vue | 202 ruoyi-applet/src/main/resources/application-prod.yml | 230 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java | 174 ruoyi-ui/src/assets/icons/svg/tool.svg | 1 ruoyi-ui/src/components/ImagePreview/index.vue | 90 ruoyi-ui/src/directive/dialog/dragHeight.js | 34 ruoyi-ui/src/views/dashboard/PanelGroup.vue | 181 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysUserController.java | 391 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java | 264 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java | 296 ruoyi-common/src/main/java/com/ruoyi/common/config/WxConfig.java | 37 ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java | 56 ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java | 34 ruoyi-ui/src/plugins/cache.js | 77 ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java | 158 ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java | 154 ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestFilterConfig.java | 20 ruoyi-ui/src/plugins/auth.js | 60 ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml | 20 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java | 48 ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java | 36 ruoyi-applet/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java | 125 ruoyi-ui/src/assets/icons/svg/money.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java | 20 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java | 168 ruoyi-ui/src/api/monitor/job.js | 71 ruoyi-ui/src/components/Breadcrumb/index.vue | 74 ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java | 26 ruoyi-ui/src/assets/styles/variables.scss | 54 ruoyi-ui/src/components/ParentView/index.vue | 3 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java | 49 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java | 70 ruoyi-ui/src/assets/icons/svg/skill.svg | 1 ruoyi-ui/src/components/FileUpload/index.vue | 215 ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java | 267 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java | 91 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java | 119 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java | 94 ruoyi-ui/src/assets/icons/svg/tab.svg | 1 ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm | 169 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java | 111 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java | 55 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java | 136 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java | 86 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java | 115 ruoyi-ui/src/assets/icons/svg/password.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java | 24 ruoyi-ui/src/assets/icons/svg/wechat.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java | 102 ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java | 34 ruoyi-ui/src/assets/icons/svg/dashboard.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java | 39 ruoyi-common/src/main/java/com/ruoyi/common/utils/VideoUtil.java | 69 ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java | 77 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BasePage.java | 50 ruoyi-common/src/main/java/com/ruoyi/common/utils/CodeGenerateUtils.java | 76 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java | 224 ruoyi-ui/src/assets/icons/svg/size.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java | 132 ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java | 21 ruoyi-ui/src/directive/permission/hasRole.js | 28 ruoyi-ui/src/assets/icons/svg/shopping.svg | 1 ruoyi-ui/src/utils/ruoyi.js | 233 ruoyi-ui/src/views/system/role/authUser.vue | 199 ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java | 291 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java | 94 ruoyi-generator/src/main/resources/vm/vue/index.vue.vm | 602 ruoyi-ui/src/utils/permission.js | 51 ruoyi-admin/src/main/resources/application.yml | 4 ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java | 94 ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java | 56 ruoyi-ui/src/assets/icons/svg/dict.svg | 1 ruoyi-ui/src/assets/images/profile.jpg | 0 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WebUtils.java | 48 ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java | 409 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java | 114 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java | 60 ruoyi-ui/src/utils/auth.js | 15 ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessRefuseBO.java | 30 ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java | 17 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java | 93 ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentMailUtil.java | 207 ruoyi-ui/src/components/Pagination/index.vue | 114 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java | 259 ruoyi-common/src/main/java/com/ruoyi/common/filter/CustomRequestWrapper.java | 75 ruoyi-ui/src/views/tool/gen/index.vue | 337 ruoyi-ui/src/views/dashboard/BarChart.vue | 102 ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java | 240 ruoyi-ui/src/api/system/notice.js | 44 ruoyi-ui/src/views/index.vue | 1023 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java | 291 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java | 66 ruoyi-ui/src/assets/images/pay.png | 0 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java | 16 ruoyi-ui/src/assets/icons/svg/zip.svg | 1 ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java | 79 ruoyi-applet/src/main/resources/banner.txt | 24 ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java | 40 ruoyi-common/src/main/java/com/ruoyi/common/enums/SubmitStatusEnum.java | 33 ruoyi-ui/src/assets/icons/svg/404.svg | 1 generator/src/test/java/com/xizang/CodeGeneratorTests.java | 158 ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml | 111 ruoyi-common/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java | 50 ruoyi-ui/src/plugins/tab.js | 71 ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg | 1 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java | 29 ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java | 45 ruoyi-ui/src/utils/validate.js | 83 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java | 61 ruoyi-system/src/main/java/com/ruoyi/system/vo/SysUserVO.java | 23 ruoyi-ui/src/views/system/role/selectUser.vue | 138 ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java | 55 ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java | 81 ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java | 19 ruoyi-system/pom.xml | 93 ruoyi-ui/src/api/monitor/operlog.js | 26 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java | 77 ruoyi-applet/src/main/java/com/ruoyi/RuoYiAppletApplication.java | 65 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java | 74 ruoyi-system/src/main/java/com/ruoyi/system/query/SysOperLogQuery.java | 13 ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml | 34 ruoyi-common/src/main/java/com/ruoyi/common/enums/UpdateTypeEnum.java | 47 ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java | 97 ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties | 1 ruoyi-ui/src/assets/icons/svg/color.svg | 1 ruoyi-ui/src/assets/styles/ruoyi.scss | 277 ruoyi-ui/src/views/monitor/operlog/index.vue | 323 ruoyi-generator/src/main/resources/vm/java/mapper.java.vm | 91 ruoyi-ui/src/components/Crontab/day.vue | 161 ruoyi-ui/src/utils/generator/css.js | 18 ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java | 34 ruoyi-ui/src/assets/icons/svg/textarea.svg | 1 ruoyi-ui/src/utils/generator/drawingDefault.js | 29 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java | 46 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resq/Code2SessionResqBody.java | 21 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java | 266 ruoyi-ui/src/api/monitor/jobLog.js | 26 ruoyi-ui/src/layout/components/Sidebar/Item.vue | 33 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java | 76 ruoyi-ui/src/utils/dict/DictData.js | 13 ruoyi-ui/src/api/monitor/logininfor.js | 34 ruoyi-ui/src/assets/icons/svg/time-range.svg | 1 ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml | 127 ruoyi-ui/src/directive/permission/hasPermi.js | 28 ruoyi-common/src/main/java/com/ruoyi/common/config/SmsProperties.java | 38 ruoyi-ui/src/layout/components/InnerLink/index.vue | 47 ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java | 186 ruoyi-system/src/main/java/com/ruoyi/system/export/OpticalInspectionExport.java | 41 ruoyi-ui/src/assets/icons/svg/button.svg | 1 ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java | 56 ruoyi-ui/public/html/ie.html | 46 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java | 84 ruoyi-ui/src/assets/icons/svg/fullscreen.svg | 1 ruoyi-applet/src/main/resources/mybatis/mybatis-config.xml | 20 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java | 32 ruoyi-ui/src/assets/icons/svg/post.svg | 1 ruoyi-ui/src/assets/icons/svg/swagger.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/export/ContractExport.java | 51 ruoyi-ui/src/components/Crontab/result.vue | 559 ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java | 124 ruoyi-ui/src/components/Crontab/month.vue | 114 ruoyi-ui/src/utils/generator/config.js | 438 ruoyi-ui/src/views/system/notice/index.vue | 312 ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java | 16 ruoyi-ui/.eslintrc.js | 199 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java | 121 ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java | 64 ruoyi-ui/src/views/monitor/job/index.vue | 513 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java | 81 ruoyi-ui/src/assets/icons/svg/rate.svg | 1 ruoyi-ui/src/components/RightToolbar/index.vue | 104 ruoyi-ui/src/views/system/config/index.vue | 343 ruoyi-ui/src/assets/icons/svg/peoples.svg | 1 ruoyi-ui/src/assets/images/light.svg | 39 ruoyi-ui/src/components/Crontab/min.vue | 116 ruoyi-admin/src/main/resources/i18n/messages.properties | 38 ruoyi-admin/src/main/resources/logback.xml | 93 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java | 136 ruoyi-ui/src/components/Crontab/year.vue | 131 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java | 60 ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml | 252 ruoyi-system/src/main/java/com/ruoyi/system/dto/TExamineDTO.java | 14 ruoyi-ui/src/api/system/dict/data.js | 52 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java | 135 ruoyi-common/src/main/java/com/ruoyi/common/enums/DisabledEnum.java | 43 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java | 187 ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java | 67 ruoyi-ui/src/api/monitor/online.js | 18 ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java | 83 ruoyi-ui/src/assets/icons/svg/search.svg | 1 ruoyi-ui/src/assets/icons/svg/language.svg | 1 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java | 51 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java | 98 ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java | 121 ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java | 132 ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml | 34 ruoyi-admin/src/main/java/com/ruoyi/web/aspect/StateAspect.java | 45 ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java | 20 ruoyi-ui/src/assets/icons/svg/upload.svg | 1 ruoyi-system/src/main/java/com/ruoyi/system/vo/RoleInfoVO.java | 16 ruoyi-ui/src/directive/dialog/dragWidth.js | 30 ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java | 27 ruoyi-ui/babel.config.js | 13 ruoyi-ui/src/views/monitor/logininfor/index.vue | 246 ruoyi-ui/src/assets/icons/svg/download.svg | 1 ruoyi-ui/src/assets/icons/svg/list.svg | 1 ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java | 60 ruoyi-ui/src/assets/icons/svg/question.svg | 1 ruoyi-admin/pom.xml | 190 ruoyi-ui/src/api/system/role.js | 119 ruoyi-ui/src/components/HeaderSearch/index.vue | 198 ruoyi-common/src/main/java/com/ruoyi/common/enums/StateProcessActionEnum.java | 26 ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java | 185 ruoyi-ui/src/views/system/user/profile/resetPwd.vue | 68 ruoyi-ui/src/App.vue | 28 ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java | 26 ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java | 44 ruoyi-applet/src/main/java/com/ruoyi/RuoYiServletInitializer.java | 18 ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java | 34 ruoyi-ui/src/components/Crontab/second.vue | 117 ruoyi-common/pom.xml | 224 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java | 102 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java | 38 ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java | 26 ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java | 76 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxJsonUtils.java | 109 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java | 83 ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java | 24 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java | 71 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java | 124 ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/Code2SessionRespBody.java | 29 747 files changed, 88,723 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 32858aa..ed8368a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,47 @@ -*.class +###################################################################### +# Build Tools -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar -# Package Files # -*.jar -*.war -*.ear +target/ +!.mvn/wrapper/maven-wrapper.jar -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +###################################################################### +# 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8564f29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +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. \ No newline at end of file diff --git a/README.md b/README.md index 8d88cea..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,4 +0,0 @@ -## laboratory - -乐山实验室 - diff --git a/bin/clean.bat b/bin/clean.bat new file mode 100644 index 0000000..24c0974 --- /dev/null +++ b/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [��Ϣ] ������target����·���� +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..c693ec0 --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [��Ϣ] ���Web���̣�����war/jar���ļ��� +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/bin/run.bat b/bin/run.bat new file mode 100644 index 0000000..41efbd0 --- /dev/null +++ b/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [��Ϣ] ʹ��Jar��������Web���̡� +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 \ No newline at end of file diff --git "a/doc/\350\213\245\344\276\235\347\216\257\345\242\203\344\275\277\347\224\250\346\211\213\345\206\214.docx" "b/doc/\350\213\245\344\276\235\347\216\257\345\242\203\344\275\277\347\224\250\346\211\213\345\206\214.docx" new file mode 100644 index 0000000..9e4daef --- /dev/null +++ "b/doc/\350\213\245\344\276\235\347\216\257\345\242\203\344\275\277\347\224\250\346\211\213\345\206\214.docx" Binary files differ diff --git a/generator/pom.xml b/generator/pom.xml new file mode 100644 index 0000000..563d41d --- /dev/null +++ b/generator/pom.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>generator</artifactId> + + <properties> + <maven.compiler.source>8</maven.compiler.source> + <maven.compiler.target>8</maven.compiler.target> + </properties> + <dependencies> + + <!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity-engine-core --> + <dependency> + <groupId>org.apache.velocity</groupId> + <artifactId>velocity-engine-core</artifactId> + <version>2.3</version> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.2</version> + <scope>test</scope> + </dependency> + <!-- jdbc 连接包 --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + </dependency> + <dependency> + <groupId>com.baomidou</groupId> + <artifactId>mybatis-plus-generator</artifactId> + <version>3.4.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-common</artifactId> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/generator/src/test/java/com/xizang/CodeGeneratorTests.java b/generator/src/test/java/com/xizang/CodeGeneratorTests.java new file mode 100644 index 0000000..8a5e34c --- /dev/null +++ b/generator/src/test/java/com/xizang/CodeGeneratorTests.java @@ -0,0 +1,158 @@ +package com.xizang; + +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.generator.AutoGenerator; +import com.baomidou.mybatisplus.generator.InjectionConfig; +import com.baomidou.mybatisplus.generator.config.*; +import com.baomidou.mybatisplus.generator.config.po.LikeTable; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * 通过指定 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) 可达到加速的效果。 + * 这时测试类启动时就只会初始化 Spring 上下文,不再启动 Tomcat 容器 + */ +public class CodeGeneratorTests { + + private static final String DRIVER_NAME = "com.mysql.cj.jdbc.Driver"; + + private static final String JDBC_URL = "jdbc:mysql://xzgt.test.591taxi.cn:13306/xizang?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"; + private static final String USER_NAME = "root"; + private static final String PASSWORD = "8f5z9g52gx4bg"; + + + // 防止误生成 + private static final String BASE_PACKAGE = "com.ruoyi.system"; + + @Test + public void contextLoads() { + // 代码生成器 + AutoGenerator mpg = new AutoGenerator(); + + // 全局配置 + GlobalConfig gc = new GlobalConfig(); + String projectPath = "D:\\workspaces\\Project\\company\\changyun\\xizang\\xizang\\generator"; + gc.setOutputDir(projectPath + "/src/main/java") + .setAuthor("yupeng") + .setMapperName("%sMapper") + .setXmlName("%sMapper") + .setServiceName("%sService") + .setServiceImplName("%sServiceImpl") + .setControllerName("%sController") + .setOpen(false)//当代码生成完成之后是否打开代码所在的文件夹 + .setSwagger2(true) //实体属性 Swagger2 注解 + .setFileOverride(true)//是否覆盖 + //.setActiveRecord(true) + .setEnableCache(false)// XML 二级缓存 + .setBaseResultMap(true)// XML ResultMap + .setBaseColumnList(true);// XML columList + mpg.setGlobalConfig(gc); + + // 数据源配置 + DataSourceConfig dsc = new DataSourceConfig(); + dsc.setUrl(JDBC_URL); + // dsc.setSchemaName("public"); + dsc.setDriverName(DRIVER_NAME); + dsc.setUsername(USER_NAME); + dsc.setPassword(PASSWORD); + mpg.setDataSource(dsc); + + // 包配置 + PackageConfig pc = new PackageConfig(); + // pc.setModuleName(scanner("模块名")); + // pc.setModuleName("sys"); + pc.setParent(BASE_PACKAGE);//controller entity service service.impl + pc.setController("controller"); + pc.setEntity("model"); + pc.setMapper("mapper"); + pc.setService("service"); + + + // 自定义mapping文件生成路径配置 + InjectionConfig cfg = new InjectionConfig() { + @Override + public void initMap() { + // to do nothing + } + }; + // 如果模板引擎是 velocity + String templatePath = "/templates/mapper.xml.vm"; + + // 自定义输出配置 + List<FileOutConfig> focList = new ArrayList<>(); + // 自定义配置会被优先输出 + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! + return projectPath + "/src/main/resources/mapping/" + + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; + } + }); + cfg.setFileOutConfigList(focList); + mpg.setCfg(cfg); + // 配置模板 + TemplateConfig templateConfig = new TemplateConfig(); + templateConfig.setXml(null); + mpg.setTemplate(templateConfig); + mpg.setPackageInfo(pc); + + // 配置模板 + // 不生成controller + //TemplateConfig templateConfig = new TemplateConfig(); + //templateConfig.setController(null); + //mpg.setTemplate(templateConfig); + + + // 策略配置 + StrategyConfig strategy = new StrategyConfig(); + //设置字段和表名的是否把下划线完成驼峰命名规则 + strategy.setNaming(NamingStrategy.underline_to_camel); + strategy.setEntitySerialVersionUID(true); + strategy.setEntityColumnConstant(false); + + + //设置生成的实体类继承的父类 +// strategy.setSuperEntityClass(BaseModel.class); + // 生成字段常量 + // strategy.setEntityColumnConstant(true); +// strategy.setSuperEntityColumns("create_by", "update_by", "disabled", "create_time", "last_time"); + strategy.setEntityLombokModel(true);//是否启动lombok + + strategy.setRestControllerStyle(true);//是否生成resetController + strategy.setLogicDeleteFieldName("isDelete"); + + // 字段自动操作策略 + /* List<TableFill> tableFillList = new ArrayList<>(); + tableFillList.add(new TableFill("create_time", FieldFill.INSERT)); + tableFillList.add(new TableFill("update_time", FieldFill.INSERT_UPDATE)); + // 表字段与属性映射关系 + strategy.setTableFillList(tableFillList);*/ + + // 公共父类 + // strategy.setSuperControllerClass("com.sxt.BaseController"); + // 写于父类中的公共字段 + // strategy.setSuperEntityColumns("person_id","person_name"); + //要设置生成哪些表 如果不设置就是生成所有的表 + // strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); + strategy.setControllerMappingHyphenStyle(true); + strategy.setEntityTableFieldAnnotationEnable(true); +// strategy.setTablePrefix(pc.getModuleName() + ""); +// strategy.setLikeTable(new LikeTable("room")); + //strategy.setLikeTable(new LikeTable("member")); + strategy.setLikeTable(new LikeTable("sys_file"));// 生成表名 +// strategy.setLikeTable(new LikeTable("t_hotel"));// 生成表名 +// strategy.setLikeTable(new LikeTable("t_scan_message"));// 生成表名 +// strategy.setNotLikeTable(new LikeTable("hotel_info"));// 不生成表名 +// strategy.setLikeTable(); + // strategy.setTablePrefix("sys_"); + mpg.setStrategy(strategy); + mpg.execute(); + } + + +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7e522a1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,233 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi</artifactId> + <version>3.8.6</version> + + <name>ruoyi</name> + <url>http://www.ruoyi.vip</url> + <description>若依管理系统</description> + + <properties> + <ruoyi.version>3.8.6</ruoyi.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <java.version>1.8</java.version> + <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> + <druid.version>1.2.16</druid.version> + <bitwalker.version>1.21</bitwalker.version> + <swagger.version>3.0.0</swagger.version> + <kaptcha.version>2.3.3</kaptcha.version> + <pagehelper.boot.version>1.4.6</pagehelper.boot.version> + <fastjson.version>2.0.39</fastjson.version> + <oshi.version>6.4.4</oshi.version> + <commons.io.version>2.13.0</commons.io.version> + <commons.collections.version>3.2.2</commons.collections.version> + <poi.version>4.1.2</poi.version> + <velocity.version>2.3</velocity.version> + <jwt.version>0.9.1</jwt.version> + </properties> + + <!-- 依赖声明 --> + <dependencyManagement> + <dependencies> + + <!-- SpringBoot的依赖配置--> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>2.5.15</version> + <type>pom</type> + <scope>import</scope> + </dependency> + + <!-- 阿里数据库连接池 --> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>druid-spring-boot-starter</artifactId> + <version>${druid.version}</version> + </dependency> + + <!-- 解析客户端操作系统、浏览器等 --> + <dependency> + <groupId>eu.bitwalker</groupId> + <artifactId>UserAgentUtils</artifactId> + <version>${bitwalker.version}</version> + </dependency> + + <!-- 获取系统信息 --> + <dependency> + <groupId>com.github.oshi</groupId> + <artifactId>oshi-core</artifactId> + <version>${oshi.version}</version> + </dependency> + + <!-- Swagger3依赖 --> + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-boot-starter</artifactId> + <version>${swagger.version}</version> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>io.swagger</groupId>--> +<!-- <artifactId>swagger-models</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- io常用工具类 --> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>${commons.io.version}</version> + </dependency> + + <!-- excel工具 --> + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-ooxml</artifactId> + <version>${poi.version}</version> + </dependency> + + + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi</artifactId> + <version>${poi.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-ooxml-schemas</artifactId> + <version>${poi.version}</version> + </dependency> + + <!-- velocity代码生成使用模板 --> + <dependency> + <groupId>org.apache.velocity</groupId> + <artifactId>velocity-engine-core</artifactId> + <version>${velocity.version}</version> + </dependency> + + <!-- collections工具类 --> + <dependency> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + <version>${commons.collections.version}</version> + </dependency> + + <!-- 阿里JSON解析器 --> + <dependency> + <groupId>com.alibaba.fastjson2</groupId> + <artifactId>fastjson2</artifactId> + <version>${fastjson.version}</version> + </dependency> + + <!-- Token生成与解析--> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + <version>${jwt.version}</version> + </dependency> + + <!-- 验证码 --> + <dependency> + <groupId>pro.fessional</groupId> + <artifactId>kaptcha</artifactId> + <version>${kaptcha.version}</version> + </dependency> + + <!-- 定时任务--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-quartz</artifactId> + <version>${ruoyi.version}</version> + </dependency> + + <!-- 代码生成--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-generator</artifactId> + <version>${ruoyi.version}</version> + </dependency> + + <!-- 核心模块--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-framework</artifactId> + <version>${ruoyi.version}</version> + </dependency> + + <!-- 系统模块--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-system</artifactId> + <version>${ruoyi.version}</version> + </dependency> + + <!-- 通用工具--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-common</artifactId> + <version>${ruoyi.version}</version> + </dependency> + + </dependencies> + </dependencyManagement> + + <modules> + <module>ruoyi-admin</module> + <module>ruoyi-framework</module> + <module>ruoyi-system</module> + <module>ruoyi-quartz</module> + <module>ruoyi-generator</module> + <module>ruoyi-common</module> + <module>ruoyi-applet</module> + </modules> + <packaging>pom</packaging> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.1</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> + </plugin> + </plugins> + </build> + + <repositories> + <repository> + <id>public</id> + <name>aliyun nexus</name> + <url>https://maven.aliyun.com/repository/public</url> + <releases> + <enabled>true</enabled> + </releases> + </repository> + </repositories> + + <pluginRepositories> + <pluginRepository> + <id>public</id> + <name>aliyun nexus</name> + <url>https://maven.aliyun.com/repository/public</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>false</enabled> + </snapshots> + </pluginRepository> + </pluginRepositories> + +</project> \ No newline at end of file diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml new file mode 100644 index 0000000..18b4c52 --- /dev/null +++ b/ruoyi-admin/pom.xml @@ -0,0 +1,190 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <packaging>jar</packaging> + <artifactId>ruoyi-admin</artifactId> + + <description> + web服务入口 + </description> + + <dependencies> + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + <version>3.8.0</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>31.1-jre</version> <!-- 请根据需要选择合适的版本 --> + </dependency> + <!-- spring-boot-devtools --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-devtools</artifactId> + <optional>true</optional> <!-- 表示依赖不会传递 --> + </dependency> + + <!-- swagger3--> + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-boot-starter</artifactId> + </dependency> + +<!-- <dependency>--> +<!-- <groupId>com.github.xiaoymin</groupId>--> +<!-- <artifactId>swagger-bootstrap-ui</artifactId>--> +<!-- <version>1.9.6</version>--> +<!-- </dependency>--> + + + <!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 --> + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-models</artifactId> + <version>1.6.2</version> + </dependency> + + <!-- Mysql驱动包 --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + </dependency> + + <!-- 核心模块--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-framework</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- 定时任务--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-quartz</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- 代码生成--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-generator</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- zxing生成二维码 --> + <dependency> + <groupId>com.google.zxing</groupId> + <artifactId>core</artifactId> + <version>3.3.3</version> + </dependency> + + <dependency> + <groupId>com.google.zxing</groupId> + <artifactId>javase</artifactId> + <version>3.3.3</version> + </dependency> + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <version>5.1.3.RELEASE</version> + </dependency> +<!-- <dependency>--> +<!-- <groupId>org.apache.httpcomponents</groupId>--> +<!-- <artifactId>httpcore</artifactId>--> +<!-- <version>4.3.2</version>--> +<!-- </dependency>--> + + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp</artifactId> + <version>4.9.3</version> + </dependency> + + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>fastjson</artifactId> + <version>1.2.78</version> + </dependency> + + <!-- easypoi --> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-base</artifactId> + <version>4.3.0</version> + </dependency> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-web</artifactId> + <version>4.3.0</version> + </dependency> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-annotation</artifactId> + <version>4.3.0</version> + </dependency> + + <!-- 阿里云短信 --> + <dependency> + <groupId>com.aliyun</groupId> + <artifactId>dysmsapi20170525</artifactId> + <version>2.0.10</version> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>2.5.15</version> + <configuration> + <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 --> + </configuration> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>3.1.0</version> + <configuration> + <failOnMissingWebXml>false</failOnMissingWebXml> + <warName>${project.artifactId}</warName> + </configuration> + </plugin> + </plugins> + <finalName>${project.artifactId}</finalName> + </build> + +</project> \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java new file mode 100644 index 0000000..558fe10 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -0,0 +1,69 @@ +package com.ruoyi; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.client.RestTemplate; +import springfox.documentation.oas.annotations.EnableOpenApi; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 启动程序 + * + * @author ruoyi + */ +@Slf4j +@EnableOpenApi +@EnableCaching +@EnableScheduling//开启定时任务 +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +public class RuoYiApplication +{ + public static void main(String[] args) { + + + ConfigurableApplicationContext application = SpringApplication.run(RuoYiApplication.class, args); + try { + + Environment env = application.getEnvironment(); + log.info("\n----------------------------------------------------------\n\t" + + "应用 '{}' 运行成功! 访问连接:\n\t" + + "Swagger文档: \t\thttp://{}:{}/doc.html\n" + + "----------------------------------------------------------", + env.getProperty("spring.application.name", "后台"), + InetAddress.getLocalHost().getHostAddress(), + env.getProperty("server.port", "8081")); + }catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * 当不存在此 wxRestTemplate 使用此方法的bean注入 + * + * @return + */ + @Bean + @ConditionalOnMissingBean(name = "restTemplate") + public RestTemplate wxRestTemplate() { + //复杂构造函数的使用 + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + // 设置超时 + requestFactory.setConnectTimeout(6000); + requestFactory.setReadTimeout(6000); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setRequestFactory(requestFactory); + return restTemplate; + } + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java new file mode 100644 index 0000000..6de67dc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java @@ -0,0 +1,18 @@ +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); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/aspect/StateAspect.java b/ruoyi-admin/src/main/java/com/ruoyi/web/aspect/StateAspect.java new file mode 100644 index 0000000..20e5b87 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/aspect/StateAspect.java @@ -0,0 +1,45 @@ +package com.ruoyi.web.aspect; + +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +@Aspect +@Component + +public class StateAspect { + + @Autowired + private ISysUserService sysUserService; + + @Autowired + private ISysRoleService roleService; + @Pointcut("execution(* com.ruoyi.web.controller.manage.*.*(..)) && !execution( * com.ruoyi.web.controller.manage.LoginController.*.*(..))") + public void state(){} + + @Before("state()") + public void isfrozen(){ + System.err.println("进入切面"); + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser sysUser = sysUserService.selectUserById(loginUser.getUserId()); + if (sysUser.getStatus().equals("1")){ + throw new ServiceException("当前账号已停用"); + } + if (roleService.selectRoleByUserId(sysUser.getUserId()).getStatus()==1){ + throw new ServiceException("当前角色已停用"); + }; + + + } + + + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TestController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TestController.java new file mode 100644 index 0000000..277b533 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TestController.java @@ -0,0 +1,30 @@ +package com.ruoyi.web.controller.api; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.domain.entity.SysUser; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@Api(tags = "测试接口") +@RestController +@RequestMapping("/api") +public class TestController { + + @ApiOperation(value = "处理加密后的数据") + @PostMapping("/data") + public R<PageInfo<SysUser>> processData(@RequestBody String encryptedRequest) { + try { + // 解密整个请求体 + System.out.println("过滤器解密后发送的请求体: " + encryptedRequest); + + return R.ok(new PageInfo<>(1, 10)); + } catch (Exception e) { + throw new RuntimeException("解密或处理消息失败", e); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java new file mode 100644 index 0000000..d2d6e8c --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -0,0 +1,94 @@ +package com.ruoyi.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.google.code.kaptcha.Producer; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.sign.Base64; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java new file mode 100644 index 0000000..cec5006 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -0,0 +1,163 @@ +package com.ruoyi.web.controller.common; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.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.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +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; +import com.ruoyi.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@RestController +@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") + 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") + public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + List<String> urls = new ArrayList<String>(); + List<String> fileNames = new ArrayList<String>(); + List<String> newFileNames = new ArrayList<String>(); + List<String> originalFilenames = new ArrayList<String>(); + 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 + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); + // 下载名称 + 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); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java new file mode 100644 index 0000000..b2cbb16 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java @@ -0,0 +1,17 @@ +//package com.ruoyi.web.controller.interceptor; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +//@Configuration +//public class MybatisConfiguration { +// +// /** +// * 注册拦截器 +// */ +// @Bean +// public MybatisInterceptor getMybatisInterceptor() { +// return new MybatisInterceptor(); +// } +// +//} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java new file mode 100644 index 0000000..fcbd5dc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java @@ -0,0 +1,119 @@ +//package com.ruoyi.web.controller.interceptor; +// +//import com.ruoyi.framework.web.service.TokenService; +//import lombok.extern.slf4j.Slf4j; +//import org.apache.ibatis.executor.Executor; +//import org.apache.ibatis.mapping.MappedStatement; +//import org.apache.ibatis.mapping.SqlCommandType; +//import org.apache.ibatis.plugin.*; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.stereotype.Component; +// +//import java.lang.reflect.Field; +//import java.time.LocalDateTime; +//import java.util.*; +// +//@Slf4j +//@Component +//@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) }) +//public class MybatisInterceptor implements Interceptor { +// +// @Autowired +// private TokenService tokenService; +// +// @Override +// public Object intercept(Invocation invocation) throws Throwable { +// MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; +// if("com.ruoyi.system.mapper.SysLogininforMapper.insertLogininfor".equals(mappedStatement.getId())){ +// return invocation.proceed(); +// } +// // sql类型:insert、update、select、delete +// SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); +// Object parameter = invocation.getArgs()[1]; +// +// if (parameter == null) { +// return invocation.proceed(); +// } +// +// // 当sql为新增或更新类型时,自动填充操作人相关信息 +// if (SqlCommandType.INSERT == sqlCommandType) { +// +// Field[] fields = getAllFields(parameter); +// for (Field field : fields) { +// try { +// // 注入创建人 +// if ("createBy".equals(field.getName())) { +// // 获取当前登录用户信息 +// if(Objects.nonNull(tokenService.getLoginUser())){ +// String userName = tokenService.getLoginUser().getUser().getUserName(); +// field.setAccessible(true); +// field.set(parameter, userName); +// field.setAccessible(false); +// } +// } +// //注入创建时间 +// if ("createTime".equals(field.getName())) { +// field.setAccessible(true); +//// field.set(parameter, LocalDateTime.now()); +// field.setAccessible(false); +// } +// } catch (Exception e) { +// log.error("failed to insert data, exception = ", e); +// } +// } +// } +// if (SqlCommandType.UPDATE == sqlCommandType) { +// Field[] fields = getAllFields(parameter); +// for (Field field : fields) { +// try { +// if ("updateBy".equals(field.getName())) { +// // 获取当前登录用户信息 +// if(Objects.nonNull(tokenService.getLoginUser())){ +// String userName = tokenService.getLoginUser().getUser().getUserName(); +// field.setAccessible(true); +// field.set(parameter, userName); +// field.setAccessible(false); +// } +// } +// if ("updateTime".equals(field.getName())) { +// field.setAccessible(true); +//// field.set(parameter, new Date()); +// field.setAccessible(false); +// } +// } catch (Exception e) { +// log.error("failed to update data, exception = ", e); +// } +// } +// } +// return invocation.proceed(); +// } +// +// @Override +// public Object plugin(Object target) { +// return Plugin.wrap(target, this); +// } +// +// @Override +// public void setProperties(Properties properties) { +// // TODO Auto-generated method stub +// } +// +// /** +// * 获取类的所有属性,包括父类 +// * +// * @param object +// * @return +// */ +// private Field[] getAllFields(Object object) { +// Class<?> clazz = object.getClass(); +// List<Field> fieldList = new ArrayList<>(); +// while (clazz != null) { +// fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); +// clazz = clazz.getSuperclass(); +// } +// Field[] fields = new Field[fieldList.size()]; +// fieldList.toArray(fields); +// return fields; +// } +// +//} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..69470d0 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java @@ -0,0 +1,120 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysCache; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate<String, String> redisTemplate; + + private final static List<SysCache> caches = new ArrayList<SysCache>(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Properties info = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback<Object>) connection -> connection.dbSize()); + + Map<String, Object> result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List<Map<String, String>> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map<String, String> data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() + { + return AjaxResult.success(caches); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) + { + Set<String> cacheKeys = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(cacheKeys); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) + { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue); + return AjaxResult.success(sysCache); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) + { + Collection<String> cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) + { + redisTemplate.delete(cacheKey); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() + { + Collection<String> cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..cc805ad --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -0,0 +1,27 @@ +package com.ruoyi.web.controller.monitor; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/server") +public class ServerController +{ + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..1039b43 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,82 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +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.framework.web.service.SysPasswordService; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { +// startPage(); + List<SysLogininfor> list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + +// @Log(title = "登录日志", businessType = BusinessType.EXPORT) +// @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysLogininfor logininfor) +// { +// List<SysLogininfor> list = logininforService.selectLogininforList(logininfor); +// ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class); +// util.exportExcel(response, list, "登录日志"); +// } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) + { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..e477c37 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.controller.monitor; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.query.SysOperLogQuery; +import com.ruoyi.system.service.ISysOperLogService; +import com.ruoyi.system.vo.SysOperLogVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@Slf4j +@Api(tags = "操作日志记录") +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private ISysOperLogService operLogService; + + @ApiOperation(value = "操作日志分页列表") + @PostMapping("/list") + public AjaxResult list(@RequestBody SysOperLogQuery query) + { + PageInfo<SysOperLogVO> list = operLogService.selectOperLogPageList(query); + return AjaxResult.success(list); + } + + @Log(title = "删除操作日志", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteById/{operIds}") + public AjaxResult remove(@PathVariable String operIds) + { + String[] split = operIds.split(","); + List<Long> id = new ArrayList<>(); + for (String s : split) { + id.add(Long.valueOf(s)); + } + return AjaxResult.success(operLogService.deleteOperLogByIds(id)); + } + + @Log(title = "清除操作日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..a442863 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,83 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private RedisCache redisCache; + + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) + { + Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List<SysUserOnline> userOnlineList = new ArrayList<SysUserOnline>(); + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) + { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + else if (StringUtils.isNotEmpty(ipaddr)) + { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) + { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + else + { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..5737466 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java @@ -0,0 +1,121 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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 + */ +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + +// @Log(title = "参数管理", businessType = BusinessType.EXPORT) +// @PreAuthorize("@ss.hasPermi('system:config:export')") +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysConfig config) +// { +// List<SysConfig> list = configService.selectConfigList(config); +// ExcelUtil<SysConfig> util = new ExcelUtil<SysConfig>(SysConfig.class); +// util.exportExcel(response, list, "参数数据"); +// } + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..a74355f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java @@ -0,0 +1,132 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.entity.SysDept; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + // @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) + { + List<SysDept> depts = deptService.selectDeptList(dept); + return AjaxResult.success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + // @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) + { + List<SysDept> depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return AjaxResult.success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + // @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return AjaxResult.success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + // @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) + { + if (!deptService.checkDeptNameUnique(dept)) + { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return AjaxResult.success(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + // @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody 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 error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return AjaxResult.success(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + // @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) + { + if (deptService.hasChildByDeptId(deptId)) + { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return AjaxResult.success(deptService.deleteDeptById(deptId)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..18f47f7 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -0,0 +1,126 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictDataService; +import com.ruoyi.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@Api(tags = "数据字典信息") +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController +{ + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) + { +// startPage(); + List<SysDictData> list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + +// @Log(title = "字典数据", businessType = BusinessType.EXPORT) +// @PreAuthorize("@ss.hasPermi('system:dict:export')") +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysDictData dictData) +// { +// List<SysDictData> list = dictDataService.selectDictDataList(dictData); +// ExcelUtil<SysDictData> util = new ExcelUtil<SysDictData>(SysDictData.class); +// util.exportExcel(response, list, "字典数据"); +// } + + /** + * 查询字典数据详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @ApiOperation(value = "根据字典类型查询字典数据信息") + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List<SysDictData> data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList<SysDictData>(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) + { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) + { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable Long[] dictCodes) + { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..855d191 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java @@ -0,0 +1,131 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +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 + */ +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) + { +// startPage(); + List<SysDictType> list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + +// @Log(title = "字典类型", businessType = BusinessType.EXPORT) +// @PreAuthorize("@ss.hasPermi('system:dict:export')") +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysDictType dictType) +// { +// List<SysDictType> list = dictTypeService.selectDictTypeList(dictType); +// ExcelUtil<SysDictType> util = new ExcelUtil<SysDictType>(SysDictType.class); +// util.exportExcel(response, list, "字典类型"); +// } + + /** + * 查询字典类型详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) + { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..13007eb --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java @@ -0,0 +1,29 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.StringUtils; + +/** + * 首页 + * + * @author ruoyi + */ +@RestController +public class SysIndexController +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() + { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..a9f2046 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -0,0 +1,171 @@ +package com.ruoyi.web.controller.system; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.web.controller.tool.MsgUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import com.ruoyi.common.constant.Constants; +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.domain.model.LoginBody; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author ruoyi + */ +@Api(tags = "登录") +@RestController +public class SysLoginController +{ + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + @Autowired + private RedisCache redisCache; + @Autowired + private TokenService tokenService; + @Autowired + private ISysRoleService roleService; + @Autowired + private MsgUtils msgUtils; + + /** + * 账号密码登录 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @ApiOperation(value = "账号密码登录",notes = "账号密码登录") + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + LoginUser loginUser = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, tokenService.createToken(loginUser)); + List<SysRole> roles = loginUser.getUser().getRoles(); + if(CollectionUtils.isEmpty(roles)){ + return AjaxResult.error("请关联角色!"); + } + if(roles.get(0).getStatus() == 1){ + return AjaxResult.error("该账号角色已被禁用!"); + } + + List<SysMenu> menus = roleService.roleInfoFromUserId(loginUser.getUserId()); + + ajax.put("menus",menus); + ajax.put("roleName",roles.get(0).getRoleName()); + ajax.put("userInfo",loginUser); + return ajax; + } + + /** + * 账号密码登录 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @ApiOperation(value = "短信登录",notes = "短信登录") + @PostMapping("/loginCode") + public AjaxResult loginCode(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + LoginUser loginUser = loginService.loginCode(loginBody.getUsername(), loginBody.getCode()); + ajax.put(Constants.TOKEN, tokenService.createToken(loginUser)); + List<SysRole> roles = loginUser.getUser().getRoles(); + if(CollectionUtils.isEmpty(roles)){ + return AjaxResult.error("请关联角色!"); + } + List<SysMenu> menus = roleService.roleInfoFromUserId(loginUser.getUserId()); + + ajax.put("menus",menus); + ajax.put("roleName",roles.get(0).getRoleName()); + ajax.put("userInfo",loginUser); + return ajax; + } + + /** + * 获取验证码 + * + * @param phone 手机号 + * @return 结果 + */ + @ApiOperation(value = "获取验证码",notes = "获取验证码") + @GetMapping("/getCode") + public AjaxResult getCode(@RequestParam String phone) + { + // 发送验证码并存储到redis + if (StringUtils.hasLength(phone)) { + String code = String.valueOf((int) (Math.random() * 1000000)); + redisCache.setCacheObject(phone, code,5*60,TimeUnit.SECONDS); + try { + msgUtils.sendMsg(phone, code); + } catch (Exception e) { + throw new RuntimeException(e); + } + return AjaxResult.success("发送短信验证码成功!5分钟内有效"); + } + return AjaxResult.error(500, "发送短信验证码失败,请确认手机号码!"); + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("getInfo") + public AjaxResult getInfo() + { + SysUser user = SecurityUtils.getLoginUser().getUser(); + // 角色集合 + Set<String> roles = permissionService.getRolePermission(user); + // 权限集合 + Set<String> permissions = permissionService.getMenuPermission(user); + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..ad9b0f4 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java @@ -0,0 +1,168 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +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.entity.SysMenu; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@Api(tags = "菜单信息") +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + @ApiOperation("菜单权限(有层级)") + @GetMapping("/levelList") + public AjaxResult levelList() + { + // 获取当前角色的菜单列表 + List<SysMenu> menus = menuService.selectList(); + if(menus.size()==0){ + return AjaxResult.success(new ArrayList<>()); + } + // 第三级 + List<SysMenu> s3 = menus.stream().filter(e -> e.getMenuType().equals("F")).collect(Collectors.toList()); + // 第二级 + List<SysMenu> s2 = menus.stream().filter(e -> e.getMenuType().equals("C")).collect(Collectors.toList()); + // 第一级 + List<SysMenu> s1 = menus.stream().filter(e -> e.getMenuType().equals("M")).collect(Collectors.toList()); + + for (SysMenu menu : s2) { + List<SysMenu> collect = s3.stream().filter(e -> e.getParentId().equals(menu.getMenuId())).collect(Collectors.toList()); + menu.setChildren(collect); + } + for (SysMenu menu : s1) { + List<SysMenu> collect = s2.stream().filter(e -> e.getParentId().equals(menu.getMenuId())).collect(Collectors.toList()); + menu.setChildren(collect); + } + + return AjaxResult.success(s1); + } + + /** + * 获取菜单列表 + */ + // @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) + { + List<SysMenu> menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + // @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return AjaxResult.success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List<SysMenu> menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List<SysMenu> menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + // @PreAuthorize("@ss.hasPermi('system:menu:add')") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return AjaxResult.success(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + // @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return AjaxResult.success(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + // @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) + { + if (menuService.hasChildByMenuId(menuId)) + { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return warn("菜单已分配,不允许删除"); + } + return AjaxResult.success(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..3d7352a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java @@ -0,0 +1,91 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) + { +// startPage(); + List<SysNotice> list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:add')") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) + { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:edit')") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) + { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:remove')") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable Long[] noticeIds) + { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java new file mode 100644 index 0000000..4d18083 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java @@ -0,0 +1,129 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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 + */ +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) + { +// startPage(); + List<SysPost> list = postService.selectPostList(post); + return getDataTable(list); + } + +// @Log(title = "岗位管理", businessType = BusinessType.EXPORT) +// @PreAuthorize("@ss.hasPermi('system:post:export')") +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysPost post) +// { +// List<SysPost> list = postService.selectPostList(post); +// ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class); +// util.exportExcel(response, list, "岗位数据"); +// } + + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:edit')") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) + { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List<SysPost> posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..be5af6a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -0,0 +1,136 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +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.core.domain.model.LoginUser; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.MimeTypeUtils; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @GetMapping + public AjaxResult profile() + { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + return ajax; + } + + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) + { + LoginUser loginUser = getLoginUser(); + SysUser currentUser = loginUser.getUser(); + currentUser.setNickName(user.getNickName()); + currentUser.setEmail(user.getEmail()); + currentUser.setPhonenumber(user.getPhonenumber()); + currentUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserProfile(currentUser) > 0) + { + // 更新缓存用户信息 + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(String oldPassword, String newPassword) + { + LoginUser loginUser = getLoginUser(); + String userName = loginUser.getUsername(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) + { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return error("新密码不能与旧密码相同"); + } + if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) + { + // 更新缓存用户密码 + loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception + { + if (!file.isEmpty()) + { + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); + if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..fe19249 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java @@ -0,0 +1,38 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysRegisterService; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author ruoyi + */ +@RestController +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) + { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..5c53a61 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java @@ -0,0 +1,322 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.system.dto.SysRoleDTO; +import com.ruoyi.system.query.SysRoleQuery; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.system.vo.RoleInfoVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +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.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author ruoyi + */ +@Api(tags = "角色信息") +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + @Autowired + private ISysMenuService menuService; + + @PreAuthorize("@ss.hasPermi('system:role')") + @ApiOperation(value = "角色列表") + @PostMapping("/list") + public AjaxResult list(@RequestBody SysRoleQuery query) + { + PageInfo<SysRole> list = roleService.selectPageList(query); + return AjaxResult.success(list); + } + + @PreAuthorize("@ss.hasPermi('system:role')") + @ApiOperation(value = "角色列表不分页") + @PostMapping("/listNotPage") + public AjaxResult list() + { + List<SysRole> list = roleService.selectRoleList(new SysRole()); + return AjaxResult.success(list); + } + @PreAuthorize("@ss.hasPermi('system:role:count')") + @ApiOperation(value = "角色数量统计") + @PostMapping("/roleCount") + public AjaxResult roleCount() + { + int all = roleService.selectCount(null); + int normal = roleService.selectCount(0); + int stop = roleService.selectCount(1); + + Map<String,Integer> map = new HashMap<>(); + map.put("all",all); + map.put("normal",normal); + map.put("stop",stop); + return AjaxResult.success(map); + } + +// @Log(title = "角色管理", businessType = BusinessType.EXPORT) +// // @PreAuthorize("@ss.hasPermi('system:role:export')") +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysRole role) +// { +// List<SysRole> list = roleService.selectRoleList(role); +// ExcelUtil<SysRole> util = new ExcelUtil<SysRole>(SysRole.class); +// util.exportExcel(response, list, "角色数据"); +// } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return AjaxResult.success(roleService.selectRoleById(roleId)); + } + +// @PreAuthorize("@ss.hasPermi('system:role:detail')") + @ApiOperation("角色详情") + @GetMapping("/roleInfo") + public AjaxResult roleInfo(@RequestParam Long roleId) + { + SysRole role = roleService.selectRoleById(roleId); + RoleInfoVO roleInfoVo = new RoleInfoVO(); + roleInfoVo.setRoleId(role.getRoleId()); + roleInfoVo.setRoleName(role.getRoleName()); + + // 获取当前角色的菜单列表 + List<SysMenu> menus = menuService.selectListByRoleId(roleId); + if(menus.size()==0){ + return AjaxResult.success(new ArrayList<>()); + } + List<Long> menusId = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + + // 获取当前的权限菜单(有层级) + List<SysMenu> levelMenus = roleService.getMenuLevelList(menusId); + + roleInfoVo.setMenus(menusId); + return AjaxResult.success(roleInfoVo); + } + + + @ApiOperation("用户获取权限菜单") + @GetMapping("/roleInfoFromUserId") + public AjaxResult roleInfoFromUserId(@RequestParam Long userId) + { + return AjaxResult.success(roleService.roleInfoFromUserId(userId)); + } + + + /** + * 新增角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:add')") + @ApiOperation(value = "新增角色") + @Log(title = "角色信息-新增角色", businessType = BusinessType.INSERT) + @PostMapping("/add") + public AjaxResult add(@Validated @RequestBody SysRoleDTO dto) + { + Boolean flag= roleService.isExit(dto.getRoleId(),dto.getRoleName()); + if(flag){ + return error("新增角色'" + dto.getRoleName() + "'失败,角色名称已存在"); + } + roleService.saveRole(dto); + return AjaxResult.success(); + + } + + /** + * 修改保存角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @ApiOperation(value = "编辑角色") + @Log(title = "角色信息-编辑角色", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRoleDTO dto) + { + Boolean flag= roleService.isExit(dto.getRoleId(),dto.getRoleName()); + if (flag){ + return error("修改角色'" + dto.getRoleName() + "'失败,角色名称已存在"); + } + if (roleService.editRole(dto) > 0) + { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) + { + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + tokenService.setLoginUser(loginUser); + } + return AjaxResult.success(); + } + return error("修改角色'" + dto.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + // @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return AjaxResult.success(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + // @PreAuthorize("@ss.hasPermi('system:role:edit')") + @ApiOperation(value = "状态修改") + @Log(title = "角色信息-角色状态修改", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) + { + role.setUpdateBy(getUsername()); + roleService.updateStatus(role); + return AjaxResult.success(); + } + + /** + * 删除角色 + */ + // @PreAuthorize("@ss.hasPermi('system:role:remove')") + @ApiOperation(value = "删除角色") + @Log(title = "角色信息-角色删除角色", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteById/{ids}") + public AjaxResult remove(@PathVariable String ids) + { + String[] split = ids.split(","); + List<Long> id = new ArrayList<>(); + for (String s : split) { + id.add(Long.valueOf(s)); + } + return AjaxResult.success(roleService.deleteRoleByIds(id)); + } + + /** + * 获取角色选择框列表 + */ + // @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return AjaxResult.success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + // @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { +// startPage(); + List<SysUser> list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + // @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { +// startPage(); + List<SysUser> list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + // @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) + { + return AjaxResult.success(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + // @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) + { + return AjaxResult.success(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + // @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) + { + roleService.checkRoleDataScope(roleId); + return AjaxResult.success(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + // @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java new file mode 100644 index 0000000..e4893e0 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -0,0 +1,296 @@ +package com.ruoyi.web.controller.system; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +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.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.dto.SysUserUpdateStatusDTO; +import com.ruoyi.system.query.SysUserQuery; +import com.ruoyi.system.service.*; +import com.ruoyi.system.vo.SysUserVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 用户信息 + * + * @author ruoyi + */ +@Api(tags = "用户信息") +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + @Autowired + private TokenService tokenService; + + /** + * 获取用户列表 + */ + @ApiOperation(value = "获取用户列表") + @PostMapping("/list") + @PreAuthorize("@ss.hasPermi('system:user')") + public AjaxResult list(@RequestBody SysUserQuery query) + { + PageInfo<SysUserVO> list = userService.pageList(query); + return AjaxResult.success(list); + } + + @ApiOperation(value = "获取用户列表-不分页") + @PostMapping("/listNotPage") + @PreAuthorize("@ss.hasPermi('system:user')") + + public AjaxResult listNotPage() + { + List<SysUser> list = userService.selectList(); + return AjaxResult.success(list); + } + + /** + * 获取用户黑名单列表 + */ +// @ApiOperation(value = "获取用户黑名单列表") +// @PostMapping("/blacklist") +// public AjaxResult blacklist(@RequestBody SysUserQuery query) +// { +// startPage(query.getPageNum(), query.getPageSize()); +// List<SysUserVO> list = userService.selectBlackPageList(query); +// return AjaxResult.success(getDataTable(list)); +// } + + /** + * 获取用户详情 + */ + @ApiOperation(value = "获取用户详情") + @GetMapping("/getDetail") + public AjaxResult getDetail(@RequestParam Long userId) + { + SysUser sysUser = userService.selectUserById(userId); + SysUserVO sysUserVO = new SysUserVO(); + BeanUtils.copyProperties(sysUser,sysUserVO); + + return AjaxResult.success(sysUser); + } + + /** + * 获取用户详情 + */ + @ApiOperation(value = "获取用户详情") + @GetMapping("/queryDetail") + public AjaxResult queryDetail() + { + Long userId = tokenService.getLoginUser().getUserId(); + SysUser sysUser = userService.selectUserById(userId); + SysUserVO sysUserVO = new SysUserVO(); + BeanUtils.copyProperties(sysUser,sysUserVO); + + return AjaxResult.success(sysUser); + } + + + /** + * 获取用户数量统计 + */ + @ApiOperation(value = "获取用户数量统计") + @PostMapping("/getUserCount") + public AjaxResult getUserCount() + { + Map<String,Integer> map = new HashMap<>(); + + Integer userCountSum = userService.selectCount(null); + Integer normalCount = userService.selectCount(0);// 正常 + Integer stopCount = userService.selectCount(1);// 停用 + + map.put("all",userCountSum); + map.put("normal",normalCount); + map.put("stop",stopCount); + + return AjaxResult.success(map); + } + + /** + * 移除黑名单 + */ + @GetMapping("/removeBlackList") + public AjaxResult removeBlackList(@RequestParam String ids) + { + String[] split = ids.split(","); + List<Long> id = new ArrayList<>(); + for (String s : split) { + id.add(Long.valueOf(s)); + } + userService.updateUserIfBlack(id); + return AjaxResult.success(); + } + + /** + * 新增用户 + */ + // @PreAuthorize("@ss.hasPermi('system:user:add')") + @ApiOperation(value = "新增用户管理") + @Log(title = "用户信息-新增用户", businessType = BusinessType.INSERT) + @PostMapping("/add") + public AjaxResult add(@Validated @RequestBody SysUser user) + { + user.setUserName(user.getUserName()); + if (!userService.checkUserNameUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword("123456")); + userService.insertUser(user); + return AjaxResult.success(); + } + + /** + * 修改用户 + */ + // @PreAuthorize("@ss.hasPermi('system:user:edit')") + @ApiOperation(value = "修改用户管理") + @Log(title = "用户信息-修改用户", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + user.setUserName(user.getPhonenumber()); +// userService.checkUserAllowed(user); +// userService.checkUserDataScope(user.getUserId()); + if (!userService.checkUserNameUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + + user.setUpdateBy(getUsername()); + if(StringUtils.isNotEmpty(user.getPassword())){ + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + } + return AjaxResult.success(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + // @PreAuthorize("@ss.hasPermi('system:user:remove')") + @ApiOperation(value = "批量删除用户") + @Log(title = "用户信息-批量删除用户", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteById/{ids}") + public AjaxResult remove(@PathVariable String ids) + { + String[] split = ids.split(","); + List<Long> userIds = new ArrayList<>(); + for (String s : split) { + userIds.add(Long.valueOf(s)); + } + if (userIds.contains(getUserId())) + { + return error("当前用户不能删除"); + } + return AjaxResult.success(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + // @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @ApiOperation(value = "重置密码") + @Log(title = "用户信息-重置密码", businessType = BusinessType.UPDATE) + @PostMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); +// userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return AjaxResult.success(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @ApiOperation(value = "状态修改") + @Log(title = "用户信息-状态修改", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUserUpdateStatusDTO dto) + { + SysUser loginUser = tokenService.getLoginUser().getUser(); + SysUser user = new SysUser(); + user.setUserId(dto.getUserId()); + user.setStatus(String.valueOf(dto.getStatus())); +// user.setRemark(dto.getRemark()); + user.setUpdateBy(getUsername()); + user.setDisableRemark(dto.getRemark()); + user.setOperatingTime(LocalDateTime.now()); + user.setOperatingPerson(loginUser.getNickName()+"("+loginUser.getUserName()+")"); + return AjaxResult.success(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + // @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List<SysRole> roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + // @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + return AjaxResult.success(); + } + + /** + * 获取部门树列表 + */ + // @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) + { + return AjaxResult.success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/TaskUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/TaskUtil.java new file mode 100644 index 0000000..2fc75ee --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/TaskUtil.java @@ -0,0 +1,83 @@ +//package com.ruoyi.web.controller.task; +// +// +//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +//import com.ruoyi.common.utils.SmsUtil; +//import com.ruoyi.system.mapper.TBillMapper; +//import com.ruoyi.system.model.TBill; +//import com.ruoyi.system.model.TContract; +//import com.ruoyi.system.model.TContractRentType; +//import com.ruoyi.system.service.TBillService; +//import com.ruoyi.system.service.TContractRentTypeService; +//import com.ruoyi.system.service.TContractService; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.scheduling.annotation.Scheduled; +//import org.springframework.stereotype.Component; +// +//import javax.annotation.Resource; +//import java.math.BigDecimal; +//import java.time.LocalDate; +//import java.time.LocalDateTime; +//import java.time.LocalTime; +//import java.time.ZoneId; +//import java.time.temporal.ChronoUnit; +//import java.time.temporal.TemporalAdjusters; +//import java.util.ArrayList; +//import java.util.Date; +//import java.util.List; +//import java.util.Random; +//import java.util.stream.Collectors; +// +///** +// * @author zhibing.pu +// * @date 2023/7/11 8:39 +// */ +//@Component +//public class TaskUtil { +// @Autowired +// private TContractService contractService; +// @Autowired +// private TBillMapper billMapper; +// // 用于更新违约金账单 +// // 每分钟执行一次的定时任务 +// +// @Scheduled(cron = "0 * * * * ?") +// public void dayOfProportionBill() { +// try { +// // 查询所有未缴费账单 +// List<TBill> list = billMapper.selectList(new LambdaQueryWrapper<TBill>().eq(TBill::getPayFeesStatus, 1) +// .le(TBill::getPayableFeesTime,LocalDate.now())); +// for (TBill tBill : list) { +// tBill.setPayFeesStatus("4"); +// TContract contract = contractService.getById(tBill.getContractId()); +// LocalDate payableFeesTime = tBill.getPayableFeesTime(); +// // 将LocalDate转化为LocalDateTime +// LocalDateTime payableFeesTime1 = LocalDateTime.of(payableFeesTime, LocalTime.of(0, 0, 0)); +// LocalDateTime now = LocalDateTime.now(); +// // 计算两个时间相差多少个小时 +// long hours = ChronoUnit.HOURS.between(payableFeesTime1, now); +// long l = hours / 24; +// if (l>=3){ +// // 违约金比例 +// BigDecimal proportion = contract.getProportion(); +// // 按每天 待缴费金额 * XX% 增加违约金费用 +// if (tBill.getOutstandingMoney().compareTo(new BigDecimal("0"))==0){ +// tBill.setPayFeesStatus("3"); +// billMapper.updateById(tBill); +// continue; +// } +// BigDecimal money = tBill.getOutstandingMoney().multiply(new BigDecimal(100).add(proportion)).divide(new BigDecimal(100),2, BigDecimal.ROUND_DOWN); +// tBill.setOverDays((int) l); +// tBill.setPayableFeesPenalty((tBill.getPayableFeesPenalty()!=null?tBill.getPayableFeesPenalty():BigDecimal.ZERO).add(money)); +// tBill.setOutstandingMoney(money); +// billMapper.updateById(tBill); +// +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// +//} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java new file mode 100644 index 0000000..2da9a44 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java @@ -0,0 +1,79 @@ +package com.ruoyi.web.controller.tool; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * @author zhy + * @title: HttpClientUtil + * @projectName car_park + * @description: http连接工具类 + * @date 2019/10/2219:23 + */ +public class HttpClientUtil { + + + /** + * @param strUrl + * @return byte[] + * @throws + * @description: 获取网络图片转成字节流 + * @author zhy + * @date 2019/10/23 8:59 + */ + public static byte[] getImageFromNetByUrl(String strUrl) { + if (!isURL(strUrl)){ + return null; + } + try { + URL url = new URL(strUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(2 * 1000); + InputStream inStream = conn.getInputStream();// 通过输入流获取图片数据 + byte[] btImg = readInputStream(inStream);// 得到图片的二进制数据 + return btImg; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 从输入流中获取字节流数据 + * + * @param inStream 输入流 + * @return + * @throws Exception + */ + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[10240]; + int len = 0; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toByteArray(); + } + + public static boolean isURL(String str) { + str = str.toLowerCase(); + String regex = "^((https|http|ftp|rtsp|mms)?://)" + + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" + + "(([0-9]{1,3}\\.){3}[0-9]{1,3}" + + "|" + + "([0-9a-z_!~*'()-]+\\.)*" + + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\." + + "[a-z]{2,6})" + + "(:[0-9]{1,5})?" + + "((/?)|" + + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"; + return str.matches(regex); + } + + +} + \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java new file mode 100644 index 0000000..e488f03 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.tool; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.R; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.List; + +/** + * 导出返回信息 + */ +@Slf4j +public class ImportExcelUtil { + + /** + * @param errorLines 错误行数 + * @param successLines 成功行数 + * @param errorMessage 错误信息 + * @return + * @throws IOException + */ + public static R<String> importReturnMsg(int errorLines, int successLines, List<String> errorMessage) throws IOException { + if (errorLines == 0) { + return R.ok("共" + successLines + "行数据全部导入成功!"); + } else { + JSONObject result = new JSONObject(5); + int totalCount = successLines + errorLines; + result.put("totalCount", totalCount); + result.put("errorCount", errorLines); + result.put("errorMessage", errorMessage); + result.put("successCount", successLines); + result.put("msg", "总上传行数:" + totalCount + ",已导入行数:" + successLines + ",错误行数:" + errorLines); + return R.ok(JSON.toJSONString(result)); + } + } + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java new file mode 100644 index 0000000..073f315 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.controller.tool; + +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.system.code.SubmitTemplateReg; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; + +/** + * 短信工具类 + */ +public class MsgCodeUtil implements Serializable { + + /**接口账号用户名*/ + private static final String AP_ID = ""; + /**企业名称*/ + private static final String EC_NAME = ""; + /**签名*/ + private static final String SECRET_KEY = ""; + /**签名编码*/ + private static final String SIGN = ""; + /**模板ID*/ + private static final String TEMPLATE_ID = ""; + + + /** + * 实体封装 + * @param code + * @return + */ + public static SubmitTemplateReg getSubmitTemplateReg(String code,String mobiles) { + SubmitTemplateReg submitReg =new SubmitTemplateReg(); + String[] paramss = {code}; + submitReg.setApId(AP_ID); + submitReg.setEcName(EC_NAME); + submitReg.setSecretKey(SECRET_KEY); + submitReg.setParams(JSONObject.toJSONString(paramss)); + submitReg.setMobiles(mobiles); + submitReg.setAddSerial(""); + submitReg.setSign(SIGN); + submitReg.setTemplateId(TEMPLATE_ID); + submitReg.setMac(TEMPLATE_ID); + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(submitReg.getEcName( ));stringBuffer.append(submitReg.getApId()); + stringBuffer.append(submitReg.getSecretKey());stringBuffer.append(submitReg.getTemplateId());stringBuffer.append(submitReg.getMobiles()); + stringBuffer.append(submitReg.getParams());stringBuffer.append(submitReg.getSign());stringBuffer.append(submitReg.getAddSerial()); + submitReg.setMac(Hex.encodeHexString(stringBuffer.toString().getBytes(StandardCharsets.UTF_8))); + String regText = JSONObject.toJSONString(submitReg); + //加密 + String encode = Base64.encodeBase64String(regText.getBytes()); + System.err.println(encode); + return submitReg; + } + + public static void main(String[] args) { + getSubmitTemplateReg("123456","18398968484"); + } + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java new file mode 100644 index 0000000..119a5ea --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java @@ -0,0 +1,70 @@ +package com.ruoyi.web.controller.tool; + +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class MsgUtils { + + @Value("${code.config.accessKeyId}") + private String accessKeyId; + @Value("${code.config.accessKeySecret}") + private String accessKeySecret; + @Value("${code.config.signName}") + private String signName; + @Value("${code.config.templateCode}") + private String templateCode; + @Value("${code.config.signNameTest}") + private String signNameTest; + @Value("${code.config.templateCodeTest}") + private String templateCodeTest; + + /** + * 使用AK&SK初始化账号Client + * @param accessKeyId + * @param accessKeySecret + * @return Client + * @throws Exception + */ + public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception { + Config config = new Config() + // 您的 AccessKey ID + .setAccessKeyId(accessKeyId) + // 您的 AccessKey Secret + .setAccessKeySecret(accessKeySecret); + // 访问的域名 + config.endpoint = "dysmsapi.aliyuncs.com"; + return new com.aliyun.dysmsapi20170525.Client(config); + } + + public void sendMsg(String phone,String code) throws Exception { + com.aliyun.dysmsapi20170525.Client client = MsgUtils.createClient(accessKeyId,accessKeySecret); + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setSignName(signName) + .setTemplateCode(templateCode) + .setPhoneNumbers(phone) + .setTemplateParam("{\"code\":\""+code+"\"}"); + RuntimeOptions runtime = new RuntimeOptions(); + try { + // 复制代码运行请自行打印 API 的返回值 + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); + log.info("短信发送成功:{},{}",sendSmsResponse.getBody().getMessage(),sendSmsResponse.getStatusCode()); + } catch (TeaException error) { + // 如有需要,请打印 error + com.aliyun.teautil.Common.assertAsString(error.message); + log.info("短信发送失败:{}",error.message); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + // 如有需要,请打印 error + com.aliyun.teautil.Common.assertAsString(error.message); + log.info("短信发送失败:{}",error.message); + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java new file mode 100644 index 0000000..e5836a2 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.core.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.context.annotation.Configuration; + +/** + * @author xiaochen + * @ClassName DataUpdateInterceptor + * @Description 数据更新操作处理 + * @date 2021-12-15 + * <p> + * 注意,之前在此处注入了 JwtTokenUtils + * <p> + * 造成spring循环依赖,项目支棱不起来 + */ +@Slf4j +@Configuration +public class DataUpdateHandlerConfig implements MetaObjectHandler { + + /** + * 新增数据执行 + * + * @param metaObject + */ + @Override + public void insertFill(MetaObject metaObject) { + // 获取登录信息 + String userName = SecurityUtils.getUsername(); + if (StringUtils.isNotBlank(userName)) { + this.setFieldValByName("createBy", userName, metaObject); + this.setFieldValByName("updateBy", userName, metaObject); + } else { + this.setFieldValByName("createBy", userName, metaObject); + this.setFieldValByName("updateBy", userName, metaObject); + } + + } + + /** + * 修改数据执行 + * + * @param metaObject + */ + @Override + public void updateFill(MetaObject metaObject) { + // 获取登录信息 + String userName = SecurityUtils.getUsername(); + if (StringUtils.isNotBlank(userName)){ + this.setFieldValByName("createBy", userName, metaObject); + this.setFieldValByName("updateBy", userName, metaObject); + } else { + this.setFieldValByName("createBy", userName, metaObject); + this.setFieldValByName("updateBy", userName, metaObject); + } + + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/FileUploaderConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/FileUploaderConfig.java new file mode 100644 index 0000000..d87ab0d --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/FileUploaderConfig.java @@ -0,0 +1,67 @@ +package com.ruoyi.web.core.config; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.http.HttpProtocol; +import com.qcloud.cos.region.Region; +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class FileUploaderConfig { + + /** + * COS的SecretId + */ + @Value("${cos.client.accessKey}") + private String secretId; + /** + * COS的SecretKey + */ + @Value("${cos.client.secretKey}") + private String secretKey; + /** + * 文件上传后访问路径的根路径,后面要最佳文件名字与类型 + */ + @Value("${cos.client.rootSrc}") + private String rootSrc; + /** + * 上传的存储桶的地域 + */ + @Value("${cos.client.bucketAddr}") + private String bucketAddr; + /** + * 存储桶的名字,是自己在存储空间自己创建的,我创建的名字是:qq-test-1303****** + */ + @Value("${cos.client.bucket}") + private String bucketName; + /** + * 文件存放位置 + */ + @Value("${cos.client.location}") + private String location; + + @Value("${file.url.prefix}") + private String fileUrlPrefix; + + + @Bean + public COSClient cosClient() { + // 1 初始化用户身份信息(secretId, secretKey)。 + COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); + // 2.1 设置存储桶的地域(上文获得) + Region region = new Region(bucketAddr); + ClientConfig clientConfig = new ClientConfig(region); + // 2.2 使用https协议传输 + clientConfig.setHttpProtocol(HttpProtocol.https); + // 生成 cos 客户端 + return new COSClient(cred, clientConfig); + } + + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java new file mode 100644 index 0000000..fac9a9f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java @@ -0,0 +1,51 @@ +package com.ruoyi.web.core.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author liheng + * @ClassName MybatisPlusConfig + * @Description MybatisPlus相关配置 + * @date 2020-09-22 11:22、 + * 直接以实现类作为bean的注入(有事务管理的类) + * @EnableTransactionManagement(proxyTargetClass = true) + */ +@Configuration +public class MybatisPlusConfig { + private final DataUpdateHandlerConfig dataUpdateHandler; + + @Autowired + public MybatisPlusConfig(DataUpdateHandlerConfig dataUpdateHandler) { + this.dataUpdateHandler = dataUpdateHandler; + } + + /** + * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + /** + * 自动填充功能 + * + * @return + */ + @Bean + public GlobalConfig globalConfig() { + GlobalConfig globalConfig = new GlobalConfig(); + globalConfig.setMetaObjectHandler(dataUpdateHandler); + return globalConfig; + } + + +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..b652bc3 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -0,0 +1,125 @@ +package com.ruoyi.web.core.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.ruoyi.common.config.RuoYiConfig; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author ruoyi + */ +@Configuration +public class SwaggerConfig +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** 是否开启swagger */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** 设置请求的统一前缀 */ + @Value("${swagger.pathMapping}") + private String pathMapping; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() + { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List<SecurityScheme> securitySchemes() + { + List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List<SecurityContext> securityContexts() + { + List<SecurityContext> securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List<SecurityReference> defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List<SecurityReference> securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() + { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("标题:西藏国投管理系统_接口文档") + // 描述 + .description("西藏国投接口文档") + // 作者信息 + .contact(new Contact(ruoyiConfig.getName(), null, null)) + // 版本 + .version("版本号:" + ruoyiConfig.getVersion()) + .build(); + } +} diff --git a/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..2b23f85 --- /dev/null +++ b/ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson.*.jar \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml new file mode 100644 index 0000000..b00292d --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -0,0 +1,237 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 3.8.6 + # 版权年份 + copyrightYear: 2023 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + 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: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# Spring配置 +spring: + main: + allow-bean-definition-overriding: true + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 500MB + # 设置总上传的文件大小 + max-request-size: 2000MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # redis 配置 + redis: + # 地址 + # host: 127.0.0.1 + # # 端口,默认为6379 + # port: 6379 + # # 数据库索引 + # database: 0 + # # 密码 + # password: 123456 + host: 127.0.0.1 + # 端口,默认为6379 + port: 16379 + # 数据库索引 + database: 0 + # 密码 + password: 8f5z9g52gx4bg + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 数据源配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://172.27.0.13:3306/xizang?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai + username: xzgt + password: changyun!6f2gshj6h3j + # 从库数据源 + 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 +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 120 + +mybatis-plus: + # 此处在多数据源中生效 + config-location: classpath:/mybatis-config.xml + global-config: + banner: false + db-config: + logic-not-delete-value: 0 + logic-delete-value: 1 + type-aliases-package: com.ruoyi.**.domain,com.ruoyi.**.vo,com.ruoyi.**.model + # 指定Mapper文件位置 + mapper-locations: classpath*:mapper/**/*.xml + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: / + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* +# file upload +file: + upload: + location: /file/ + qrLocation: /file/qrCode/ + accessPath: /file/ + allowExt: .jpg|.png|.gif|.jpeg|.doc|.docx|.apk|.MP4|.mp4|.pdf|.PDF + url: + prefix: https://xzgt.test.591taxi.cn:${server.port}${server.servlet.context-path} +wx: + conf: + appId: wxe91f1af7638aa5dd + secretId: a787e1a462715604e0c9528b6d8960d1 +#OSS及短信配置 +code: + config: + templateCodeTest: "SMS_154950909" + signNameTest: "阿里云短信测试" + accessKeyId: LTAI5tAdba8HtT1C6UqtSxBt + accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8 + signName: "四川金达通信工程" + templateCode: "SMS_293985284" +cos: + client: + accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + bucket: xzgttest-1305134071 + bucketAddr: ap-chengdu + rootSrc: https://xzgttest-1305134071.cos.ap-chengdu.myqcloud.com/ + location: /xizang +sms: + enable: true + appId: 1400957506 + secretid: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretkey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + sign: 畅云出行 +com: + taxi591: + bank: + cer-path: /usr/local/bank/TrustPay.cer + base-url: http://hello.enjoy.abchina.com + enable: true + keystore-password: gggs6666 + pfx-path: /usr/local/bank/103882597000441.pfx \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-test.yml b/ruoyi-admin/src/main/resources/application-test.yml new file mode 100644 index 0000000..6cc5d39 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-test.yml @@ -0,0 +1,238 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 3.8.6 + # 版权年份 + copyrightYear: 2023 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8081 + 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: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# Spring配置 +spring: + main: + allow-bean-definition-overriding: true + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 500MB + # 设置总上传的文件大小 + max-request-size: 2000MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # redis 配置 + redis: + # 地址 +# host: 127.0.0.1 +# # 端口,默认为6379 +# port: 6379 +# # 数据库索引 +# database: 0 +# # 密码 +# password: 123456 + host: xzgt.test.591taxi.cn + # 端口,默认为6379 + port: 16379 + # 数据库索引 + database: 0 + # 密码 + password: 8f5z9g52gx4bg + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms +# 数据源配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://127.0.0.1:3306/laboratory?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: 123456 + # 从库数据源 + 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 +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 120 + +mybatis-plus: + # 此处在多数据源中生效 + config-location: classpath:/mybatis-config.xml + global-config: + banner: false + db-config: + logic-not-delete-value: 0 + logic-delete-value: 1 + type-aliases-package: com.ruoyi.**.domain,com.ruoyi.**.vo,com.ruoyi.**.model + # 指定Mapper文件位置 + mapper-locations: classpath*:mapper/**/*.xml + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: / + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* +# file upload +file: + upload: + location: /file/ + qrLocation: /file/qrCode/ + accessPath: /file/ + allowExt: .jpg|.png|.gif|.jpeg|.doc|.docx|.apk|.MP4|.mp4|.pdf|.PDF + url: +# prefix: http://localhost:${server.port}${server.servlet.context-path} + prefix: https://xzgt.test.591taxi.cn:${server.port}${server.servlet.context-path} +wx: + conf: + appId: wxe91f1af7638aa5dd + secretId: a787e1a462715604e0c9528b6d8960d1 +#OSS及短信配置 +code: + config: + templateCodeTest: "SMS_154950909" + signNameTest: "阿里云短信测试" + accessKeyId: LTAI5tAdba8HtT1C6UqtSxBt + accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8 + signName: "四川金达通信工程" + templateCode: "SMS_293985284" +cos: + client: + accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + bucket: xzgttest-1305134071 + bucketAddr: ap-chengdu + rootSrc: https://xzgttest-1305134071.cos.ap-chengdu.myqcloud.com/ + location: /xizang +sms: + enable: true + appId: 1400957506 + secretid: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretkey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + sign: 畅云出行 +com: + taxi591: + bank: + cer-path: D:\workspaces\工作文件\畅云\农业银行\正式\TrustPay.cer + base-url: http://hello.enjoy.abchina.com + enable: true + keystore-password: gggs6666 + pfx-path: D:\workspaces\工作文件\畅云\农业银行\正式\103882597000441.pfx \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml new file mode 100644 index 0000000..dcc106d --- /dev/null +++ b/ruoyi-admin/src/main/resources/application.yml @@ -0,0 +1,4 @@ +# 项目相关配置 +spring: + profiles: + active: test diff --git a/ruoyi-admin/src/main/resources/banner.txt b/ruoyi-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..0931cb8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/banner.txt @@ -0,0 +1,24 @@ +Application Version: ${ruoyi.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..93de005 --- /dev/null +++ b/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,38 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..a360583 --- /dev/null +++ b/ruoyi-admin/src/main/resources/logback.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <!-- 日志存放路径 --> + <property name="log.path" value="/home/ruoyi/logs" /> + <!-- 日志输出格式 --> + <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /> + + <!-- 控制台输出 --> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + </appender> + + <!-- 系统日志输出 --> + <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${log.path}/sys-info.log</file> + <!-- 循环政策:基于时间创建日志文件 --> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- 日志文件名格式 --> + <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern> + <!-- 日志最大的历史 60天 --> + <maxHistory>60</maxHistory> + </rollingPolicy> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <!-- 过滤的级别 --> + <level>INFO</level> + <!-- 匹配时的操作:接收(记录) --> + <onMatch>ACCEPT</onMatch> + <!-- 不匹配时的操作:拒绝(不记录) --> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${log.path}/sys-error.log</file> + <!-- 循环政策:基于时间创建日志文件 --> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- 日志文件名格式 --> + <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern> + <!-- 日志最大的历史 60天 --> + <maxHistory>60</maxHistory> + </rollingPolicy> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <!-- 过滤的级别 --> + <level>ERROR</level> + <!-- 匹配时的操作:接收(记录) --> + <onMatch>ACCEPT</onMatch> + <!-- 不匹配时的操作:拒绝(不记录) --> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <!-- 用户访问日志输出 --> + <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${log.path}/sys-user.log</file> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- 按天回滚 daily --> + <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern> + <!-- 日志最大的历史 60天 --> + <maxHistory>60</maxHistory> + </rollingPolicy> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + </appender> + + <!-- 系统模块日志级别控制 --> + <logger name="com.ruoyi" level="info" /> + <!-- Spring日志级别控制 --> + <logger name="org.springframework" level="warn" /> + + <root level="info"> + <appender-ref ref="console" /> + </root> + + <!--系统操作日志--> + <root level="info"> + <appender-ref ref="file_info" /> + <appender-ref ref="file_error" /> + </root> + + <!--系统用户操作日志--> + <logger name="sys-user" level="info"> + <appender-ref ref="sys-user"/> + </logger> +</configuration> \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/mybatis-config.xml b/ruoyi-admin/src/main/resources/mybatis-config.xml new file mode 100644 index 0000000..53c5587 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mybatis-config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> +<configuration> + + <settings> + <!-- 打印查询语句 不会写入到日志文件中--> + <setting name="logImpl" value="STDOUT_LOGGING"/> + <!--<setting name="logImpl" value="LOG4J" />--> + <!-- 控制全局缓存(二级缓存),按美团技术团队的说法,尽量别用缓存机制 emmmm.... --> + <setting name="cacheEnabled" value="true"/> + <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false --> + <!-- <setting name="lazyLoadingEnabled" value="true"/> --> + <setting name="mapUnderscoreToCamelCase" value="true"/><!--是否将map下划线方式转为驼峰式命名--> + <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖--> + <!-- <setting name="aggressiveLazyLoading" value="false"/>--> + <!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST --> + <!--<setting name="proxyFactory" value="CGLIB" />--> + <!-- 关于mybatis的一二级缓存 请参照:https://tech.meituan.com/2018/01/19/mybatis-cache.html --> + <!-- 一级缓存范围默认:SESSION ,此范围在复杂应用场景中可能会出现脏读数据--> + <!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 --> + <!--<setting name="localCacheScope" value="STATEMENT"/>--> + <setting name="localCacheScope" value="STATEMENT"/> + </settings> + +</configuration> diff --git a/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration +PUBLIC "-//mybatis.org//DTD Config 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-config.dtd"> +<configuration> + <!-- 全局参数 --> + <settings> + <!-- 使全局的映射器启用或禁用缓存 --> + <setting name="cacheEnabled" value="true" /> + <!-- 允许JDBC 支持自动生成主键 --> + <setting name="useGeneratedKeys" value="true" /> + <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 --> + <setting name="defaultExecutorType" value="SIMPLE" /> + <!-- 指定 MyBatis 所用日志的具体实现 --> + <setting name="logImpl" value="SLF4J" /> + <!-- 使用驼峰命名法转换字段 --> + <!-- <setting name="mapUnderscoreToCamelCase" value="true"/> --> + </settings> + +</configuration> diff --git "a/ruoyi-admin/src/main/resources/template/1_yzj_\347\247\237\350\265\201\345\220\210\345\220\214.xml" "b/ruoyi-admin/src/main/resources/template/1_yzj_\347\247\237\350\265\201\345\220\210\345\220\214.xml" new file mode 100644 index 0000000..ae1bef7 --- /dev/null +++ "b/ruoyi-admin/src/main/resources/template/1_yzj_\347\247\237\350\265\201\345\220\210\345\220\214.xml" @@ -0,0 +1,11322 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<?mso-application progid="Word.Document"?> +<pkg:package xmlns:pkg="http://schemas.microsoft.com/office/2006/xmlPackage"> + <pkg:part pkg:name="/_rels/.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml"> + <pkg:xmlData> + <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> + <Relationship Id="rId4" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" + Target="word/document.xml"/> + <Relationship Id="rId2" + Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" + Target="docProps/core.xml"/> + <Relationship Id="rId1" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" + Target="docProps/app.xml"/> + <Relationship Id="rId3" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" + Target="docProps/custom.xml"/> + </Relationships> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/_rels/document.xml.rels" + pkg:contentType="application/vnd.openxmlformats-package.relationships+xml"> + <pkg:xmlData> + <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> + <Relationship Id="rId9" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" + Target="footer3.xml"/> + <Relationship Id="rId8" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" + Target="footer2.xml"/> + <Relationship Id="rId7" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" + Target="footer1.xml"/> + <Relationship Id="rId6" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" + Target="header3.xml"/> + <Relationship Id="rId5" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" + Target="header2.xml"/> + <Relationship Id="rId4" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" + Target="header1.xml"/> + <Relationship Id="rId3" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" + Target="footnotes.xml"/> + <Relationship Id="rId2" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" + Target="settings.xml"/> + <Relationship Id="rId12" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" + Target="fontTable.xml"/> + <Relationship Id="rId11" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" + Target="numbering.xml"/> + <Relationship Id="rId10" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" + Target="theme/theme1.xml"/> + <Relationship Id="rId1" + Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" + Target="styles.xml"/> + </Relationships> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/document.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"> + <pkg:xmlData> + <w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:body> + <w:p w14:paraId="274FC418"> + <w:pPr> + <w:pStyle w:val="3"/> + <w:tabs> + <w:tab w:val="left" w:pos="212"/> + </w:tabs> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="360" w:lineRule="auto"/> + <w:ind w:firstLine="3373" w:firstLineChars="1200"/> + <w:jc w:val="both"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:color w:val="000000"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:bookmarkStart w:id="0" w:name="_GoBack"/> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:color w:val="000000"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>租赁合同</w:t> + </w:r> + <w:bookmarkEnd w:id="0"/> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:color w:val="000000"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="01125D4B"> + <w:pPr> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="360" w:lineRule="auto"/> + <w:ind w:firstLine="560" w:firstLineChars="200"/> + <w:jc w:val="center"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>合同编号:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>LSDQ202502060016</w:t> + </w:r> + </w:p> + <w:p w14:paraId="10E0F21B"> + <w:pPr> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="360" w:lineRule="auto"/> + <w:ind w:firstLine="560" w:firstLineChars="200"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="73B2E368"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方(出租方):${partyOneName}</w:t> + </w:r> + </w:p> + <w:p w14:paraId="11E1A514"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:keepNext/> + <w:keepLines/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA"/> + </w:rPr> + <w:t xml:space="preserve"> 社会统一信用代码:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="61C74B9F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>经营地:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="48CD6B99"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>法定代表人:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:tab/> + </w:r> + </w:p> + <w:p w14:paraId="2D36559E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="241799E1"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方(承租方):${partyTwoName}</w:t> + </w:r> + </w:p> + <w:p w14:paraId="522A5B89"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>社会统一信用代码:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="2763E6DE"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>经营地:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="52C0BE7D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>法定代表人(负责人):</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5C6E8665"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>身份证号码:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="3536AE51"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>鉴于:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="11EB8AC8"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方为西藏国有资产管理有限公司全资子公司,自</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2022年7月1日起由甲方代</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>西藏国有资产管理有限公司</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>履行业主相关责任,包括但不限于资产租赁、管理等</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="387BF820"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲、乙双方根据《中华人民共和国</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>民法典</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 》及有关法律、法规之规定,就房屋租赁一事,本着“互惠互利”的原则,经平等协商,现就双方一致意愿,达成以下条款。以资共同遵守: + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="5560832D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="1"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>房屋租赁概况</w:t> + </w:r> + </w:p> + <w:p w14:paraId="6DFB42B7"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方出租给乙方的房屋位于</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${houseAddress},</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>共</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(间),房屋共计建筑面积</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${houseArea}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>平方米。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7A078878"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>概况补充:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + + </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="3C1FBAE9"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> 二、租赁用途</w:t> + </w:r> + </w:p> + <w:p w14:paraId="358974A7"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="2"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方租赁房屋仅为</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t></w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>使用,使用时应遵守国家、行业和本市有关房屋规定,合法合规使用。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="79EF5814"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="2"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">在租赁期内未征得甲方书面同意,或按规定须经有关部门审核批准前,乙方不得改变房屋使用用途。 </w:t> + </w:r> + </w:p> + <w:p w14:paraId="22E420EE"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="2"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 乙方在签订本合同之前已经对所租赁房屋进行了全面细致的考察,认为甲方所出租房屋符合乙方使用目的。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="4A1FA2A5"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="2"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方承诺:在租赁期内,未事先征得甲方书面同意,不擅自改变承租房屋原有主体结构和用途。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="3347EB9E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>三、租赁期限</w:t> + </w:r> + </w:p> + <w:p w14:paraId="0E1F500E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.租赁期限共计</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t></w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>,自</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${startTime}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>到</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${endTime}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>止。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5032499B"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>.租赁期满,甲方有权收回该房屋,乙方应在收到甲方发出的《房屋收回通知书》等材料后,在</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>10日内腾空物品及</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>返还房屋。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="411FB5F3"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>四、租赁费用、支付方式及期限</w:t> + </w:r> + </w:p> + <w:p w14:paraId="17018CD6"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.租金支付:经甲乙双方约定每月租金为</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${monthRent}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>元(大写:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${monthRentString}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>),合计年租金为</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${totalYear}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>元(大写:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">${totalYearString} </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>)。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7AF9B466"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.自本合同签订之日起</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>10</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>个工作日内乙方应将本</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${payType}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>/</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t></w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>度租金</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${firstRent}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(大写:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${firstRentString}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>)支付至甲方指定账户。下一</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${nextPayTime}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>/</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t></w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>的租金需在每</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${nextPayTime}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>/</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t></w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>首月的15日前支付。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="79B24F30"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>3.支付押金:为保障合同的顺利履行,乙方应在本合同签订之日</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>15日内</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>一次性向甲方缴纳</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${deposit}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(大写</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>人民币</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${depositString}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>)作为押金。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7A0E093C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 甲方收取押金时,须向乙方出具收据,押金不计算利息。合同履行期满或合同提前终止时,经甲方验收乙方无任何违约并按时、完好返还承租房屋后,凭押金收据向甲方取回押金,甲方应在一个月内将押金无息返还给乙方。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="0F069C61"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>4.租金及押金支付方式:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="038D2BED"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 为方便统一监管,乙方需到中国银行股份有限公司西藏区分行营业部(拉萨市林廓北路7号)办理开卡业务。租金与押金通过下述银行卡支付至甲方指定账户。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="47D760AC"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方指定收款账号: </w:t> + </w:r> + </w:p> + <w:p w14:paraId="2CDBAF65"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">开户行: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="5B72F89E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>户名: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="13E716B4"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方户名:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="1FFF87EA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>账号:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="31039007"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>开户行:</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="0CCD94B2"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>如有特殊情况,可使用移动终端支付方式,但必须预支付半年以上租金。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="0E61404B"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="3"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>租赁期间相关费用</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1328018F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:jc w:val="both"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.乙方租赁期间发生的水电气费、网络等费用由乙方自行缴纳至相关单位。关于物业管理,如乙方选择甲方指定的物业公司服务,则需要以书面方式与甲方确定协商签订协议,如无需甲方指定,则由乙方自行与物业公司协商确定物业服务事项,与甲方无关。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="579D709F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.维修费:租赁期间,乙方应合理使用承租的房屋及其附属设施。如因使用不当造成房屋及设施损坏的,乙方应立即负责修复。如果经甲方催告后3日内乙方未进行修缮,甲方有权自行安排人员进行修缮,所产生的风险和费用等均由乙方承担,甲方有权要求乙方直接支付,也可直接从押金中扣除上述费用,不足部分甲方有权向乙方进行追偿。扣除的押金乙方应在扣除之日起五日内进行补足,包括但不限于门窗、给排水、电路等维修费。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="45D52681"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 3.乙方使用该房屋过程中产生的其它各项费用均由乙方承担,(其中包括但不限于乙方自己申请安装电话、宽带、有线电视等设备的费用)。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="6C55D887"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 4.租赁期间内,因乙方自身原因导致水电、下水管道产生的相关问题由乙方自行解决,若因乙方原因未及时解决导致甲方承担责任的,甲方将追究乙方相关责任。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="235ACC06"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> 5.租赁期内,因乙方生产生活原因产生的污水处理费用由乙方自行解决。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1F62EC8D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>六、租赁期间房屋使用要求</w:t> + </w:r> + </w:p> + <w:p w14:paraId="17C2253D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.乙方应当在法律准许的范围内经营,不得违反国家法律法规,不得违反社会公序良俗,不得损害甲方声誉。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="667A42B8"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.乙方在经营中自负盈亏,由乙方经营造成的债务及其它法律纠纷与甲方无关。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="073EB74D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>3</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + .租赁期内在不损害租赁房屋的前提下,乙方可使用租赁房屋悬挂广告牌、宣传板、横幅等设施。乙方的设施设备应严格按照国家、行业、相关主管部门统一管理规范,同时需要将门前广告设计图片资料报甲方备案后方可实施。由广告位、广告内容引起的任何纠纷甲方不承担任何责任,若因此导致甲方被第三方追究责任的,甲方有权向乙方进行追偿。在本合同因任何原因解除、终止时,乙方应拆除相应广告。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7D372D7A"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>4</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + .乙方在租赁期满、合同终止、解除时要保证承租房屋的门、窗及水电等设施完好,乙方经甲方同意进行的装修,甲方同意保留的,乙方不得拆除改造设施,也不能向甲方收取任何费用。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="532967D8"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>5</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>.在租赁期内,因乙方施工等原因造成的一切人员安全伤害和财产损失均由乙方负责。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7F087204"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>6</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + .乙方应当遵守法律法规及相关主管部门的规章制度,安全合理用电。若乙方违规用电导致损失的,甲方不承担任何法律责任。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="0C45EF41"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>7.</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方承租期间,由乙方自行负责房屋内外的财产和人员安全,甲方对此不负任何责任及义务。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="491A0D8F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="4"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方不得在房屋内存放易燃易爆、剧毒或违反国家规定的货物。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="4F2BA647"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="4"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 乙方需要使用租赁房屋办理相应经营手续的,租赁期满双方未能续约或不论任何原因导致本合同解除的,乙方必须在 + </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>15</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>日内工商信息应当注销完毕,不得影响甲方的再次使用。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5EF0184F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="4"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 乙方要确保房屋的消防安全、治安安全和卫生清洁,承担门前三包责任,若发生相应的安全事故,由乙方负责解决纠纷并承担责任,同时乙方应当在安全事故发生的 + </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>小时内上报甲方。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="42DC02BC"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="-1"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> 11.乙方应当保证乙方营业执照及店铺名称为统一名称,若出现营业执照与店铺名称不符的,乙方应在30日内妥善解决,经甲方提醒,在合理期限内未妥善解决的,甲方视为乙方擅自转租转让。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5CB540C9"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>七、房屋装修</w:t> + </w:r> + </w:p> + <w:p w14:paraId="3FFB7596"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.甲方将房屋交给乙方后,如乙方不再租赁甲方的房屋,乙方对装修部分不得作价处理给甲方,同时甲方对于乙方在租赁合同期间产生的所有装修费用均不予以赔偿。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="2389DB46"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.乙方装修房屋或增设附属设施,应当以书面形式征得甲方同意后在甲方监督下施工,但不得改变承租房屋的内部结构或设置对房屋结构有影响的设备设施。对乙方的装修装饰部分,甲方不负有修缮的义务。无论何种原因致合同变更、终止或提前解除,甲方均不承担这部分装修的任何补偿责任,甲方有权无偿取得依附于房屋的装饰装修。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="1908C4A3"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 3.乙方在进行装修前,设计规模、动工方案、范围、工艺、用料等方案均须事先征得甲方的书面同意后方可施工,但甲方的书面同意不免除乙方对房屋及设备设施的合理使用责任。装修期间,乙方应当保证租赁房屋有关的一切安全,并严格按照国家、行业和西藏自治区的相关规定,文明施工、安全施工、及时清洁建筑垃圾,不扰民、不影响周边环境。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="63C30A95"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>八、双方的权利与义务</w:t> + </w:r> + </w:p> + <w:p w14:paraId="344935F7"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.甲方权利与义务</w:t> + </w:r> + </w:p> + <w:p w14:paraId="214FFC46"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.1甲方享有向乙方收取约定租金的权利。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="787E620C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.2租赁期满,或因任何原因致合同变更、终止或解除时,甲方有权收回租赁房屋,并无偿取得房屋内装修装饰的所有权,乙方应予配合。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="74759991"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:jc w:val="both"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.3针对出租房屋的转让,乙方承诺放弃优先购买权。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="4BF64B7E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.4对乙方日常及按上级部门要求和部署进行安全、消防等统一检查。对违反合同约定或违反消防、治安规定等行为,甲方有权提出限期整改要求。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="349F99EA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.5要求乙方合法、正当经营,有权要求乙方及时整改影响甲方利益和声誉的行为。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="167520A5"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.6对乙方的违约行为进行权利主张。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="6C3979EE"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.7合同签订后,甲方需要按照合同约定进行交付。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7528A46D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.8</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>法律法规规定的其他义务。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="25C4132D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.乙方权利与义务</w:t> + </w:r> + </w:p> + <w:p w14:paraId="4C1AC41E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.1乙方享有对合同约定房屋的租赁使用权,但应当按合同约定及时足额支付租赁费用。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="743A5C01"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.2乙方享有独立生产、经营的权利,但乙方应当在法律准许的范围内经营,不得违反国家法律法规,不得违反社会公序良俗,不得损害甲方声誉。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="329E8665"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.2.1在租赁期内,乙方在经营中,应遵守国家法律、法规规定,合法经营,不得超出核准登记的经营范围,不准进行非法活动。如发生治安事件和刑事案件,乙方将承担全部法律责任。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="2DE0AB58"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.2.2乙方在经营过程中自负盈亏,经营所产生的水电、网络、电视、燃气、物业、管理费等一切费用由乙方承担。由乙方经营造成的债务等纠纷与甲方无关。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="0CB9A50A"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="0"/> + <w:numId w:val="5"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.3从事食品、药品、生态环境等涉及重大民生项目的经营的乙方,应当遵守各项法律法规。因违法违规承担法律责任的,与甲方无关。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="4B13AA77"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.3 + 乙方可以使用租赁房屋合法办理相应建设、经营手续,但租赁期满双方未能续约或不论因任何原因导致本合同终止、解除的,乙方必须在 + </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>15</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>日内注销完毕工商登记注册信息,不得影响甲方的再次使用。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="32EC959E"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.4在租赁期内,乙方不得擅自改变租赁房屋的约定用途,不得擅自扩大租赁范围。在租赁期内,未经甲方书面同意,乙方不得将租赁房屋转租、转让、转借或者是变相转租、转让转借给他人。乙方不得以任何形式收取次承租人转让费,因此产生的纠纷和责任由乙方自行处理和承担,甲方不承担任何责任。涉及税收的,甲方有权按照相关法律法规从押金和预交房租中扣除。乙方承诺不存在合伙承租、合伙经营的情况,乙方之外的任何第三方使用承租房屋均属于擅自转租。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="5764C6E0"> + <w:pPr> + <w:keepNext w:val="0"/> + <w:keepLines w:val="0"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.4.1乙方不得通过任何途径发布关于租赁房屋的转租、转让、转借或者是变相转租、转让转借给他人的信息。若有上述行为,一经甲方查实,视为乙方存在转租、转让、转借行为。甲方有权扣除押金并视情节轻重,按照本合同第十二条规定追究乙方的违约责任。 + </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="1EAAB93C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="640"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.5乙方要确保</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 在租赁期内,防火安全,门前三包,综合治理及安全、保卫等工作,乙方应执行当地相关部门规定并承担全部责任和服从甲方监督检查。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="29C15CE4"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.6乙方保证在本次租赁合同签订之前不存在任何法律纠纷,本次租赁合同之前存在的任何纠纷均与甲方无关。乙方在签订本协议之前如有支付转让费给第三人的,与甲方无关。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="354AAE51"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.7乙方在承租期内,不得以乙方承租经营权向第三方融资。如确需向第三方融资的,不得以任何方式将甲方资源作为抵押或融资,甲方资源包括但不限于出租权、租赁房屋、地上建筑物、构筑物等与本次租赁相关的所属物、衍生物及相关权益。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="5133C699"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.8在租赁期内,乙方不得将承租房屋转租、分租、转让、抵押、质押。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7A3818B2"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.9乙方有权在交付租赁房屋起3日内审查房屋之手续、结构、设施、装修等情况。交付租赁房屋3日后乙方无异议,视为乙方同意接受该房屋全部事实状况,后期乙方不得再以任何理由对该房屋或甲方提出任何异议。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7C6AF8CA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.10对甲方的违约行为主张权利。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="285C99C2"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.11法律法规规定的其他权利。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="18C2837C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>九、提前退租、续租</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1F412652"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">9.1 </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>因乙方原因租赁合同提前解除、终止的,乙方因提前</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 30日向甲方提交退租相关材料,按照本合同约定办理退租手续。若乙方未按上述约定退租,甲方有权要求乙方赔偿损失。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="5535B8D5"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>9.2</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方若要求在租赁期满后续租该处房屋的,应当在租赁期满</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>60</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>日前</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>书面通知甲方,如甲方同意,双方应当重新订立租赁合同。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="2CF914E0"> + <w:pPr> + <w:keepNext w:val="0"/> + <w:keepLines w:val="0"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:right="0" w:rightChars="0" w:firstLine="560" + w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:color w:val="000000" w:themeColor="text1"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + <w14:textFill> + <w14:solidFill> + <w14:schemeClr w14:val="tx1"/> + </w14:solidFill> + </w14:textFill> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>9.3</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:color w:val="000000" w:themeColor="text1"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + <w14:textFill> + <w14:solidFill> + <w14:schemeClr w14:val="tx1"/> + </w14:solidFill> + </w14:textFill> + </w:rPr> + <w:t> + 《租赁合同》到期后30日内未办理退租手续的,由营业部向承租方送达《关于尽快办理退租手续的通知》,超过30日的,公司将按日收取房屋占有使用费,房屋占有使用费按已到期合同约定的租金确认。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7FE3504A"> + <w:pPr> + <w:keepNext w:val="0"/> + <w:keepLines w:val="0"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:color w:val="000000" w:themeColor="text1"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + <w14:textFill> + <w14:solidFill> + <w14:schemeClr w14:val="tx1"/> + </w14:solidFill> + </w14:textFill> + </w:rPr> + <w:t>9.4</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:color w:val="000000" w:themeColor="text1"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + <w14:textFill> + <w14:solidFill> + <w14:schemeClr w14:val="tx1"/> + </w14:solidFill> + </w14:textFill> + </w:rPr> + <w:t> + 合同到期45日未办理退租手续,且拒绝交纳房屋占有使用费的,由相关部门介入并考虑通过诉讼方式解决相关问题。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="28D07876"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>十、房屋现状、交付与到期交还租赁房屋的约定</w:t> + </w:r> + </w:p> + <w:p w14:paraId="485C8F08"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1. 房屋现状良好、现有设备正常,甲乙双方均予以认可。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5E8862AD"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.1房屋现状与设备的交付时间为</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${endTime}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + ;交付方式为自交付之日起双方签收,如存在问题,在交付日可以直接将所有问题一次性以书面方式向甲方提出异议,否则视为认可交付。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="5ACF9AE1"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.2对于管道等在短时间不易被察觉的隐蔽工程问题的交付时间为</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${endTime}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + ;交付方式为自甲方交付之日起三个月内,如存在问题,可以直接将所有问题一次性以书面方式向甲方提出异议,否则视为认可交付。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="275B0147"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.房屋交还:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="02B181FE"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.1因租赁合同期限届满且双方未达成续租协议或提前解除、终止的,乙方应当在自合同解除、终止/到期之日起10日内搬出房屋,结清水电费的费用。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="637F305C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.2交还房屋时,乙方需将承租房屋及附属设施、设备交还甲方。按照合同内容交还甲方相应设施、设备以及水卡、电卡、气卡钥匙等,并保证租赁房屋本身及附属设施、设备处于能够正常、完好使用状态。若乙方逾期30日不履行搬离义务的,甲方有权自行收回房屋,乙方同意甲方通过停水停电、换锁等方式直接收回房屋,并对房屋中所有物品进行搬离。甲方对搬离的物品不负有保管义务,由此产生的损失由乙方自行承担。因此类行为所产生的包括但不限于公证费、鉴定费、保管费、运输费、律师费、诉讼费等均由乙方承担。乙方采取拦截行为导致甲方无法收回房屋的,甲方还有权按照本合同约定日租金的三倍收取房屋占有使用费。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="3A3DA5AB"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:numPr> + <w:ilvl w:val="255"/> + <w:numId w:val="0"/> + </w:numPr> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.3乙方应于房屋租赁期满后,自行向相关单位付清水、电、卫生、物业管理等费用,由乙方经营造成的债务及其它法律纠纷与甲方无关。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="20A31710"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>十一、合同解除及终止条件</w:t> + </w:r> + </w:p> + <w:p w14:paraId="36E77F6A"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.甲、乙双方同意在租赁期内,有下列情形之一且双方均不存在违约的情形下,本合同终止,双方互不承担责任: + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="67A0F03C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(1)该房屋占用范围内的土地使用权依法提前收回的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="48467850"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(2)该房屋因社会公共利益被依法征用的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="66775918"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(3)该房屋因城市建设需要被依法列入房屋拆迁许可范围的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="345DEB8A"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(4)本合同期限届满,双方不再续签合同;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="543FF9DF"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(5)双方通过书面协议解除本合同;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="321A5313"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(6)因国家及自治区政府相关产业发展规划要求收回房屋。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5B4B7375"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">2.涉及到国家补偿的,应按国家或地方相关法律法规进行。 </w:t> + </w:r> + </w:p> + <w:p w14:paraId="1C88F3BE"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:tabs> + <w:tab w:val="right" w:pos="8717"/> + </w:tabs> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>十二、违约责任</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:tab/> + </w:r> + </w:p> + <w:p w14:paraId="45D37F69"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.乙方未按照合同约定足额缴纳租赁费用的,每拖延一日,应向甲方支付年租金的万分之六缴纳逾期利息,逾期30日(含30日)未支付的,甲方有权单方解除协议,收回租赁租赁物,同时要求乙方承担按照年度租赁产生的费用(押金+预交房租)的30%支付违约金。并赔偿由此给甲方造成的全部损失,逾期期间不免除乙方继续履行支付租赁费用的义务。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="67288FCB"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.有下列情形之一的,一方可通知另一方解除本合同。甲方有权不退还乙方已支付的租赁费用(包括前期的预付的租金和押金)。同时乙方应当向甲方按年度租赁产生的费用(押金+预交房租)的30%支付违约金,并赔偿由此给守约方造成的全部损失: + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="047722C4"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(1)乙方未征得甲方书面同意擅自改变租赁房屋用途、违法经营、从事非法活动;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="2E85A4E3"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(2)乙方转租、转让租赁房屋或变相转租、转让房屋的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="429E8D47"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(3)乙方利用租赁房屋进行非法活动,损害公共利益的,被相关部门进行处罚的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="631848F6"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(4)乙方未经相关部门审批及甲方书面同意,擅自进行修建和装修的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="76B5C80D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(5)在租赁期内,乙方将房屋进行抵押、质押的 ;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="469DED28"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(6)乙方以承租权等因本租赁合同衍生的认可权利向第三方融的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1FB4AF04"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(7)乙方及其工作人员出现闹事等涉稳事件的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1B65734A"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + (8)乙方及其工作人员、流动人员未按照辖区派出所要求办理暂住证等相关证件或擅自留宿闲杂人员(包括经济、刑事、民事等违纪违法人员)的; + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="21B201A8"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(9)因乙方原因导致的火灾或其他重大安全事故,重大治安事件、刑事案件的;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="422DEDCE"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(10)其他因乙方使用房屋不当,导致甲方声誉受损、被追责的情形。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5F4DAE6F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 3.本合同所称损失是指直接经济损失、可得利益损失和为解决争议而额外支出的包括但不限于律师费、诉讼费、鉴定费、保全费、差旅费等所有费用。上述违约金、迟延履行金、损失赔偿互不抵扣。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="35B4A1FA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> 十三、免责条款</w:t> + </w:r> + </w:p> + <w:p w14:paraId="71754F31"> + <w:pPr> + <w:pStyle w:val="8"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>1.因不可抗力原因致使本合同不能继续履行或造成的损失,合同各方互不承担违约责任。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="0CFF32DA"> + <w:pPr> + <w:pStyle w:val="8"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.不可抗力系指不能预见、不能避免并不能克服的客观情況,包括政府指令。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="20DFAF5E"> + <w:pPr> + <w:pStyle w:val="8"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 3.因上述原因导致合同终止的,甲、乙双方均不承担责任,本合同自行终止;本合同终止后,甲乙双方均应积极处理善后事宜。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="38C4E3F0"> + <w:pPr> + <w:pStyle w:val="8"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>4.</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>因合同解除或到期而终止履行合同</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>时</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>,</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>经甲方书面限期搬离催告后,乙方</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>不及时搬走</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>屋内自由</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>设施和</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>屋内</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>物品的,视为</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>已放弃租赁房屋内自由设施</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>及</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>物品,</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>有权采取抛弃、变卖、自用等方式进行处置,</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + </w:rPr> + <w:t>无权再要求返还或赔偿。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1EE84DAF"> + <w:pPr> + <w:pStyle w:val="8"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:i w:val="0"/> + <w:caps w:val="0"/> + <w:color w:val="333333"/> + <w:spacing w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:shd w:val="clear" w:fill="FFFFFF"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>5.因资产原占有单位原因致使甲方无法履行本合同的,不构成甲方违约。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="07F3F5CD"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>十四、争议处理方式</w:t> + </w:r> + </w:p> + <w:p w14:paraId="39DCB61D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.本合同的制定、解释及其在执行过程中出现的、或与本协议有关的纠纷之解决,受中华人民共和国现行有效的法律的约束。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7ACB156D"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 2.因本合同引起的或与本合同有关的任何争议,由合同双方协商解决,协商不成的,依法向甲方所在地有管辖权的人民法院起诉。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="73B78164"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>十五、通知和送达</w:t> + </w:r> + </w:p> + <w:p w14:paraId="73274FDF"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.本合同所涉事宜如需本合同签订主体通知对方或司法机关需要送达诉讼法律文书的,可采取下列任意方式通知或送达。通知或送达时间以以下方式中最先发出的通知或送达的文书为准: + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="38BB5620"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(1)以向对方联系人电话发送短信,短信发出视为送达;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="45550662"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="640"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(2)以拨打对方联系人所留电话通知,电话接通视为送达;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="7258EEAA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="640"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(3)以向对方联系人电子邮箱发送邮件,邮件发出视为送达;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1EEDDF3C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="640"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(4)以向对方联系人地址寄送EMS,EMS发出视为送达;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="5F53B1AD"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="640"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(5)以在西藏自治区境内发行的报纸上刊登公告,公告发出视为送达;</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1F88A355"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="640"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(6)甲方还可选择在乙方承租房屋门口张贴通知内容,通知贴出视为信息送达。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="0517F47C"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方邮寄址: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>5号</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="6B8FB8C1"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方联系人: ${partyOnePerson} </w:t> + </w:r> + </w:p> + <w:p w14:paraId="67827E3F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方电话: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>${partyOnePhone}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7AFC5F48"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">甲方电子邮箱: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="279A654B"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">乙方邮寄地址:— — </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="49945435"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">乙方联系人: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">${partyTwoPerson} </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="634EA417"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">乙方电话: </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> ${partyTwoPhone} </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="05E11076"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve">乙方电子邮箱:— — </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b w:val="0"/> + <w:bCs w:val="0"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="44BDF3CC"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 任何一方变更联系方式的,应当提前三日通知对方,否则按照原通讯方式进行通知即视为通知到达。由此产生的不利后果由未履行通知义务的一方承担。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="00F7A5FD"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 双方共同确认:在涉及诉讼或非诉执行阶段,上述送达方式适用于各个司法阶段,包括但不限于一审、二审、再审、执行以及督促程序。同时双方保证送达地址准确、有效,如果提供的地址不准确,或者不及时告知变更后的地址,使法律文书无法送达或未及时送达,自行承担由此可能产生的法律后果。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="2FAE7B7F"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>十六、其他</w:t> + </w:r> + </w:p> + <w:p w14:paraId="55936448"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 1.本合同未尽事宜,依照有关法律、法规执行,法律、法规未作规定的,双方可以达成书面补充合同。本合同的附件和补充合同均为本合同不可分割的组成部分,与本合同具有同等的法律效力。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="0FF8AC34"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>2.本合同自双方或双方法定代表人或授权代表人签字并加盖单位公章或合同专用章之日起生效。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="45E62463"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>3.本合同正本壹式</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>肆</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>份,甲方执</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>叁</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>,乙方执</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>壹</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>份</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>,具有同等法律效力。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="75B992AD"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>4.本合同附件与本合同具有同等效力。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="57D7A304"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 5.乙方在签订本合同前,已经仔细阅读、理解并知悉合同全部条款,经过充分协商并明确予以同意。甲方已经向乙方履行本合同相应条款的全部告知义务。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7B0CB8D7"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>6.本合同中所指的“租赁费用”包括但不限于租金、押金等。</w:t> + </w:r> + </w:p> + <w:p w14:paraId="4C9D67C8"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:val="en-US" w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>7.</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t> + 本协议任何条款、及本协议所涉及的甲方的任何资料,均属于应该保密的信息,乙方均负有保密义务。未经甲方书面正式许可,不能以任何理由,在任何场合泄露、披露本协议任何条款及本协议所涉及甲方的任何资料。否则,甲方有权行使本合同所约定的权利。 + </w:t> + </w:r> + </w:p> + <w:p w14:paraId="4684371B"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>经甲方工作人员充分提示和说明,</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>本人已经仔细阅读、理解并知悉合同全部条款,明确予以同意。)</w:t> + </w:r> + </w:p> + <w:p w14:paraId="0669D1A4"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="7101FC22"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="0" w:firstLineChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:u w:val="single"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> </w:t> + </w:r> + </w:p> + <w:p w14:paraId="4DF04898"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(以下无正文)</w:t> + </w:r> + </w:p> + <w:p w14:paraId="293BF1C8"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:br w:type="page"/> + </w:r> + </w:p> + <w:p w14:paraId="4789233B"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>(本页为签字页,无正文)</w:t> + </w:r> + </w:p> + <w:p w14:paraId="0E375010"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>甲方(盖章):</w:t> + </w:r> + </w:p> + <w:p w14:paraId="33B36336"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>法定代表人或授权代表(签字):</w:t> + </w:r> + </w:p> + <w:p w14:paraId="31D387AA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>授权经办人:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="798C3AEA"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>联系电话:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="073B781B"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="134239FA"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="2C008BD3"> + <w:pPr> + <w:rPr> + <w:rFonts w:hint="eastAsia"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="386CCEA1"> + <w:pPr> + <w:rPr> + <w:rFonts w:hint="eastAsia"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="13D3DFF1"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="562" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:b/> + <w:bCs/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>乙方(盖章):</w:t> + </w:r> + </w:p> + <w:p w14:paraId="1C2D82B2"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>法定代表人或授权代表(签字):</w:t> + </w:r> + </w:p> + <w:p w14:paraId="462FA378"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>联系电话:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="3A37B4D1"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="0" w:firstLineChars="0"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="3DF15E38"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="0F8D2D8D"> + <w:pPr> + <w:rPr> + <w:rFonts w:hint="eastAsia"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="71250920"> + <w:pPr> + <w:pStyle w:val="2"/> + <w:rPr> + <w:rFonts w:hint="eastAsia"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + </w:p> + <w:p w14:paraId="062B975A"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>签署地点:</w:t> + </w:r> + </w:p> + <w:p w14:paraId="56853637"> + <w:pPr> + <w:pageBreakBefore w:val="0"/> + <w:widowControl/> + <w:kinsoku/> + <w:wordWrap/> + <w:overflowPunct/> + <w:topLinePunct w:val="0"/> + <w:autoSpaceDE/> + <w:autoSpaceDN/> + <w:bidi w:val="0"/> + <w:adjustRightInd/> + <w:snapToGrid/> + <w:spacing w:before="0" w:beforeAutospacing="0" w:after="0" w:afterAutospacing="0" + w:line="576" w:lineRule="exact"/> + <w:ind w:left="0" w:leftChars="0" w:firstLine="560" w:firstLineChars="200"/> + <w:textAlignment w:val="auto"/> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" + w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + <w:highlight w:val="none"/> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:t>签署时间:</w:t> + </w:r> + </w:p> + <w:sectPr> + <w:headerReference r:id="rId6" w:type="first"/> + <w:footerReference r:id="rId9" w:type="first"/> + <w:headerReference r:id="rId4" w:type="default"/> + <w:footerReference r:id="rId7" w:type="default"/> + <w:headerReference r:id="rId5" w:type="even"/> + <w:footerReference r:id="rId8" w:type="even"/> + <w:pgSz w:w="12240" w:h="15840"/> + <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" + w:gutter="0"/> + <w:cols w:space="720" w:num="1"/> + </w:sectPr> + </w:body> + </w:document> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/docProps/app.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"> + <pkg:xmlData> + <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" + xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"> + <Template>Normal</Template> + <Company>Microsoft</Company> + <Pages>16</Pages> + <Words>7647</Words> + <Characters>7997</Characters> + <Lines>56</Lines> + <Paragraphs>15</Paragraphs> + <TotalTime>1028</TotalTime> + <ScaleCrop>false</ScaleCrop> + <LinksUpToDate>false</LinksUpToDate> + <CharactersWithSpaces>8486</CharactersWithSpaces> + <Application>WPS Office_12.1.0.19770_F1E327BC-269C-435d-A152-05C5408002CA</Application> + <DocSecurity>0</DocSecurity> + </Properties> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/docProps/core.xml" + pkg:contentType="application/vnd.openxmlformats-package.core-properties+xml"> + <pkg:xmlData> + <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" + xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" + xmlns:dcmitype="http://purl.org/dc/dcmitype/" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <dcterms:created xsi:type="dcterms:W3CDTF">2020-03-06T15:28:00Z</dcterms:created> + <dc:creator>Chris</dc:creator> + <cp:lastModifiedBy>Small 黑</cp:lastModifiedBy> + <cp:lastPrinted>2020-11-03T09:32:00Z</cp:lastPrinted> + <dcterms:modified xsi:type="dcterms:W3CDTF">2025-02-08T02:01:29Z</dcterms:modified> + <cp:revision>10</cp:revision> + </cp:coreProperties> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/docProps/custom.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"> + <pkg:xmlData> + <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" + xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"> + <property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="KSOProductBuildVer"> + <vt:lpwstr>2052-12.1.0.19770</vt:lpwstr> + </property> + <property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="3" name="ICV"> + <vt:lpwstr>1DC0A18B44B644918FF2849E3A9A2ACB_13</vt:lpwstr> + </property> + </Properties> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/fontTable.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"> + <pkg:xmlData> + <w:fonts xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14"> + <w:font w:name="Times New Roman"> + <w:panose1 w:val="02020603050405020304"/> + <w:charset w:val="86"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E0002EFF" w:usb1="C000785B" w:usb2="00000009" w:usb3="00000000" w:csb0="400001FF" + w:csb1="FFFF0000"/> + </w:font> + <w:font w:name="宋体"> + <w:panose1 w:val="02010600030101010101"/> + <w:charset w:val="7A"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="00000203" w:usb1="288F0000" w:usb2="00000006" w:usb3="00000000" w:csb0="00040001" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Wingdings"> + <w:panose1 w:val="05000000000000000000"/> + <w:charset w:val="02"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="00000000" w:usb1="00000000" w:usb2="00000000" w:usb3="00000000" w:csb0="80000000" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Arial"> + <w:panose1 w:val="020B0604020202020204"/> + <w:charset w:val="01"/> + <w:family w:val="swiss"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E0002EFF" w:usb1="C000785B" w:usb2="00000009" w:usb3="00000000" w:csb0="400001FF" + w:csb1="FFFF0000"/> + </w:font> + <w:font w:name="黑体"> + <w:panose1 w:val="02010609060101010101"/> + <w:charset w:val="86"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="800002BF" w:usb1="38CF7CFA" w:usb2="00000016" w:usb3="00000000" w:csb0="00040001" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Courier New"> + <w:panose1 w:val="02070309020205020404"/> + <w:charset w:val="01"/> + <w:family w:val="modern"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E0002EFF" w:usb1="C0007843" w:usb2="00000009" w:usb3="00000000" w:csb0="400001FF" + w:csb1="FFFF0000"/> + </w:font> + <w:font w:name="Symbol"> + <w:panose1 w:val="05050102010706020507"/> + <w:charset w:val="02"/> + <w:family w:val="roman"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="00000000" w:usb1="00000000" w:usb2="00000000" w:usb3="00000000" w:csb0="80000000" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Calibri"> + <w:panose1 w:val="020F0502020204030204"/> + <w:charset w:val="00"/> + <w:family w:val="swiss"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E4002EFF" w:usb1="C200247B" w:usb2="00000009" w:usb3="00000000" w:csb0="200001FF" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Calibri Light"> + <w:panose1 w:val="020F0302020204030204"/> + <w:charset w:val="00"/> + <w:family w:val="swiss"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E4002EFF" w:usb1="C200247B" w:usb2="00000009" w:usb3="00000000" w:csb0="200001FF" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Cambria"> + <w:panose1 w:val="02040503050406030204"/> + <w:charset w:val="00"/> + <w:family w:val="roman"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E00006FF" w:usb1="420024FF" w:usb2="02000000" w:usb3="00000000" w:csb0="2000019F" + w:csb1="00000000"/> + </w:font> + <w:font w:name="仿宋_GB2312"> + <w:altName w:val="仿宋"/> + <w:panose1 w:val="02010609030101010101"/> + <w:charset w:val="86"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="00000000" w:usb1="00000000" w:usb2="00000000" w:usb3="00000000" w:csb0="00040000" + w:csb1="00000000"/> + </w:font> + <w:font w:name="仿宋"> + <w:panose1 w:val="02010609060101010101"/> + <w:charset w:val="86"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="800002BF" w:usb1="38CF7CFA" w:usb2="00000016" w:usb3="00000000" w:csb0="00040001" + w:csb1="00000000"/> + </w:font> + <w:font w:name="微软雅黑"> + <w:panose1 w:val="020B0503020204020204"/> + <w:charset w:val="86"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="80000287" w:usb1="2ACF3C50" w:usb2="00000016" w:usb3="00000000" w:csb0="0004001F" + w:csb1="00000000"/> + </w:font> + <w:font w:name="Tahoma"> + <w:panose1 w:val="020B0604030504040204"/> + <w:charset w:val="00"/> + <w:family w:val="auto"/> + <w:pitch w:val="default"/> + <w:sig w:usb0="E1002EFF" w:usb1="C000605B" w:usb2="00000029" w:usb3="00000000" w:csb0="200101FF" + w:csb1="20280000"/> + </w:font> + </w:fonts> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/footer1.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"> + <pkg:xmlData> + <w:ftr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:sdt> + <w:sdtPr> + <w:id w:val="9072722"/> + </w:sdtPr> + <w:sdtContent> + <w:sdt> + <w:sdtPr> + <w:id w:val="171357217"/> + </w:sdtPr> + <w:sdtContent> + <w:p w14:paraId="63CC8DC0"> + <w:pPr> + <w:pStyle w:val="6"/> + <w:jc w:val="center"/> + </w:pPr> + <w:r> + <w:rPr> + <w:b/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + <w:fldChar w:fldCharType="begin"/> + </w:r> + <w:r> + <w:rPr> + <w:b/> + </w:rPr> + <w:instrText xml:space="preserve">PAGE</w:instrText> + </w:r> + <w:r> + <w:rPr> + <w:b/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + <w:fldChar w:fldCharType="separate"/> + </w:r> + <w:r> + <w:rPr> + <w:b/> + </w:rPr> + <w:t>18</w:t> + </w:r> + <w:r> + <w:rPr> + <w:b/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + <w:fldChar w:fldCharType="end"/> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="zh-CN"/> + </w:rPr> + <w:t xml:space="preserve"> / </w:t> + </w:r> + <w:r> + <w:rPr> + <w:b/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + <w:fldChar w:fldCharType="begin"/> + </w:r> + <w:r> + <w:rPr> + <w:b/> + </w:rPr> + <w:instrText xml:space="preserve">NUMPAGES</w:instrText> + </w:r> + <w:r> + <w:rPr> + <w:b/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + <w:fldChar w:fldCharType="separate"/> + </w:r> + <w:r> + <w:rPr> + <w:b/> + </w:rPr> + <w:t>18</w:t> + </w:r> + <w:r> + <w:rPr> + <w:b/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + <w:fldChar w:fldCharType="end"/> + </w:r> + </w:p> + </w:sdtContent> + </w:sdt> + </w:sdtContent> + </w:sdt> + <w:p w14:paraId="5985DD6F"> + <w:pPr> + <w:pStyle w:val="6"/> + </w:pPr> + </w:p> + </w:ftr> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/footer2.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"> + <pkg:xmlData> + <w:ftr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:p w14:paraId="4CB7D29F"> + <w:pPr> + <w:pStyle w:val="6"/> + </w:pPr> + </w:p> + </w:ftr> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/footer3.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"> + <pkg:xmlData> + <w:ftr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:p w14:paraId="3581C36B"> + <w:pPr> + <w:pStyle w:val="6"/> + </w:pPr> + </w:p> + </w:ftr> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/footnotes.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"> + <pkg:xmlData> + <w:footnotes xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:footnote w:type="separator" w:id="0"> + <w:p> + <w:pPr> + <w:spacing w:before="0" w:after="0"/> + </w:pPr> + <w:r> + <w:separator/> + </w:r> + </w:p> + </w:footnote> + <w:footnote w:type="continuationSeparator" w:id="1"> + <w:p> + <w:pPr> + <w:spacing w:before="0" w:after="0"/> + </w:pPr> + <w:r> + <w:continuationSeparator/> + </w:r> + </w:p> + </w:footnote> + </w:footnotes> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/_rels/header1.xml.rels" + pkg:contentType="application/vnd.openxmlformats-package.relationships+xml"> + <pkg:xmlData> + <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + Target="media/image1.jpeg"/> + </Relationships> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/header1.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"> + <pkg:xmlData> + <w:hdr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:p w14:paraId="34581354"> + <w:pPr> + <w:pStyle w:val="7"/> + <w:rPr> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + </w:pPr> + <w:r> + <w:rPr> + <w:sz w:val="18"/> + </w:rPr> + <w:drawing> + <wp:anchor distT="0" distB="0" distL="114300" distR="114300" simplePos="0" + relativeHeight="251661312" behindDoc="1" locked="0" layoutInCell="1" + allowOverlap="1"> + <wp:simplePos x="0" y="0"/> + <wp:positionH relativeFrom="margin"> + <wp:align>center</wp:align> + </wp:positionH> + <wp:positionV relativeFrom="margin"> + <wp:align>center</wp:align> + </wp:positionV> + <wp:extent cx="5943600" cy="3353435"/> + <wp:effectExtent l="0" t="0" r="0" b="18415"/> + <wp:wrapNone/> + <wp:docPr id="4" name="WordPictureWatermark15005" descr="微信图片_20200308181427"/> + <wp:cNvGraphicFramePr> + <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" + noChangeAspect="1"/> + </wp:cNvGraphicFramePr> + <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"> + <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"> + <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"> + <pic:nvPicPr> + <pic:cNvPr id="4" name="WordPictureWatermark15005" + descr="微信图片_20200308181427"/> + <pic:cNvPicPr> + <a:picLocks noChangeAspect="1"/> + </pic:cNvPicPr> + </pic:nvPicPr> + <pic:blipFill> + <a:blip r:embed="rId1"> + <a:lum bright="70000" contrast="-70000"/> + </a:blip> + <a:stretch> + <a:fillRect/> + </a:stretch> + </pic:blipFill> + <pic:spPr> + <a:xfrm> + <a:off x="0" y="0"/> + <a:ext cx="5943600" cy="3353435"/> + </a:xfrm> + <a:prstGeom prst="rect"> + <a:avLst/> + </a:prstGeom> + </pic:spPr> + </pic:pic> + </a:graphicData> + </a:graphic> + </wp:anchor> + </w:drawing> + </w:r> + </w:p> + </w:hdr> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/_rels/header2.xml.rels" + pkg:contentType="application/vnd.openxmlformats-package.relationships+xml"> + <pkg:xmlData> + <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + Target="media/image1.jpeg"/> + </Relationships> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/header2.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"> + <pkg:xmlData> + <w:hdr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:p w14:paraId="37887B69"> + <w:pPr> + <w:pStyle w:val="7"/> + </w:pPr> + <w:r> + <w:rPr> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:drawing> + <wp:anchor distT="0" distB="0" distL="114300" distR="114300" simplePos="0" + relativeHeight="251660288" behindDoc="1" locked="0" layoutInCell="0" + allowOverlap="1"> + <wp:simplePos x="0" y="0"/> + <wp:positionH relativeFrom="margin"> + <wp:align>center</wp:align> + </wp:positionH> + <wp:positionV relativeFrom="margin"> + <wp:align>center</wp:align> + </wp:positionV> + <wp:extent cx="6076950" cy="3429000"/> + <wp:effectExtent l="0" t="0" r="0" b="0"/> + <wp:wrapNone/> + <wp:docPr id="3" name="WordPictureWatermark27394618" descr="微信图片_20200308181427"/> + <wp:cNvGraphicFramePr> + <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" + noChangeAspect="1"/> + </wp:cNvGraphicFramePr> + <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"> + <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"> + <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"> + <pic:nvPicPr> + <pic:cNvPr id="3" name="WordPictureWatermark27394618" + descr="微信图片_20200308181427"/> + <pic:cNvPicPr> + <a:picLocks noChangeAspect="1"/> + </pic:cNvPicPr> + </pic:nvPicPr> + <pic:blipFill> + <a:blip r:embed="rId1"> + <a:lum bright="70001" contrast="-70000"/> + </a:blip> + <a:stretch> + <a:fillRect/> + </a:stretch> + </pic:blipFill> + <pic:spPr> + <a:xfrm> + <a:off x="0" y="0"/> + <a:ext cx="6076950" cy="3429000"/> + </a:xfrm> + <a:prstGeom prst="rect"> + <a:avLst/> + </a:prstGeom> + <a:noFill/> + <a:ln> + <a:noFill/> + </a:ln> + </pic:spPr> + </pic:pic> + </a:graphicData> + </a:graphic> + </wp:anchor> + </w:drawing> + </w:r> + </w:p> + </w:hdr> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/_rels/header3.xml.rels" + pkg:contentType="application/vnd.openxmlformats-package.relationships+xml"> + <pkg:xmlData> + <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> + <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + Target="media/image1.jpeg"/> + </Relationships> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/header3.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"> + <pkg:xmlData> + <w:hdr xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 w15 wp14"> + <w:p w14:paraId="652FD995"> + <w:pPr> + <w:pStyle w:val="7"/> + </w:pPr> + <w:r> + <w:rPr> + <w:lang w:eastAsia="zh-CN"/> + </w:rPr> + <w:drawing> + <wp:anchor distT="0" distB="0" distL="114300" distR="114300" simplePos="0" + relativeHeight="251659264" behindDoc="1" locked="0" layoutInCell="0" + allowOverlap="1"> + <wp:simplePos x="0" y="0"/> + <wp:positionH relativeFrom="margin"> + <wp:align>center</wp:align> + </wp:positionH> + <wp:positionV relativeFrom="margin"> + <wp:align>center</wp:align> + </wp:positionV> + <wp:extent cx="6076950" cy="3429000"/> + <wp:effectExtent l="0" t="0" r="0" b="0"/> + <wp:wrapNone/> + <wp:docPr id="1" name="WordPictureWatermark27394617" descr="微信图片_20200308181427"/> + <wp:cNvGraphicFramePr> + <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" + noChangeAspect="1"/> + </wp:cNvGraphicFramePr> + <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"> + <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"> + <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"> + <pic:nvPicPr> + <pic:cNvPr id="1" name="WordPictureWatermark27394617" + descr="微信图片_20200308181427"/> + <pic:cNvPicPr> + <a:picLocks noChangeAspect="1"/> + </pic:cNvPicPr> + </pic:nvPicPr> + <pic:blipFill> + <a:blip r:embed="rId1"> + <a:lum bright="70001" contrast="-70000"/> + </a:blip> + <a:stretch> + <a:fillRect/> + </a:stretch> + </pic:blipFill> + <pic:spPr> + <a:xfrm> + <a:off x="0" y="0"/> + <a:ext cx="6076950" cy="3429000"/> + </a:xfrm> + <a:prstGeom prst="rect"> + <a:avLst/> + </a:prstGeom> + <a:noFill/> + <a:ln> + <a:noFill/> + </a:ln> + </pic:spPr> + </pic:pic> + </a:graphicData> + </a:graphic> + </wp:anchor> + </w:drawing> + </w:r> + </w:p> + </w:hdr> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/media/image1.jpeg" pkg:contentType="image/jpeg"> + <pkg:binaryData>/9j/4QCERXhpZgAATU0AKgAAAAgABQEAAAQAAAABAAABPwEBAAQAAAABAAAAtAEyAAIAAAAUAAAA + SgESAAMAAAABAAEAAIdpAAQAAAABAAAAXgAAAAAyMDIwOjAzOjA4IDE4OjE0OjAwAAACoAMABAAA + AAEAAAC0oAIABAAAAAEAAAE/AAAAAP/gABBKRklGAAEBAAABAAEAAP/iAihJQ0NfUFJPRklMRQAB + AQAAAhgAAAAAAhAAAG1udHJSR0IgWFlaIAAAAAAAAAAAAAAAAGFjc3AAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAABAAD21gABAAAAANMtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAACWRlc2MAAADwAAAAdHJYWVoAAAFkAAAAFGdYWVoAAAF4AAAAFGJY + WVoAAAGMAAAAFHJUUkMAAAGgAAAAKGdUUkMAAAGgAAAAKGJUUkMAAAGgAAAAKHd0cHQAAAHIAAAA + FGNwcnQAAAHcAAAAPG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAWAAAABwAcwBSAEcAQgAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZ + WiAAAAAAAAAkoAAAD4QAALbPcGFyYQAAAAAABAAAAAJmZgAA8qcAAA1ZAAAT0AAAClsAAAAAAAAA + AFhZWiAAAAAAAAD21gABAAAAANMtbWx1YwAAAAAAAAABAAAADGVuVVMAAAAgAAAAHABHAG8AbwBn + AGwAZQAgAEkAbgBjAC4AIAAyADAAMQA2/9sAQwABAQEBAQEBAQEBAQEBAgIDAgICAgIEAwMCAwUE + BQUFBAQEBQYHBgUFBwYEBAYJBgcICAgICAUGCQoJCAoHCAgI/9sAQwEBAQECAgIEAgIECAUEBQgI + CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI/8AAEQgAtAE/ + AwEiAAIRAQMRAf/EAB8AAQABAgcBAAAAAAAAAAAAAAAKCAkBAgMEBgcLBf/EAGQQAAECBAQCBAYK + DAUQCAcAAAECAwAEBREGBwghCRIKEzFBFBoiUWHTFRYycYGRk5XR0hcYGSMzVVdYlJah1CRSU3WF + JjQ2N0JUVmJydoKDkrPB8ChDR0hmaIaHl6KlscPh8f/EAB0BAQAABwEBAAAAAAAAAAAAAAACAwQF + BgcIAQn/xABREQABAwMBBAMJCggLCQAAAAABAAIDBAURBgcSITETQWEIFCJRcYGRkqEVFiOTscHR + 0tPhCRcZMjNUcnMYJDU2QlJWYuLw8URTVWNkdLKz4//aAAwDAQACEQMRAD8An8QhCCJCEIIkIQgi + QhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJC + EIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQ + giQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgi2z02zLqSl0qFyALC/wDz2Rh4YzzcoJva + /ZtFB3Er1E5o6VNJeZWe+UWGKDi3FGH0y827J1Pn6gyhdSl5XkXVdKVE/BEfzQZ0g3ObUDquycyN + zfy1wDhnBeKKiukOTtPmHC7KzK2HeqA5hYoU8GkX7rkmw3jGbrq2koqhlNPwc/kt4aE7nTVWpLFU + 6ktEQfTU2d/iN4brd48PJxHYpeyplpKuQk83Z2RmS+2oBQJt70R2+MbxUtQ3DwzOyiw9l7l3gLE+ + A8SUWcmfDKot8PJnGHUpcT97IFgl+UIB3PMojZJtZ5a6Tfq3JQlvJPKFSlrQj8NNW5iAP42wv39k + UNx13Q0szoJjhzef3LN9A9xnrvUtnjvlqha+nlBIO+0YwcHOT9CnWB1Cr2JjNzp9MQTR0nbVsENr + Rk3lEErSFgddNbA/DtGPjPGrj8jmUPy019MW8bUbUeO8fQs6P4PDaYOcEfxjfpU7HnT6Yc6fTEE7 + xnjVx+RzKH5aa+mMp6T1q3Gxycyi+Wmvph+NC1f1j6F5+Tx2l/7iP4xv0qdct9CCL3jATLRAI5rE + 27IgrN9J31bLsU5NZQKsSFDrZq/YbHt7NjF8HQjxOc1tUWgPVLqlxfg7CNIxnglmqrkpKSddMvMd + RTUTKOt57qsVOEG3cBFdb9oFuqt5sJJc1YRtD7jPXOl6Jlxu0TGxPeyMYe1xLnnDeAPDzq/WH2yL + g3EOuR6YgpI6TvqvaBbZyWygS2CbBT8ydrm3YR3WMZ/GetWv5F8nPlZr60W+TalaQ4jfd6v3rOI/ + wee0wjPe0fnlYPZlTquuR6Ydcj0xBV8Z61a/kXyc+VmvrQ8Z61a/kXyc+VmvrRL/ABp2n+ufV+9R + /k8dpn6tH8cz6VOq65Hph1yPTEFXxnrVr+RfJz5Wa+tDxnrVr+RfJz5Wa+tD8adp/rn1fvT8njtM + /Vo/jmfSp1AmG+flvbvjKZpoBRUSADaILXjOmrdxJX9hTJxxA7i5M7H0eVGKOk56tVOKSvJjJoL5 + kp5XHpkAX7Nubs9Pd8MVEe061uG8CcDsUuX8HhtPGWiljBI4fCs4+1ToVzzDd+Yq7CdgTe3/API1 + TMNg2vc3t8MQ4dH/AB8dXuqXVBktkdLZP5XU9rElbZkpiZacmCZaW3U8tCTfcNpcNybWTExRtaA0 + FcptzEXPbe+5+O8ZNp+/wXGN0sHEA4XO21/YvfNC1zLfqFrWyPbvANIdgdpHBb9KwoqA7jYxmjSb + IPPYW3jVi+kLU7QceFzSEIR4vUhCEESEIQRIQhBEhCEESEIQRIQhBEhCEESEIQRdP575c0nOLKXM + bKWustvUjElDnaLMcwvyJfZW3zgedJUkj/8AUeVFT3MT6ec5ae+sTMnjfBuJG18l7KbmpSaSSCob + bltQv2bx61c40HFkqWUeSQDbs7/+EeafxnMok5PcR3UfQ25XwOmVqoNYjlk8tgGpxCXVlPoCw6Pg + MaY2uUGOhrGji0r6l/gxNURyXC66YqTmOpiyGnkSDuuPna72KRxx+MGUfUtw1sj9U+GGPDjSJumY + kbmEIKbU+pS6UKvzWIHM/LHl7bjs2NosXD2xZpYwxqXw0vWvRafXtPU3ITsnVi7LTT/gjhQksupT + LXcV5bQHkpNucm/ml7cP3Dkvr74HlLyNqVbpjOIF4XqmBWpmdJLcjOyi1Ik3nLeVZsiWc2B2SLRZ + 5b6MfqwfbZbGoDT4stoSkkzFQSFG1ybeDq7ye+LJqfTNZVvguNGxr8gE5I4+VbP2AbXtN6WsF72f + aorn0fRzTRsczfDw1x5tIyARzHBVkHMnox3OtbsjRlKUbj+pyviw7AB95+DeH2SujE/i+jfq5X/V + RRsejFasSSFZ+6eG7beTMTx/+8rDxYjVh+cBp6+Xnv3WLhLDdt7+T4j5x9ZUbZ9lgAHvzuPxrvsl + WV9knoxNr+AUUf8Apyv+pj7GHcTdGexbiGhYXolIocxWalNsyEk2rD9dQHn3FhCEBSmgBdSgN9oo + fT0YnVkohsZ96eVf43Xz37rHOMteja6p8DZlZeYsqWfOQcxTqRW5GpvtszE8VuNszCHFJQCwkBVk + Hc+iIoqe5umYx1vi3Tz4/eqO51WzSOlldTazuDpQ0lg6V2N7qB+D+hdX9IT0X6btINZ0kfa8ZbUr + L2WxEziNdU8HdcV4WmXVTOrBCiTceEumK3OBFiPCeF+F3rKxFmLh+Yxlg2Tm6zN1aloUEGoyaKQ0 + VsBVxYrQAm/dHXXSmUdXU9CrPXB9xLGL+cpVzBNjR+091wDYd9j5o+vwdA0eDXxC1HlKxLYjA89x + RGwR7/kmKGSOKl1FJ0cYG6380cvzQo626V122DW+e5zySvfVMaXFx3j/ABggEuPHIHAHPDCoORrt + 4OCS8t/hp4tmXCtS1LTVkAAX2Gz47rDs9EZhrw4NBFxwzcWkeiso/eItNaQZ7T/RNSuU05qnp7tV + yERMzK8QsIQ8VFPg7oav1Kg5s8GTYX7PNElaUzE6M34O14Zl4hMyRdYHs8beYfhiNhYbbRYbVcX1 + WXdJEzieBYMjy8Fv7afpu16Vq4qFtFdK3eaCXQVEzmjPV+lHHsx4lQYdefBlGx4aGKwf55R+8Q+3 + z4Mp7OGhis/0yj94ivtGPejLvHlYy+W4vuSlNdKvi63ftjROYPRlh2YDBHn5a7Y/D1sXTB5d8Qep + /hWsRqW35/kW94/ezfbKgr7fPgyjt4aGKx/TKP3iM6deHBoWSEcM3FyyO4VlJPYT/fHmBivJGYPR + lgoFWASU+blrvrYoX4iGYPBZmdOGIqZoey/TL54TU7JtykwV1ZsSkulwLeXeYc5DdKOrt2/fL2sD + FPVSOjjMjaiB3YGD6FfNN19tuFwiofcm8xiQgbz5pmtbnrJ6Y8B1rsLJzVRwgs5M1ctcpMN8MvFX + tgxLWpWjyana0nkZcfWltKlATF9ioX83faOV8fPTBo+0j4O08YU0+5L0PAOP8RVOeqE9Nysw484K + fLNNoU2oLUq3O7OtKBuP63MWGtMeUWo/OfNGnUvS7Q8XYgzPpbfso0/QnyxM01CXU3fS4FIseYpt + vf4o1dTf20FPzTOCtWOI8ya3mlQ2GmvB8UVh2fmqcwpHWhoqW45yc4dbWUAixPYDeLJJfmvtz3Sx + DePAEDA9i3VSbDxSbRLe60XeTcgaXyQPqJJJHZ4N4Oe4BvmV6vo0+RjmO9YmPM7KpJCYpOCcMOS8 + qpQun2TnXEtoUP8AGSyzNo27Osv3iJ2TReUOV2wBTyjfvHZ+yI6XRpMnEYR0TYnzYm5BDFRxbi6d + dYd73ZSVSiWT6bda1M9vv9hBiRy00ShPMBcG943fswt4pbWwYxvcV8d+7k12+/7Sq8yOLmQkwjsD + PnyStdj3J7eYWB9+NeNNtNisjsJvGpGdg55rkpmccUhCEeqJIQhBEhCEESEIQRIQhBEhCEESEIQR + IQhBEhCEEW0futRbA5jbYRC26SbpXzOxnqiyUzZyxy2xtjpFUwl7DVBVGpT02Jd2Um3VIU4W0nlK + kzxG/wDEETRH3HEPqI9xyi23fFpri7a8sdcP3I/BWbeB8vKBmK5UMQJoU1LT82tgNBbDzqVJUlKj + uWD3d3vRietaOGeiLKk7rfGuie5S1teLHrelq7BC2aqk3o2scd0O3hjmPZ2qCVl9h7iIZW0N3DuW + OHtVWDMPKdMwZGk0+osM9coAKWpCEgBZCUk33taObjFHFZTyhE7rUQLb8rNVH/CLufjQmfCAEu6W + MsFOd/NWZg/t6kQ8aHz0/NWys+d5j1MaGjgszW7prD6CvsLW3fadVvM0+kKZxJycyx5J7d5ufSrR + qsU8Vna07rXV77VV+iMntp4rX9961vk6r9EXdfGh89PzVsrPneY9TDxofPT81bKz53mPUxE2OzgY + Fa70FSXVG0cnPvNpPjYvqq0YMU8Vvun9ayf9VVfojKcUcVo+6nNai/SWqp9EXdfGh89PzVsrPneY + 9TDxofPT81bKz53mPUxAIbOCT3870FeNm2jB2+3RtID+9i+qrHmYWWOvLOJynTWa2X2pzMGapzbv + gCqzSqhM+ChYHMG+dJsVFKb93kiJTnBEycfwxw2tXWC9R1FxjlNguqTtZaqkzVZNck/L0xdMbQ5M + JDqO5HMebcX2tcWii5rpQ2evKSNKuVNwCbmszA//ABRee0U6vMa8WbQnqecxLg/CWT9TnpSq4MZd + l5px+VZQ/TgevcK0pPkmYNxbsAN94yjR1Nbe+3SUk5mk3eRGPaVpDuo9U6/OkYqLUFkittCJoiZI + 5mOI8McQxo+bzKykNCPAMWq69f1WlBb3Ps/L3v5yOq7Y1BoR4BKbJRxAaq8s9oFblr/EGY4r4tzj + hJIOsTT4yR5PLZwe5HKNkgAGwEZV9G7x0UhKdZWnvlJ3B68j4rdsSG0lQCc0UeT28flV4pdb6VjA + LNaVreGOEXz9DxXws2NKnAXyvwjUMWSGr7NDMh2WLYTR8OzsrOT02tShYNtFoWF0C5JGxjlmG9Fn + AHxDRqZVE66MZ0pL7YUmVqVRZlpiX29y4ytgKbPoUBvc98XFtFHAn0hZAYg9veoLNnB2oPELbZak + qc8GZalSJUmy3CjmKnlncWWbAWsL3jrTV/0ffTbmrjar480xZ+4JyZbnVB57DM6tqZpssrsUZdSF + BxtCrA8liAeY33sKr3CqmxiXvaM56g45/wDLCxRm2rThuXuW7UlybGP9oLGbhPi3ej3/AD4CpORo + V4Aq72191VO9r+zsr6mNQaEuAQkhSOIJU21+iuS3Z8lHDWOjgY3UlQGsbIFvlNuRRfIT/k7dka/i + 3uOPzydPnxP/AERRigmaSRQxgnxuyflWYx640s0cNaVzc8x0efb0GFcS0JVTgs8P3H2NcwMrNbNB + xLW61SUUl41qtMOpbbDyVjqkpbTZV++/fEUviAZy07P3WtqdzkoE/K1aiVbFlQcpM2z7iapzC+ol + nE2H90yw0rz72MXesa9HoxbgjCWJcVVDWDkXN0+nU+an3mJdt1a5gNNFYQkKA3ukd/fEerDdDcrm + J6LRWldc67PS8qhtIP3znWAbJJuLlXZ5z2RZ9QVVSY4qaSJjG5A4f6rdHc4aY0t7qXDVtmuc1wqu + jDHPmbu7oaN7wRuM48F6bnCqypTk1w+dLWBxLpYmm8LS9QmR535q804Se88z6vii4QgL5SCCDeOF + ZZYblsG4BwfhGTaDMrS6ZKyDaQNgG2Up2+K0c7jpK1wiKmZGOoBfAbWt6907xWXAnJmle/P7TiVg + L2F+2MYQitWMgYSEIQXqQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIItnMJHWJWpWwtZPpigfi + JYH0g4n08Vap642WHchqLUpSpTT7q30JlZlazLNKJY++WKpoJ7Nua8V8vJuvfsNre/FD3EU0uTet + LSLnBpzpNbkMO1XEMrK+Cz02VdQw9LzjEynrOUE2KpdI2H91Fuu8e/SvDmh2M8Dy+ZZToSrjgvtL + NUTuhja9mZGEhzAT4TgR1gcQo93tf6MmbhVdoSlDbebqx7u498Pa90ZL8dUH9Kq0UrDoxuqR4BZz + 5yQ5uVIVymbsDYXABZ23vGPixOqP8vGSnxzXqo0lEbnu/BUMe71f5JX1qfc9mxOffrXnyyuz/wCt + VVDDnRkiL+zVCt3nwirWHvnsHwxlOHejJgc6qzQUt/xjNVXl+OKWldGQ1Rsyz/WZ75G9TdJP9eFR + 33/6ncDY27YsK6otPeINLee2aGRmK65RMS4jw1NIlZidp6l+DzJUyl0EJWgW8lY29EW643mtpgHS + 0kY82fnWf7ONn2lNV1j6Cwatr6iRjS92JS0NA8e8zj5lKQOHejJgAms0MA7i8zVhcececRh7XujJ + fjqg/pVWigLJDo7mo7O3KfAGbFEzpyepdLxBS5eqsS7/AISFstuoC0hVmiL2IPwx2r4sTqj/AC8Z + KfHNeqivp6m6SsEkdFGWnlyHzrCK5+zylmfTVGs65sjCWkdKeBBwR+jVU6sPdGUIAbrNBW4dgPC6 + qL3i6Nply00XZk6EtT+WvDQmKcrDtdkavSlOJmppCPZh6nhpHM5Mbo8lTO42Gxiwf4sTqg61oLz5 + yVQCpIuDN3/3UXrtGWgDPvh9cPLVdlZI4qp+Ns3KrJVqr0J3DnWdamZNNDTDbZWkK6wuNi1hbyh6 + YvWnnVbqp0VVSNYC0nLeB9OVpbbVU6PdaoTYNT1FbVCaLDJ3l0YG+PCI3G8G8zxUdn7glxRpglwy + +EUG52GMmzbc33Ck3797X9PZD7gVxRf5HCX65J9ZHXi8KcdRJstnWhyny0XemQClXlCwATtZQjL7 + VuOh/I6zfl5r6Ywp1DRbx/i8x8/3LsmDU2r5GB/u7aeP/Ld9ddjjgG8UXkdQGMJhXkkKOMEkJ7bk + DntfaLXGp7JTPvSJm3VcmM5atUadjaUlW551uTrHhKHGFpJSrrEKIAPKrbui4B7WOOigpXfWa3yn + YGYmwNwQSbHfYxTrj3QzxMs2cSTGMMzMhdQmP8Svobl36hU5R+YdW0i4CedW4QBvyjvUT3xZ7xbo + 2tb3nTyhx5kn7lsTZvrO409e6bVV2tstKGkNETQ1291cS48Ofb2ruvT9wf8AiH6kspMG5z5dJoru + C65K+F09c5icS7q2iogEtlYI7D2gH9kdzfcC+KL/ACOEf1xT6yOi8C5O8Z/LfCtIwZgPDerPC+F5 + FBalafT/AAlpiVTzE8qEjsG8ct9q3HQ/kdZvy819MV9NQU/Rjpqebf6+PX6Fit41Lqc1chor3a2w + lx3Q5ji4NzwDjv8AE45ldgP8A3ighJDkvhVYUlTZSMXNkKCrAg3X2Ec3xemOx9OPAJ144I1D5FYr + x5hXL6XwJS8aUOqVlLOImXFex8vPIcdIbTbm+9NjYXJvaKfUYW46AAK29aHJ/cgPzXb8cVq8OXC3 + FzTre0/OagXdUsrlAmqvLrqq07MGU6kSj5R1nMk7FxLY7e/4Yu9toaJ9THH0MreI5kY+RYHrfXOs + KKz1FSL1bHYZJ4MTXb7ss3TgCTng8M54+NTfaegNsFKbhN9r90b6NpJqSpDnIEgc57Bbf0+nvjdx + 0bgdS+E2Xf0+JSEIQRIQhBEhCEESEIQRIQhBEhCEESEIQRIQhBEhCEESEIQRbN4Ol02QCgDmB9MU + J8R2Zx7SNEOp6rZXzdepmP5bCFQmaU7TFLM2mYQ0tSSyEHm57gAW33ivJS7LIt3RSPrezsq+mzSx + ntn7RaXT63WMK4cnKtKSs2ohl9xCSQhYTuQSQPhijurh3o8E4GFk+he+RfaR1KwSSdJHhhIDXneG + ASQefJeecc8+KufKGYGsq5JJ3qFybnt8naMBnnxWD/2gayv/AKh9EXO/GbtV7aFtqyMyOcCSq6k+ + FdgPfdfbvGdHSb9V6CQnIvI4kemZIPvEL3jnFwtJJJrSCOYweC+8jbdrvO43RtHnq+Gj4j1OKtF4 + s1e8RTAj0lL44z61NYQfmUqMuiqVCbly8kEBRbDlrkbdkU5zdJzsz3r2IMbzFNzEzYxHNPAVSqBE + xPTDjgRy+W6kKseUDb6YrR4hHE4zV4ii8ufslZf4CwS9hvwtUs5R0Oc7xdCBZSlkm3kna9o5tw/u + LNm/w88A4xy9y4y6y3xdSa1W/Zt56speLzS+obZLaShQHKQwhXvqMY64xTVBj76JiHI/6reVqob9 + btNm5W7T8Ed0dwdC2RrG7ucH4QNweHHGOztXTNAzb4mGDqJScLYYxlq4oGH5GWblpKTYM8lthlKQ + EJSAhO1rf89n1lag+KSgArzN1ftgm3lOT47vei6EnpM2qdxxC2shciesFja00d+3+N2dvxRzfLPp + IGqHGOY+XmEKhkbklKSFZrlPkXHmfCedtD0yhtSkgqtcJUe+L+yO1YEffbg/OORwtEXJ2uII5K2o + 0bSOY0FziJY3HtJwwn2KzLiLVpxG8FtS01jHPXU1haUfcCGX6lPzbDbikp5rIWuwJsb2G8SfeELq + jz+xFwudZOc+Mc0sS47zEwq9XJqj1KtvqnXJRxmltuISS4TdsODm5ez3Xnj4/SggzMabdNUyJdlt + 44zeHWFoXUkyTptc72BCdvTHSnB4CjwZuIOEl19zqcQqWbe6V7CNk2A7rk+/Yxe7ZFWUN6dTNnc5 + gZw9C0ptDvdo1jsqpNS+5UNJI6rjYdwNPg9MGkZ3WnB68q2BRuOzxRazUJGiYfzIo1cq804pDErJ + 4WlnXXlbnlQhLdyUpA+BN47L+698aEmyW8RKHnGAkEH4Q1Fp7SBqHqGkrUVllqIpOHJLGFTw5NvT + TVNmXC2zNodlHmCCsBVtn+a9jukRIkT0orHqQAjSBglDYSByqxM4qxtvYeDbb90WGzX0yh3fVW9j + gTwDfnyt8bT9llNba2KHS2j6Ovgc0FzjJEwtPiIc0n2qj8cXzjSAWDeJbeb2gI9VGYcX/jT8pARi + cJt2e0BHqoq5V0ojH6if+iJgkD/OJz92jVb6URmDuEaQsEEgXI9sblwPOf4N2Rd23OmxxrpM/sj6 + y1T7x72XcNCUJd4umi+TCo8HF440IuEs4m7b/wBgCPVRj91540f8jif9QE+qisHxonMIduj/AAVY + 7j+qJ3cecfwbeHjReYP5oGCv1jd/doi90YDx7+k9UfSoI9H31wy3QlER++h+qqPjxeeNGQAWsUBI + N/7AUjf5KK/eGTxGOJzn5rOysy11Bispyunmp9VTL2E0SQPVyy1tjrOqBHlA94v2e/wdvpQ+YT90 + J0iYFTy+WebEjn7tFZmgDj34r1kaqsvtO9Y06YbwKxXGptz2Slq6t9UuWZdbwHIWU3KrBPbtE+0X + Wl7/AGMfWucc8AR/nmsO2o6K1BBpiqqKjRVJTx7knwrJIiWDHMBrckjmOKk8ygSlC0JQpACrb332 + jdxspFNmlKsRzG+/b5t/ijexv8HIznK+OjYwwboSEIR6okhCEESEIQRIQhBEhCEESEIQRIQhBEhC + EESEIQRIQhBFtXAvrTZJ5eXtvFqfjTVNNF4Z2rGaVMljrqGzJkK2B6+bZa5b+nni627dRUgbHlte + Le/Et0xYu1iaPs19OmBcUUvCeJK37GLaqE9zlllMtUpWaUkpSCTzJl1p+GLZfIXyUUscZwSCs52X + XGkpNTW+orZBHE2aMuOM+CHAk47B6eS85jR+xkjNalMn5TUi8w3kY7VgrEfWOuoQZXqVE3UyQv3R + bG2+/oMSgmcKdGJ6hsEYZZcGygmv142PePw5/ZFJSujKaoJnkebz0ybcbO/LyzAIvva/J/wjTPRj + tUKQEqztycKRsmzkzsPkvfjnaxWq60TS1sDH563DP0L7Z7W9rWzPVNbHWR6oqaMsbuhsEj42+Ut3 + Tx8mFV97VejDpUEpbw0o2/Cez1eBB+WjBWFejDj3bOGZhXnVXq966KQfFj9T35a8nv8AbmPVQ8WP + 1P8A5bMnv9uY9VF8JuR50cXqn6y1CajZ05m47WteQeJHTv5+oqtZrCPRkESzrsucNqWLED2bryuX + ftCeuINh3WMRnpVrLWX1v0FrJ6cW9lIMyZRGG1OrVYU/2RQWQCs81uSx8rfuMXqvFjtTy1hsZ0ZN + lfaCvwhSf93f9kc9y26NxqYwXmRl7iyp525RTVPpFbkKk6yjwjmdQzMIcUlJKBbZJigrqG61D4vg + WMDT/RGPlytjaL2qbN9NUldjUs9c+eItDZ3veAcHGPBHHzqsrpQhU/ph0wr5LA4xfV29/gKyY49w + AsaYOy+4aOrXHGO6UMQYQo1bqlUqlNDSHTPSrVKZccbCFkBXMGyLK2Mfc6UBMIldNOmaTcmWVvIx + jMk+WCLeBLSLfFe3pjjXAOby2c4ZGroZwrdYyvNYq/thUhToUim+xbSXykN+WVBAXskEnbtjJHgD + VRa94yWcPR1rnygijHc5U7HNcWOrRkMHhOb0/Dd6wepdMJ4w/B+BUHdANUcWFFPN7V6UOex5b/hv + RG7HGO4OqUlJ4e9RK+4nDFK9dHEHqP0YlHMV4gxsgi6rBuv7DcnbquztjKKL0YsqI9sONHACQSE1 + 42sbb/e4tYjnBPwtOMZ54z51mkGl9KPaALPe97ngdL8xXLmuMXwe+sJXw+KioWJsMMUrYec/fo4r + XOJRpbzccZm9G/Cgkc0JKlnwzEzk3QWG/AZIE8xR4Kl7yrA257AxvZKT6MTJTLL6MQ41faSeZTLj + deLaj6R1W/xxcryQ4rPBN044OGBclMf0zL/Ddwsy8rhOpguOcoT1i1FglarJSCVE9g27YqaCPpXF + 0lRAB2NaVj2qqG3W+JtVp/T93lmzgtmMzWAdfFriSfFwVuWS4w/CGQw2zWOHhOU+sJT/AAlhvDNL + PI4NlC5dSSQQRuAdo3f3Yng69/D7qgP+bNK9fHY2d2dnRwtRGLKzjjMCYap2JZ58zE3MUeh1iQVM + ukklxxLLQSVqJuVWuRYHsjpM0boxJN/bHjf5Kv8Aq4SMmBxHNAR+wFdLbpzT74GvrLNeo5SPCa0z + OaD4gSQSO3Cpo138SXhu6g9LOZeUGQWj2bytzMqvgCqfXHMP09nwQsz8s8sdY06VjnaaeR/pRRVw + XqqqmcTPSytohozFVnZdaxtzc8i+SAPSQYu1Lo3RiA2opxFjkud3kV+3+6jvjSnVOj5YP1JZJVfT + 5X8WIzoXiKSp2GlPIrfIqozTqZZpKi42EAKLwF1EAA3JtFvbaXzVkVS+piywjAaAFskbQbbYdIXG + yWuxXPdqI5N588b3YyzG9vOzhoxk+c9qlryakKD3IoKAWQQO4+aN5G0k0qDZK1cxO/be20buOhz5 + cr4dRMDW4CQhCPFMSEIQRIQhBEhCEESEIQRIQhBEhCEESEIQRIQhBEhCEEWzedQlwJUoA7GI73SN + NQ+YORulzKqSywzAxblziysY0atPUOoOSkyqWZlJkrRztkHlK1tXF+4eaJD02lKlIvcEEEW7VEd0 + QzOlJZhCazG0rZXNuBapSk1PEEwyFbIU44GW7/A0/v6DGH6/rHU9rfM3OerC6i7i3SEF72lWyiqm + h0e85zgRkYY0kZ4Y58/GrBOFdW/EHxnOTVKwTqN1WYqnZdHMtimYqqkw4hA2C1JS4SL2968c2Xnf + xUkEBeZ+uJJPnq9Z3/8AnjX4fXESx5w8sRZlYqy+y9wrjis4ilJSUWuqOuJEshpa3AUBII364i/b + sIuoL6TjqhVyp+wLk8AncFLkzv3+jzxoCzXBksYdNWvYfFu5x7eK+zm0Kw3ujupp7BpSkqKbqe58 + bXO7d0x5b5DlWpfs48VH8qGuH53rP1ofZy4qP5UNcXzvWfrRdc8Z21SEAHIXKE22/CTP0xh4zrqk + /IJlD8pM/TFzFVS/8Rf6v+JYK236569G0Xxsf2atS/Zx4qPb9lHXED/O9Z+vGBzw4p6rFWZ+t8n0 + 1as/Wi634zrqk/IJlD8pM/TDxnbVHe32BcoR/rJn6Ylmal3S33Rfx4/m/wCJRCg1yDvN0bRA/vY/ + s1ZbzIVrrzpp8jI5wNam80JKRdL0kxiA1KdbZdUhSeZAdSog9neBv2bkxJY4UGCMXYF4OGv2m41w + piTDk7MM4jdl26jJqYW4j2HR5QSoAkXPb6IpMT0nTVEVAnInJ+ySlXL1swARex3v6RF8XQRq0zA4 + tGhjUoxjugYUy0r0+mrYNl3ZArcl2m3pAfflJWdyFPrvY2skRftJ26imuLpYajp5Sw8wQfTlab7p + DU2taXTNPDe7JDQ0Ec8LnPjmDt3dkDuDGtHM81Bx0gZa5V5zajsscrs7swE5W5WViamEVau+FNy3 + gDbcs4tBLjnkgKdDSd/4xPYCRISZ4S3BpmG0ur4l1IU57hXNjGjEi23eT5r/AAxmR0YLHBZ5TrCy + 7WyAUhHsC4nnHcFcroH7Lxt2ui+43WlCDq6y8DoFl8tDc5b/AOL9+uB2ftiRYLJW0zTHJRtfxPEk + ZWbbUe6D0lqCsirbXquWhDG4LGwOI8uSwknzrfp4RnBhVYniZ0Rvzg4uo0ZF8I/gyJNk8TGhqT5x + i+jRt3ui844SoBWr3L5G3dQnPXRpeK942/PAwB8xL9dF29yZMnFuYc/3voK1u3avaG+C3XlQQP8A + ph9TK+kjhG8GJQuOJlRQP88KNA8I7gxDt4mNF/XCjR83xXzG47NXmAVf0Ev10YjoveNybfbe4BB/ + mJfrohbapwMCgbj9r71E3apagP5+1GP+2H1FvneEfwYkNKdPEvo5A/8AF1Gt8cc4yn4ZXB8y0zVy + yzLwvxKMLz+IcO4gp9dkWHcWUZYcmZZ9LyEkDf3TaY4Ex0XjH5UhyU1aYBm3EEhYTQHSUpNgdw6b + bc598CI1+feV1cyMzrzSyarjwm8QYRxLPYbem20KR4SuVmlsB5AUdknq0qG/Yra8Wq6VrLfGyWei + a1xdzGfbxWbaDtB1waqgsWtJ6iVjCXsdA0ZYfBOMt68r1laG61MSDM1LzaJ6WdSlxp5PKQ4gpBCg + U7EEWNx54+xFIGgXMxnOLRrptzKRNCdmKtg6lzMw4Cqxe8HQlex3B5kG4894q/joOlqBLE2UDGQC + vihqK0Pt9wnoZfzonuYeGOLSRy6uSQhCJ6syQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQ + gi+ZPcyVBf4RP8Ww29N/giO/r2z14ImMNRFdo+s96Ur2deG6Y1h6aKqbVHhJy/OuYSyFSyC2TeaW + Ta58qx7LCQRiyuSOG6NWcQVWZ8FpUhKOTsw4SAG2kIUVqJPcBv8ABHlB6gM06pnxnxm3mrPpdmKj + ibEtQrDDQb5lHnmCUN290SEKQOUdyRGtdpepzRQx0zWhxk8f+oXd/cJ7BY9YXStqqiqmpGUzBiSJ + +47eeeWSCMYByApT5zA6Mi31gOH5MkkcoFGrmybWA9x6IzN5idGNI++4elL/AMzVz6kU5YV6MfnZ + ifDVAxE5qNy4o652TZmlSj1MmS5KqWgKU2opIBKVKUL+iPv+K5Z0/nP5X/Nc39MYY03dox3lEe3d + HzldQy3XZaxzon6vuDS0kY6Z5xg45iLHoXeBzD6MXfbD0qB6KNXPqRh9kPoxn+D8t8zVz6kdIeK5 + Z0/nP5X/ADXN/TDxXLOn85/K/wCa5v6YlBl2/Uo/VCl+7Oyv+2Vw+Nk+yXd/2Q+jGfiCW+Zq79SP + t4axF0aHFmIaFhmiYalHqxUZxmQlEKpFbSl191YQhPMUWF1KAubDeKd09FwzqWeQansrvf8AYub+ + tHM8u+jMZxYGzEwFiye1LZYz0lSK1I1VxhNNmAp4MzCHCkFV7bIMTIormZmMdSRBp5+AMhW666g2 + ZRU0rqbWFwdIGksHSyY3uoH4JcL4/HD/ANKukzJnIjG2nrLKQy9q9VxM/T552XddX10uJJxwCy1E + Hywj/m8VI8B7CeK8ccLbWngnL91yWxtVp2u0+iLRN+DrTOO0pCWvvm3V+WpHlA7X7rRzTpQB8H0y + 6Z5ZTpdeTjWZST3W8Adt6BdKFG0dV8ErEmI8E8JLXdjTCdXfomJaQ/Xp6RmWzdcrMt0dtSFhKgU3 + Ckp7QeyIpKSGLUZicA1m5x3Rjq7FRG+3K87AqWorZ3TTvrGgPlLnH9MA3nxI8efMqCXeDnxoWyn+ + qnFhve1sx1WSLm1j1u+1j8MZ2+DfxpnE8ycS4rP/ALkq9bFHODOJ1xV8ysVULA+X2pTNbFuL6k64 + 1JU2nSEi69MqAUshCBLXNkJJ95JPdFWScxukRqSHEHVuWzexGGJex37f617IxtjbdMC6COZwzzDu + HyLoq/Uu0S1uZT3irtNMSBute14yPJvL6h4OHGnvb2y4t22/tkK9bGH3HDjT/wCEuLv/AIkK9bHz + xmP0iYjyPtuCPRheX/dox+yN0in/AM3X6ry/7tETaKjx+hn9b7lYDftWg8LnZAPIfnJW/PBw40vL + c4kxd22/tkK9bFNmqvQ/xRtGuV7mb2duN8w6RgxM61IPTUnjt+YWyty/JcNvXAJSRe1r2F9wIqIT + mL0icjlV9t4pPmGF5f8AdY6T1Ay3GzzaytxNhPUXhbUlirK1LSahUWKvhptEu22wpLocUtDCSnlU + hB9I5u0A2p5qOndG7o4pmuHjdw+QLItH3y+i504ulxs8kDnAPbG0l5H93jjPiyFVt0dvUxjpet/E + GVeZGYuNsUymJsJTjdPFWqz02ETUqtD9kJdUoXLXhBuO0IMUc8d/K85acSjOSoy7S5WRxRLyGI2S + U+StS5dLLhT3XKpZSz5yo98Wyskcwcz8qMz8F43yVrk7hLNOUmizRZyUU2VtvzCVMFJC0cu7b62j + fYc19rRWdr9yg4i9JewXm7r3ouMn3ptXsHR6zUlSi+u5Q48lkGWNgkBbpBULnfc7RRPu809jdT7r + nOY7y4C2iNmEVj2sM1LTVUNPFVwCMQlwY57gebW44jgFME6O1muxjnhu4GwzMTCnajhOs1OhOpJJ + UlBe8Kb+AInEJH+T6Ivth1Kr237ohrdF2zvYYrupbTpPzzSFusyGK5BouXUsIV4O+Up/0pa/nuIm + PMJIaVcEG5HvgbX/AGR0PoavFRa43ZOQMcexfEruxNHGx7SLpSsGI3yb7e3f8I+0lb5KuYXEZoyN + +4EZ4yxc0JCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCK1bxls6XskuH1qHrNPVMiuVWk + +16R6lC1L6ybPUm3KDbyFr37o82WXp9fpdUlalTZWtSM5LrD0tMNMK523BsVIVY7kj9keuhVaVL1 + BV5iWamhYWStKVC/cbKBFxHypbClDbQ4pVBozS1KKreDIuB6dvfPwxrjWez911lbMJMFvJdydy/3 + ZEWziyVNsNrFS6aTfLjLuk4GAMbh4DxZ5rzHhxFuIjLNtMyup3PZLQBISiecSEXJPLYJA2vb4Ifd + HuIz+c7n584O/Vj05kYbw2rmtQKMk33/AIOnf9kZ/azhz8RUb9HT9EWv3hV7fB7+cOzC2zJ3fOlt + 7+aNP6zD7TEvMW+6PcRn853Pz5wd+rD7o9xGfznc/PnB36senT7WcOfiKjfo6foh7WcOfiKjfo6f + ojz3i1/6+70KD+Hzpb+yNP6WfYrzGE8SDiMj/vO5+j+kHfojKviO8RY7/bNZ9rV6ag79WPTp9rOH + PxFRv0dP0Q9rOHPxFRv0dP0R43QVYDnv52fJ96iZ3fel28W6RpwfKz7FeVZnJqT1T6hqXS6Nnhmb + mXmdTJB1c1JS9VcW8ll0pKCpBI2VyrXEl/g+omZDg18QiUdlJxLrjOJFI5mlBax7DIO+26rHu84i + XY7hTD6lpWaFSFIAJCQwnc/FG8bo0kyz1DFNlUSi0FtbCUJS2UkG4Kbb3vbfbeK61bPJaec1b5uk + eRjwlhO1Pu56bUFij09R2dlOxksco3XjdHRvD8YDAMk88eheUtp4zfzb0uZyYKzzyypSWceYemX5 + mQFRkXXZZSlsuSywttKk3+9vzA98iLw6OkR8Sks8hoWSbQTewGFZsk77XPhHmtE9hrC+H/vq3KBR + 0uKWST4Okc3dfv8ARGt7WcOfiKjfo6foijodn9VSNMcVWWjxbuQPIsi1f3dmltSVTK++6WjqJWtD + QXy9XxeeecZKgOK6RJxLTbloWSqNu7Cs1+8Rl8Yj4l/4lyX/AFWmv3iJ8vtZw5+IqN+jp+iHtZw5 + +IqN+jp+iKtuj60DHfh9VYc3up9nwGPedD8Z/wDNQHUdIl4l6CVCh5Lk278LTX7xGzqfSD+IzV5K + bp9Tw1klUZSZR1L7S8LzaUraVcLT+HIJII7RawV5xE+72s4c/EVG/R0/RD2s4c/EVG/R0/REJ0bW + EFrqwkH+6o291XoBuS3R8IdwwRLgjHi+DXkgy1LrzUy1NylPrfXtLQtp4MOc6HSVEKKd/KvvsbC0 + TZ9czb+vLgW4Czjk5GZqmPKLQaNiuYlRLr61qflUBidbCBchQ5n/AHwARcEXkpu4WoKkkt0WioPc + fBkXB897QkpCTlmPY9lhpLBBbCUt2ShJ7Rbst6OyKiwbOTSwyNEu9vdntUe2Tu726jrLVcae2tpZ + rfKHs+E3i4cMszuDAOF5wPBizfqeQXEPyKrk0zUZOhV1xzClSKZfdUvONpDfWG17B9Ms5fu6s3j0 + l5V5lbCXWSAwvmUk7733v8ZMfC9rdFLnXootOS6lV03bTZBA2VYCwNwOyOQtpCGikXSLkADuH/O/ + wxf9G6ZNrgdAXFwJzxWje6e29x7Qryy996d7SNY1jgHb4IHEHkDniVu2jdJPdeNWNnJ25F25u3vj + eRlhXOLC0jLTkJCEI8UaQhCCJCEIIkIQgiQhCCJCEIIkIQgiQhCCJCEIIvnTa1pX5KinaM6VEtXU + bwhETQN0lWvfPTHitBsk83Z2xqwhEUX5oXjZ345lIQhExe9M/wAZSEIQTpn+MrUCEuIKFi4jQQlL + aw2hICb29+EIpJj4QVb0juiHFaqm0NkhIIuT3xlhCKoFSJ5nh5wSkIQj1Semf4ykIQgnTP8AGVqo + AtfvvGdSE7bdohCJEgGVcKZxPNaTfYe4XjOoXBPYfRCES3nwsKXSOJAJWaXvym5JPpjcQhE1w4qr + k/OSEIRCoEhCEESEIQRIQhBF/9k= + </pkg:binaryData> + </pkg:part> + <pkg:part pkg:name="/word/numbering.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"> + <pkg:xmlData> + <w:numbering xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" + xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" + xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" + xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" + xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" + mc:Ignorable="w14 wp14"> + <w:abstractNum w:abstractNumId="0"> + <w:nsid w:val="5E0E1B1B"/> + <w:multiLevelType w:val="singleLevel"/> + <w:tmpl w:val="5E0E1B1B"/> + <w:lvl w:ilvl="0" w:tentative="0"> + <w:start w:val="1"/> + <w:numFmt w:val="decimal"/> + <w:suff w:val="nothing"/> + <w:lvlText w:val="%1."/> + <w:lvlJc w:val="left"/> + </w:lvl> + </w:abstractNum> + <w:abstractNum w:abstractNumId="1"> + <w:nsid w:val="5E0E27B4"/> + <w:multiLevelType w:val="singleLevel"/> + <w:tmpl w:val="5E0E27B4"/> + <w:lvl w:ilvl="0" w:tentative="0"> + <w:start w:val="1"/> + <w:numFmt w:val="chineseCounting"/> + <w:suff w:val="nothing"/> + <w:lvlText w:val="%1、"/> + <w:lvlJc w:val="left"/> + </w:lvl> + </w:abstractNum> + <w:abstractNum w:abstractNumId="2"> + <w:nsid w:val="5E0E2FD0"/> + <w:multiLevelType w:val="singleLevel"/> + <w:tmpl w:val="5E0E2FD0"/> + <w:lvl w:ilvl="0" w:tentative="0"> + <w:start w:val="5"/> + <w:numFmt w:val="chineseCounting"/> + <w:suff w:val="nothing"/> + <w:lvlText w:val="%1、"/> + <w:lvlJc w:val="left"/> + </w:lvl> + </w:abstractNum> + <w:abstractNum w:abstractNumId="3"> + <w:nsid w:val="5E0E569B"/> + <w:multiLevelType w:val="singleLevel"/> + <w:tmpl w:val="5E0E569B"/> + <w:lvl w:ilvl="0" w:tentative="0"> + <w:start w:val="2"/> + <w:numFmt w:val="decimal"/> + <w:suff w:val="nothing"/> + <w:lvlText w:val="%1."/> + <w:lvlJc w:val="left"/> + </w:lvl> + </w:abstractNum> + <w:abstractNum w:abstractNumId="4"> + <w:nsid w:val="5E0E62C2"/> + <w:multiLevelType w:val="singleLevel"/> + <w:tmpl w:val="5E0E62C2"/> + <w:lvl w:ilvl="0" w:tentative="0"> + <w:start w:val="8"/> + <w:numFmt w:val="decimal"/> + <w:suff w:val="nothing"/> + <w:lvlText w:val="%1."/> + <w:lvlJc w:val="left"/> + </w:lvl> + </w:abstractNum> + <w:num w:numId="1"> + <w:abstractNumId w:val="1"/> + </w:num> + <w:num w:numId="2"> + <w:abstractNumId w:val="0"/> + </w:num> + <w:num w:numId="3"> + <w:abstractNumId w:val="2"/> + </w:num> + <w:num w:numId="4"> + <w:abstractNumId w:val="4"/> + </w:num> + <w:num w:numId="5"> + <w:abstractNumId w:val="3"/> + </w:num> + </w:numbering> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/settings.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"> + <pkg:xmlData> + <w:settings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" mc:Ignorable="w14"> + <w:zoom w:percent="140"/> + <w:embedSystemFonts/> + <w:bordersDoNotSurroundHeader w:val="0"/> + <w:bordersDoNotSurroundFooter w:val="0"/> + <w:documentProtection w:edit="forms" w:enforcement="0"/> + <w:defaultTabStop w:val="420"/> + <w:drawingGridVerticalSpacing w:val="156"/> + <w:displayHorizontalDrawingGridEvery w:val="1"/> + <w:displayVerticalDrawingGridEvery w:val="1"/> + <w:noPunctuationKerning w:val="1"/> + <w:characterSpacingControl w:val="compressPunctuation"/> + <w:hdrShapeDefaults> + <o:shapelayout v:ext="edit"> + <o:idmap v:ext="edit" data="8"/> + </o:shapelayout> + </w:hdrShapeDefaults> + <w:footnotePr> + <w:footnote w:id="0"/> + <w:footnote w:id="1"/> + </w:footnotePr> + <w:compat> + <w:spaceForUL/> + <w:balanceSingleByteDoubleByteWidth/> + <w:doNotLeaveBackslashAlone/> + <w:ulTrailSpace/> + <w:doNotExpandShiftReturn/> + <w:adjustLineHeightInTable/> + <w:doNotWrapTextWithPunct/> + <w:doNotUseEastAsianBreakRules/> + <w:useFELayout/> + <w:doNotUseIndentAsNumberingTabStop/> + <w:useAltKinsokuLineBreakRules/> + <w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" + w:val="14"/> + <w:compatSetting w:name="overrideTableStyleFontSizeAndJustification" + w:uri="http://schemas.microsoft.com/office/word" w:val="1"/> + <w:compatSetting w:name="enableOpenTypeFeatures" w:uri="http://schemas.microsoft.com/office/word" + w:val="1"/> + <w:compatSetting w:name="doNotFlipMirrorIndents" w:uri="http://schemas.microsoft.com/office/word" + w:val="1"/> + </w:compat> + <w:docVars> + <w:docVar w:name="commondata" w:val="eyJoZGlkIjoiMjg4OTg5Y2Q4ZTQwMTc5MjAyNmY3NzBlYWI0YTY5Y2MifQ=="/> + </w:docVars> + <w:rsids> + <w:rsidRoot w:val="7FBE809A"/> + <w:rsid w:val="00004E8B"/> + <w:rsid w:val="000C1C37"/> + <w:rsid w:val="00115CCF"/> + <w:rsid w:val="00190FCC"/> + <w:rsid w:val="001E1A79"/> + <w:rsid w:val="002D3169"/> + <w:rsid w:val="003B64AC"/> + <w:rsid w:val="003E7EE3"/> + <w:rsid w:val="00414849"/> + <w:rsid w:val="004209B2"/> + <w:rsid w:val="00440210"/> + <w:rsid w:val="0048180F"/> + <w:rsid w:val="00490EE1"/> + <w:rsid w:val="004A377D"/> + <w:rsid w:val="004C0ACE"/> + <w:rsid w:val="004C5C48"/> + <w:rsid w:val="0052154C"/> + <w:rsid w:val="00546844"/> + <w:rsid w:val="006200F6"/> + <w:rsid w:val="00635A54"/> + <w:rsid w:val="0064690C"/> + <w:rsid w:val="00667869"/> + <w:rsid w:val="006B2E90"/> + <w:rsid w:val="0071681A"/> + <w:rsid w:val="007A4DFA"/> + <w:rsid w:val="008C7B29"/> + <w:rsid w:val="008E2F71"/> + <w:rsid w:val="008F4150"/> + <w:rsid w:val="00941365"/> + <w:rsid w:val="00945E9D"/> + <w:rsid w:val="00953989"/> + <w:rsid w:val="009616A3"/> + <w:rsid w:val="009A6D9A"/> + <w:rsid w:val="00A3722B"/> + <w:rsid w:val="00A46EDF"/> + <w:rsid w:val="00AC3916"/> + <w:rsid w:val="00AE30F3"/> + <w:rsid w:val="00AF33FF"/> + <w:rsid w:val="00B057A6"/> + <w:rsid w:val="00B16730"/> + <w:rsid w:val="00B35F50"/> + <w:rsid w:val="00B47D02"/> + <w:rsid w:val="00B841A9"/> + <w:rsid w:val="00BD17F4"/> + <w:rsid w:val="00C20493"/> + <w:rsid w:val="00C4171D"/> + <w:rsid w:val="00C42427"/> + <w:rsid w:val="00C53AB3"/> + <w:rsid w:val="00CE6CA4"/> + <w:rsid w:val="00DD2240"/> + <w:rsid w:val="00E42201"/> + <w:rsid w:val="00E81A24"/> + <w:rsid w:val="00E9163F"/> + <w:rsid w:val="00EB0D87"/> + <w:rsid w:val="00EC3596"/> + <w:rsid w:val="00ED4E96"/> + <w:rsid w:val="00F07EC2"/> + <w:rsid w:val="00F611D4"/> + <w:rsid w:val="00F66D77"/> + <w:rsid w:val="00F91DC0"/> + <w:rsid w:val="00FB46CA"/> + <w:rsid w:val="00FE436A"/> + <w:rsid w:val="01877527"/> + <w:rsid w:val="07825ADA"/> + <w:rsid w:val="085E1022"/> + <w:rsid w:val="09662083"/> + <w:rsid w:val="09E16D97"/> + <w:rsid w:val="0C97325F"/> + <w:rsid w:val="0D6C6A48"/> + <w:rsid w:val="0DDA3A88"/> + <w:rsid w:val="0FC60B42"/> + <w:rsid w:val="10514032"/> + <w:rsid w:val="13F235CD"/> + <w:rsid w:val="166B0F91"/> + <w:rsid w:val="171267DB"/> + <w:rsid w:val="194671F7"/> + <w:rsid w:val="1C9F4611"/> + <w:rsid w:val="1D822D85"/> + <w:rsid w:val="214546F6"/> + <w:rsid w:val="25783B2B"/> + <w:rsid w:val="2DBA3C39"/> + <w:rsid w:val="2EA61615"/> + <w:rsid w:val="2FD03658"/> + <w:rsid w:val="353975D3"/> + <w:rsid w:val="36763E50"/> + <w:rsid w:val="36D52D7C"/> + <w:rsid w:val="377E4778"/> + <w:rsid w:val="37CE40C8"/> + <w:rsid w:val="392A0DF4"/> + <w:rsid w:val="3A5F4CF8"/> + <w:rsid w:val="3AAE3CEE"/> + <w:rsid w:val="3AFD1C48"/> + <w:rsid w:val="3DA34510"/> + <w:rsid w:val="3DBC1439"/> + <w:rsid w:val="3DCE16D7"/> + <w:rsid w:val="3F6D43FE"/> + <w:rsid w:val="3FED68E6"/> + <w:rsid w:val="41A32197"/> + <w:rsid w:val="41FE18EA"/> + <w:rsid w:val="422539DF"/> + <w:rsid w:val="43E474DD"/> + <w:rsid w:val="466B56EF"/> + <w:rsid w:val="46A34E9E"/> + <w:rsid w:val="54185F90"/> + <w:rsid w:val="57FBEC05"/> + <w:rsid w:val="58167AA9"/> + <w:rsid w:val="586A2D04"/> + <w:rsid w:val="58826FCB"/> + <w:rsid w:val="5AC34D0D"/> + <w:rsid w:val="5B0B39BE"/> + <w:rsid w:val="5F0A2970"/> + <w:rsid w:val="64D6385A"/> + <w:rsid w:val="66A42A15"/> + <w:rsid w:val="6E514CE0"/> + <w:rsid w:val="72062DC6"/> + <w:rsid w:val="72344BBF"/> + <w:rsid w:val="72477064"/> + <w:rsid w:val="7341326F"/> + <w:rsid w:val="74434F9E"/> + <w:rsid w:val="745B1693"/> + <w:rsid w:val="76036C01"/> + <w:rsid w:val="78162C22"/> + <w:rsid w:val="7A8073AC"/> + <w:rsid w:val="7AE17D20"/> + <w:rsid w:val="7EBF4AFD"/> + <w:rsid w:val="7FBE809A"/> + </w:rsids> + <m:mathPr> + <m:mathFont m:val="Cambria Math"/> + <m:brkBin m:val="before"/> + <m:brkBinSub m:val="--"/> + <m:smallFrac m:val="0"/> + <m:dispDef/> + <m:lMargin m:val="0"/> + <m:rMargin m:val="0"/> + <m:defJc m:val="centerGroup"/> + <m:wrapIndent m:val="1440"/> + <m:intLim m:val="subSup"/> + <m:naryLim m:val="undOvr"/> + </m:mathPr> + <w:themeFontLang w:val="en-US" w:eastAsia="zh-CN"/> + <w:clrSchemeMapping w:bg1="light1" w:t1="dark1" w:bg2="light2" w:t2="dark2" w:accent1="accent1" + w:accent2="accent2" w:accent3="accent3" w:accent4="accent4" w:accent5="accent5" + w:accent6="accent6" w:hyperlink="hyperlink" + w:followedHyperlink="followedHyperlink"/> + <w:doNotIncludeSubdocsInStats/> + </w:settings> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/styles.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"> + <pkg:xmlData> + <w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" + xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" + xmlns:v="urn:schemas-microsoft-com:vml" + xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" + xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" + xmlns:w10="urn:schemas-microsoft-com:office:word" + xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" + xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" mc:Ignorable="w14"> + <w:docDefaults> + <w:rPrDefault> + <w:rPr> + <w:rFonts w:asciiTheme="minorHAnsi" w:hAnsiTheme="minorHAnsi" + w:eastAsiaTheme="minorEastAsia" w:cstheme="minorBidi"/> + </w:rPr> + </w:rPrDefault> + <w:pPrDefault/> + </w:docDefaults> + <w:latentStyles w:count="260" w:defQFormat="0" w:defUnhideWhenUsed="1" w:defSemiHidden="1" + w:defUIPriority="99" w:defLockedState="0"> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Normal"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="heading 1"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:semiHidden="0" w:name="heading 2"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 3"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 4"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 5"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 6"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 7"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 8"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="heading 9"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 7"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 8"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index 9"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 7"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 8"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toc 9"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Normal Indent"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="footnote text"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="annotation text"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="header"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="footer"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="index heading"/> + <w:lsdException w:qFormat="1" w:uiPriority="0" w:name="caption"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="table of figures"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="envelope address"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="envelope return"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="footnote reference"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="annotation reference"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="line number"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="page number"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="endnote reference"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="endnote text"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="table of authorities"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="macro"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="toa heading"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Bullet"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Number"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Bullet 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Bullet 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Bullet 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Bullet 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Number 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Number 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Number 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Number 5"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Title"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Closing"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Signature"/> + <w:lsdException w:qFormat="1" w:uiPriority="1" w:semiHidden="0" w:name="Default Paragraph Font"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Body Text"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Body Text Indent"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Continue"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Continue 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Continue 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Continue 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="List Continue 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Message Header"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Subtitle"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Salutation"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Date"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Body Text First Indent"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Body Text First Indent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Note Heading"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Body Text 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Body Text 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Body Text Indent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Body Text Indent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Block Text"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Hyperlink"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="FollowedHyperlink"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Strong"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Emphasis"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Document Map"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Plain Text"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="E-mail Signature"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Normal (Web)"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Acronym"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Address"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Cite"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Code"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Definition"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Keyboard"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Preformatted"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Sample"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Typewriter"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="HTML Variable"/> + <w:lsdException w:qFormat="1" w:uiPriority="99" w:semiHidden="0" w:name="Normal Table"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="annotation subject"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Simple 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Simple 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Simple 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Classic 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Classic 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Classic 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Classic 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Colorful 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Colorful 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Colorful 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Columns 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Columns 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Columns 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Columns 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Columns 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 7"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid 8"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 7"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table List 8"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table 3D effects 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table 3D effects 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table 3D effects 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Contemporary"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Elegant"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Professional"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Subtle 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Subtle 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Web 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Web 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Web 3"/> + <w:lsdException w:qFormat="1" w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" + w:name="Balloon Text"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Grid"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="0" w:semiHidden="0" w:name="Table Theme"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" w:name="Light Shading"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" w:name="Light List"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" w:name="Light Grid"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" w:name="Medium Shading 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" w:name="Medium Shading 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" w:name="Medium List 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" w:name="Medium List 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" w:name="Medium Grid 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" w:name="Medium Grid 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" w:name="Medium Grid 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" w:name="Dark List"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" w:name="Colorful Shading"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" w:name="Colorful List"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" w:name="Colorful Grid"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" + w:name="Light Shading Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" + w:name="Light List Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" + w:name="Light Grid Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" + w:name="Medium Shading 1 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" + w:name="Medium Shading 2 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" + w:name="Medium List 1 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" + w:name="Medium List 2 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" + w:name="Medium Grid 1 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" + w:name="Medium Grid 2 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" + w:name="Medium Grid 3 Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" + w:name="Dark List Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" + w:name="Colorful Shading Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" + w:name="Colorful List Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" + w:name="Colorful Grid Accent 1"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" + w:name="Light Shading Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" + w:name="Light List Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" + w:name="Light Grid Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" + w:name="Medium Shading 1 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" + w:name="Medium Shading 2 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" + w:name="Medium List 1 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" + w:name="Medium List 2 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" + w:name="Medium Grid 1 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" + w:name="Medium Grid 2 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" + w:name="Medium Grid 3 Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" + w:name="Dark List Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" + w:name="Colorful Shading Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" + w:name="Colorful List Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" + w:name="Colorful Grid Accent 2"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" + w:name="Light Shading Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" + w:name="Light List Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" + w:name="Light Grid Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" + w:name="Medium Shading 1 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" + w:name="Medium Shading 2 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" + w:name="Medium List 1 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" + w:name="Medium List 2 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" + w:name="Medium Grid 1 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" + w:name="Medium Grid 2 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" + w:name="Medium Grid 3 Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" + w:name="Dark List Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" + w:name="Colorful Shading Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" + w:name="Colorful List Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" + w:name="Colorful Grid Accent 3"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" + w:name="Light Shading Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" + w:name="Light List Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" + w:name="Light Grid Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" + w:name="Medium Shading 1 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" + w:name="Medium Shading 2 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" + w:name="Medium List 1 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" + w:name="Medium List 2 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" + w:name="Medium Grid 1 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" + w:name="Medium Grid 2 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" + w:name="Medium Grid 3 Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" + w:name="Dark List Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" + w:name="Colorful Shading Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" + w:name="Colorful List Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" + w:name="Colorful Grid Accent 4"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" + w:name="Light Shading Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" + w:name="Light List Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" + w:name="Light Grid Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" + w:name="Medium Shading 1 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" + w:name="Medium Shading 2 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" + w:name="Medium List 1 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" + w:name="Medium List 2 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" + w:name="Medium Grid 1 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" + w:name="Medium Grid 2 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" + w:name="Medium Grid 3 Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" + w:name="Dark List Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" + w:name="Colorful Shading Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" + w:name="Colorful List Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" + w:name="Colorful Grid Accent 5"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="60" w:semiHidden="0" + w:name="Light Shading Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="61" w:semiHidden="0" + w:name="Light List Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="62" w:semiHidden="0" + w:name="Light Grid Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="63" w:semiHidden="0" + w:name="Medium Shading 1 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="64" w:semiHidden="0" + w:name="Medium Shading 2 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="65" w:semiHidden="0" + w:name="Medium List 1 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="66" w:semiHidden="0" + w:name="Medium List 2 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="67" w:semiHidden="0" + w:name="Medium Grid 1 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="68" w:semiHidden="0" + w:name="Medium Grid 2 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="69" w:semiHidden="0" + w:name="Medium Grid 3 Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="70" w:semiHidden="0" + w:name="Dark List Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="71" w:semiHidden="0" + w:name="Colorful Shading Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="72" w:semiHidden="0" + w:name="Colorful List Accent 6"/> + <w:lsdException w:unhideWhenUsed="0" w:uiPriority="73" w:semiHidden="0" + w:name="Colorful Grid Accent 6"/> + </w:latentStyles> + <w:style w:type="paragraph" w:default="1" w:styleId="1"> + <w:name w:val="Normal"/> + <w:next w:val="2"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:pPr> + <w:spacing w:before="100" w:beforeAutospacing="1" w:after="100" w:afterAutospacing="1"/> + </w:pPr> + <w:rPr> + <w:rFonts w:asciiTheme="minorHAnsi" w:hAnsiTheme="minorHAnsi" w:eastAsiaTheme="minorEastAsia" + w:cstheme="minorBidi"/> + <w:sz w:val="22"/> + <w:szCs w:val="22"/> + <w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA"/> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:styleId="3"> + <w:name w:val="heading 1"/> + <w:basedOn w:val="1"/> + <w:next w:val="1"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:pPr> + <w:keepNext/> + <w:keepLines/> + <w:outlineLvl w:val="0"/> + </w:pPr> + <w:rPr> + <w:rFonts w:asciiTheme="majorHAnsi" w:hAnsiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" + w:cstheme="majorBidi"/> + <w:b/> + <w:bCs/> + <w:color w:val="2E75B6" w:themeColor="accent1" w:themeShade="BF"/> + <w:sz w:val="28"/> + <w:szCs w:val="28"/> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:styleId="2"> + <w:name w:val="heading 2"/> + <w:basedOn w:val="1"/> + <w:next w:val="1"/> + <w:unhideWhenUsed/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:pPr> + <w:keepNext/> + <w:keepLines/> + <w:spacing w:before="260" w:after="260" w:line="416" w:lineRule="auto"/> + <w:outlineLvl w:val="1"/> + </w:pPr> + <w:rPr> + <w:rFonts w:ascii="Cambria" w:hAnsi="Cambria" w:eastAsia="宋体" w:cs="Times New Roman"/> + <w:b/> + <w:bCs/> + <w:sz w:val="32"/> + <w:szCs w:val="32"/> + </w:rPr> + </w:style> + <w:style w:type="character" w:default="1" w:styleId="10"> + <w:name w:val="Default Paragraph Font"/> + <w:unhideWhenUsed/> + <w:qFormat/> + <w:uiPriority w:val="1"/> + </w:style> + <w:style w:type="table" w:default="1" w:styleId="9"> + <w:name w:val="Normal Table"/> + <w:unhideWhenUsed/> + <w:qFormat/> + <w:uiPriority w:val="99"/> + <w:tblPr> + <w:tblCellMar> + <w:top w:w="0" w:type="dxa"/> + <w:left w:w="108" w:type="dxa"/> + <w:bottom w:w="0" w:type="dxa"/> + <w:right w:w="108" w:type="dxa"/> + </w:tblCellMar> + </w:tblPr> + </w:style> + <w:style w:type="paragraph" w:styleId="4"> + <w:name w:val="annotation text"/> + <w:basedOn w:val="1"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + </w:style> + <w:style w:type="paragraph" w:styleId="5"> + <w:name w:val="Balloon Text"/> + <w:basedOn w:val="1"/> + <w:link w:val="13"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:pPr> + <w:spacing w:before="0" w:after="0"/> + </w:pPr> + <w:rPr> + <w:sz w:val="18"/> + <w:szCs w:val="18"/> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:styleId="6"> + <w:name w:val="footer"/> + <w:basedOn w:val="1"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:pPr> + <w:tabs> + <w:tab w:val="center" w:pos="4153"/> + <w:tab w:val="right" w:pos="8306"/> + </w:tabs> + <w:snapToGrid w:val="0"/> + </w:pPr> + <w:rPr> + <w:sz w:val="18"/> + <w:szCs w:val="18"/> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:styleId="7"> + <w:name w:val="header"/> + <w:basedOn w:val="1"/> + <w:link w:val="12"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:pPr> + <w:pBdr> + <w:bottom w:val="single" w:color="auto" w:sz="6" w:space="1"/> + </w:pBdr> + <w:tabs> + <w:tab w:val="center" w:pos="4153"/> + <w:tab w:val="right" w:pos="8306"/> + </w:tabs> + <w:snapToGrid w:val="0"/> + <w:jc w:val="center"/> + </w:pPr> + <w:rPr> + <w:sz w:val="18"/> + <w:szCs w:val="18"/> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:styleId="8"> + <w:name w:val="Normal (Web)"/> + <w:basedOn w:val="1"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:rPr> + <w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:cs="宋体"/> + <w:sz w:val="24"/> + <w:szCs w:val="24"/> + </w:rPr> + </w:style> + <w:style w:type="character" w:styleId="11"> + <w:name w:val="annotation reference"/> + <w:basedOn w:val="10"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:rPr> + <w:sz w:val="21"/> + <w:szCs w:val="21"/> + </w:rPr> + </w:style> + <w:style w:type="character" w:customStyle="1" w:styleId="12"> + <w:name w:val="页眉 Char"/> + <w:basedOn w:val="10"/> + <w:link w:val="7"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:rPr> + <w:sz w:val="18"/> + <w:szCs w:val="18"/> + <w:lang w:eastAsia="en-US"/> + </w:rPr> + </w:style> + <w:style w:type="character" w:customStyle="1" w:styleId="13"> + <w:name w:val="批注框文本 Char"/> + <w:basedOn w:val="10"/> + <w:link w:val="5"/> + <w:qFormat/> + <w:uiPriority w:val="0"/> + <w:rPr> + <w:sz w:val="18"/> + <w:szCs w:val="18"/> + <w:lang w:eastAsia="en-US"/> + </w:rPr> + </w:style> + <w:style w:type="character" w:customStyle="1" w:styleId="14"> + <w:name w:val="Placeholder Text"/> + <w:basedOn w:val="10"/> + <w:unhideWhenUsed/> + <w:qFormat/> + <w:uiPriority w:val="99"/> + <w:rPr> + <w:color w:val="808080"/> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:customStyle="1" w:styleId="15"> + <w:name w:val="List Paragraph"/> + <w:basedOn w:val="1"/> + <w:unhideWhenUsed/> + <w:qFormat/> + <w:uiPriority w:val="99"/> + <w:pPr> + <w:ind w:firstLine="420" w:firstLineChars="200"/> + </w:pPr> + </w:style> + </w:styles> + </pkg:xmlData> + </pkg:part> + <pkg:part pkg:name="/word/theme/theme1.xml" + pkg:contentType="application/vnd.openxmlformats-officedocument.theme+xml"> + <pkg:xmlData> + <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office 主题"> + <a:themeElements> + <a:clrScheme name="Office"> + <a:dk1> + <a:sysClr val="windowText" lastClr="000000"/> + </a:dk1> + <a:lt1> + <a:sysClr val="window" lastClr="FFFFFF"/> + </a:lt1> + <a:dk2> + <a:srgbClr val="44546A"/> + </a:dk2> + <a:lt2> + <a:srgbClr val="E7E6E6"/> + </a:lt2> + <a:accent1> + <a:srgbClr val="5B9BD5"/> + </a:accent1> + <a:accent2> + <a:srgbClr val="ED7D31"/> + </a:accent2> + <a:accent3> + <a:srgbClr val="A5A5A5"/> + </a:accent3> + <a:accent4> + <a:srgbClr val="FFC000"/> + </a:accent4> + <a:accent5> + <a:srgbClr val="4472C4"/> + </a:accent5> + <a:accent6> + <a:srgbClr val="70AD47"/> + </a:accent6> + <a:hlink> + <a:srgbClr val="0563C1"/> + </a:hlink> + <a:folHlink> + <a:srgbClr val="954F72"/> + </a:folHlink> + </a:clrScheme> + <a:fontScheme name="Office"> + <a:majorFont> + <a:latin typeface="Calibri Light"/> + <a:ea typeface=""/> + <a:cs typeface=""/> + <a:font script="Jpan" typeface="MS ゴシック"/> + <a:font script="Hang" typeface="맑은 고딕"/> + <a:font script="Hans" typeface="宋体"/> + <a:font script="Hant" typeface="新細明體"/> + <a:font script="Arab" typeface="Times New Roman"/> + <a:font script="Hebr" typeface="Times New Roman"/> + <a:font script="Thai" typeface="Angsana New"/> + <a:font script="Ethi" typeface="Nyala"/> + <a:font script="Beng" typeface="Vrinda"/> + <a:font script="Gujr" typeface="Shruti"/> + <a:font script="Khmr" typeface="MoolBoran"/> + <a:font script="Knda" typeface="Tunga"/> + <a:font script="Guru" typeface="Raavi"/> + <a:font script="Cans" typeface="Euphemia"/> + <a:font script="Cher" typeface="Plantagenet Cherokee"/> + <a:font script="Yiii" typeface="Microsoft Yi Baiti"/> + <a:font script="Tibt" typeface="Microsoft Himalaya"/> + <a:font script="Thaa" typeface="MV Boli"/> + <a:font script="Deva" typeface="Mangal"/> + <a:font script="Telu" typeface="Gautami"/> + <a:font script="Taml" typeface="Latha"/> + <a:font script="Syrc" typeface="Estrangelo Edessa"/> + <a:font script="Orya" typeface="Kalinga"/> + <a:font script="Mlym" typeface="Kartika"/> + <a:font script="Laoo" typeface="DokChampa"/> + <a:font script="Sinh" typeface="Iskoola Pota"/> + <a:font script="Mong" typeface="Mongolian Baiti"/> + <a:font script="Viet" typeface="Times New Roman"/> + <a:font script="Uigh" typeface="Microsoft Uighur"/> + <a:font script="Geor" typeface="Sylfaen"/> + </a:majorFont> + <a:minorFont> + <a:latin typeface="Calibri"/> + <a:ea typeface=""/> + <a:cs typeface=""/> + <a:font script="Jpan" typeface="MS 明朝"/> + <a:font script="Hang" typeface="맑은 고딕"/> + <a:font script="Hans" typeface="宋体"/> + <a:font script="Hant" typeface="新細明體"/> + <a:font script="Arab" typeface="Arial"/> + <a:font script="Hebr" typeface="Arial"/> + <a:font script="Thai" typeface="Cordia New"/> + <a:font script="Ethi" typeface="Nyala"/> + <a:font script="Beng" typeface="Vrinda"/> + <a:font script="Gujr" typeface="Shruti"/> + <a:font script="Khmr" typeface="DaunPenh"/> + <a:font script="Knda" typeface="Tunga"/> + <a:font script="Guru" typeface="Raavi"/> + <a:font script="Cans" typeface="Euphemia"/> + <a:font script="Cher" typeface="Plantagenet Cherokee"/> + <a:font script="Yiii" typeface="Microsoft Yi Baiti"/> + <a:font script="Tibt" typeface="Microsoft Himalaya"/> + <a:font script="Thaa" typeface="MV Boli"/> + <a:font script="Deva" typeface="Mangal"/> + <a:font script="Telu" typeface="Gautami"/> + <a:font script="Taml" typeface="Latha"/> + <a:font script="Syrc" typeface="Estrangelo Edessa"/> + <a:font script="Orya" typeface="Kalinga"/> + <a:font script="Mlym" typeface="Kartika"/> + <a:font script="Laoo" typeface="DokChampa"/> + <a:font script="Sinh" typeface="Iskoola Pota"/> + <a:font script="Mong" typeface="Mongolian Baiti"/> + <a:font script="Viet" typeface="Arial"/> + <a:font script="Uigh" typeface="Microsoft Uighur"/> + <a:font script="Geor" typeface="Sylfaen"/> + </a:minorFont> + </a:fontScheme> + <a:fmtScheme name="Office"> + <a:fillStyleLst> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:lumMod val="110000"/> + <a:satMod val="105000"/> + <a:tint val="67000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="50000"> + <a:schemeClr val="phClr"> + <a:lumMod val="105000"/> + <a:satMod val="103000"/> + <a:tint val="73000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:lumMod val="105000"/> + <a:satMod val="109000"/> + <a:tint val="81000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:lin ang="5400000" scaled="0"/> + </a:gradFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:satMod val="103000"/> + <a:lumMod val="102000"/> + <a:tint val="94000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="50000"> + <a:schemeClr val="phClr"> + <a:satMod val="110000"/> + <a:lumMod val="100000"/> + <a:shade val="100000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:lumMod val="99000"/> + <a:satMod val="120000"/> + <a:shade val="78000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:lin ang="5400000" scaled="0"/> + </a:gradFill> + </a:fillStyleLst> + <a:lnStyleLst> + <a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:prstDash val="solid"/> + <a:miter lim="800000"/> + </a:ln> + <a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:prstDash val="solid"/> + <a:miter lim="800000"/> + </a:ln> + <a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:prstDash val="solid"/> + <a:miter lim="800000"/> + </a:ln> + </a:lnStyleLst> + <a:effectStyleLst> + <a:effectStyle> + <a:effectLst/> + </a:effectStyle> + <a:effectStyle> + <a:effectLst/> + </a:effectStyle> + <a:effectStyle> + <a:effectLst> + <a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"> + <a:srgbClr val="000000"> + <a:alpha val="63000"/> + </a:srgbClr> + </a:outerShdw> + </a:effectLst> + </a:effectStyle> + </a:effectStyleLst> + <a:bgFillStyleLst> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:solidFill> + <a:schemeClr val="phClr"> + <a:tint val="95000"/> + <a:satMod val="170000"/> + </a:schemeClr> + </a:solidFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:tint val="93000"/> + <a:satMod val="150000"/> + <a:shade val="98000"/> + <a:lumMod val="102000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="50000"> + <a:schemeClr val="phClr"> + <a:tint val="98000"/> + <a:satMod val="130000"/> + <a:shade val="90000"/> + <a:lumMod val="103000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:shade val="63000"/> + <a:satMod val="120000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:lin ang="5400000" scaled="0"/> + </a:gradFill> + </a:bgFillStyleLst> + </a:fmtScheme> + </a:themeElements> + <a:objectDefaults/> + </a:theme> + </pkg:xmlData> + </pkg:part> +</pkg:package> \ No newline at end of file diff --git a/ruoyi-applet/pom.xml b/ruoyi-applet/pom.xml new file mode 100644 index 0000000..13ffff9 --- /dev/null +++ b/ruoyi-applet/pom.xml @@ -0,0 +1,185 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <packaging>jar</packaging> + <artifactId>ruoyi-applet</artifactId> + + <description> + web服务入口 + </description> + + <dependencies> + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + <version>3.8.0</version> + </dependency> + <!-- spring-boot-devtools --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-devtools</artifactId> + <optional>true</optional> <!-- 表示依赖不会传递 --> + </dependency> + + <!-- swagger3--> + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-boot-starter</artifactId> + </dependency> + +<!-- <dependency>--> +<!-- <groupId>com.github.xiaoymin</groupId>--> +<!-- <artifactId>swagger-bootstrap-ui</artifactId>--> +<!-- <version>1.9.6</version>--> +<!-- </dependency>--> + + + <!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 --> + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-models</artifactId> + <version>1.6.2</version> + </dependency> + + <!-- Mysql驱动包 --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + </dependency> + + <!-- 核心模块--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-framework</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- 定时任务--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-quartz</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- 代码生成--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-generator</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + + <!-- zxing生成二维码 --> + <dependency> + <groupId>com.google.zxing</groupId> + <artifactId>core</artifactId> + <version>3.3.3</version> + </dependency> + + <dependency> + <groupId>com.google.zxing</groupId> + <artifactId>javase</artifactId> + <version>3.3.3</version> + </dependency> + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <version>5.1.3.RELEASE</version> + </dependency> +<!-- <dependency>--> +<!-- <groupId>org.apache.httpcomponents</groupId>--> +<!-- <artifactId>httpcore</artifactId>--> +<!-- <version>4.3.2</version>--> +<!-- </dependency>--> + + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp</artifactId> + <version>4.9.3</version> + </dependency> + + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>fastjson</artifactId> + <version>1.2.78</version> + </dependency> + + <!-- easypoi --> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-base</artifactId> + <version>3.0.3</version> + </dependency> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-web</artifactId> + <version>3.0.3</version> + </dependency> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-annotation</artifactId> + <version>3.0.3</version> + </dependency> + + <!-- 阿里云短信 --> + <dependency> + <groupId>com.aliyun</groupId> + <artifactId>dysmsapi20170525</artifactId> + <version>2.0.10</version> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>2.5.15</version> + <configuration> + <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 --> + </configuration> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>3.1.0</version> + <configuration> + <failOnMissingWebXml>false</failOnMissingWebXml> + <warName>${project.artifactId}</warName> + </configuration> + </plugin> + </plugins> + <finalName>${project.artifactId}</finalName> + </build> + +</project> \ No newline at end of file diff --git a/ruoyi-applet/src/main/java/com/ruoyi/RuoYiAppletApplication.java b/ruoyi-applet/src/main/java/com/ruoyi/RuoYiAppletApplication.java new file mode 100644 index 0000000..d7127b6 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/RuoYiAppletApplication.java @@ -0,0 +1,65 @@ +package com.ruoyi; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.client.RestTemplate; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 启动程序 + * + * @author ruoyi + */ +@Slf4j +@EnableScheduling//开启定时任务 +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +public class RuoYiAppletApplication +{ + public static void main(String[] args) { + + + ConfigurableApplicationContext application = SpringApplication.run(RuoYiAppletApplication.class, args); + try { + + Environment env = application.getEnvironment(); + log.info("\n----------------------------------------------------------\n\t" + + "应用 '{}' 运行成功! 访问连接:\n\t" + + "Swagger文档: \t\thttp://{}:{}/doc.html\n" + + "----------------------------------------------------------", + env.getProperty("spring.application.name", "后台"), + InetAddress.getLocalHost().getHostAddress(), + env.getProperty("server.port", "8081")); + }catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * 当不存在此 wxRestTemplate 使用此方法的bean注入 + * + * @return + */ + @Bean + @ConditionalOnMissingBean(name = "restTemplate") + public RestTemplate wxRestTemplate() { + //复杂构造函数的使用 + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + // 设置超时 + requestFactory.setConnectTimeout(6000); + requestFactory.setReadTimeout(6000); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setRequestFactory(requestFactory); + return restTemplate; + } + +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/RuoYiServletInitializer.java b/ruoyi-applet/src/main/java/com/ruoyi/RuoYiServletInitializer.java new file mode 100644 index 0000000..7c8401b --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/RuoYiServletInitializer.java @@ -0,0 +1,18 @@ +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(RuoYiAppletApplication.class); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/aspect/StateAspect.java b/ruoyi-applet/src/main/java/com/ruoyi/web/aspect/StateAspect.java new file mode 100644 index 0000000..20e5b87 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/aspect/StateAspect.java @@ -0,0 +1,45 @@ +package com.ruoyi.web.aspect; + +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +@Aspect +@Component + +public class StateAspect { + + @Autowired + private ISysUserService sysUserService; + + @Autowired + private ISysRoleService roleService; + @Pointcut("execution(* com.ruoyi.web.controller.manage.*.*(..)) && !execution( * com.ruoyi.web.controller.manage.LoginController.*.*(..))") + public void state(){} + + @Before("state()") + public void isfrozen(){ + System.err.println("进入切面"); + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser sysUser = sysUserService.selectUserById(loginUser.getUserId()); + if (sysUser.getStatus().equals("1")){ + throw new ServiceException("当前账号已停用"); + } + if (roleService.selectRoleByUserId(sysUser.getUserId()).getStatus()==1){ + throw new ServiceException("当前角色已停用"); + }; + + + } + + + } diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java new file mode 100644 index 0000000..d2d6e8c --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -0,0 +1,94 @@ +package com.ruoyi.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.google.code.kaptcha.Producer; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.sign.Base64; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CommonController.java new file mode 100644 index 0000000..cec5006 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -0,0 +1,163 @@ +package com.ruoyi.web.controller.common; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.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.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +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; +import com.ruoyi.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@RestController +@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") + 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") + public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + List<String> urls = new ArrayList<String>(); + List<String> fileNames = new ArrayList<String>(); + List<String> newFileNames = new ArrayList<String>(); + List<String> originalFilenames = new ArrayList<String>(); + 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 + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); + // 下载名称 + 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); + } + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java new file mode 100644 index 0000000..5390dc1 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisConfiguration.java @@ -0,0 +1,17 @@ +package com.ruoyi.web.controller.interceptor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MybatisConfiguration { + + /** + * 注册拦截器 + */ + @Bean + public MybatisInterceptor getMybatisInterceptor() { + return new MybatisInterceptor(); + } + +} \ No newline at end of file diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java new file mode 100644 index 0000000..57a4ff0 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java @@ -0,0 +1,121 @@ +package com.ruoyi.web.controller.interceptor; + +import com.ruoyi.framework.web.service.TokenService; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.util.*; + +@Slf4j +@Component +@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) }) +public class MybatisInterceptor implements Interceptor { + + @Autowired + private TokenService tokenService; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; + log.debug("{}:"+mappedStatement); + log.debug("------sqlId------" + mappedStatement.getId()); + if("com.ruoyi.system.mapper.SysLogininforMapper.insertLogininfor".equals(mappedStatement.getId())){ + return invocation.proceed(); + } + // sql类型:insert、update、select、delete + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + Object parameter = invocation.getArgs()[1]; + log.debug("------sqlCommandType------" + sqlCommandType); + + if (parameter == null) { + return invocation.proceed(); + } + + // 当sql为新增或更新类型时,自动填充操作人相关信息 + if (SqlCommandType.INSERT == sqlCommandType) { + + Field[] fields = getAllFields(parameter); + for (Field field : fields) { + try { + // 注入创建人 + if ("createBy".equals(field.getName())) { + // 获取当前登录用户信息 + if(Objects.nonNull(tokenService.getLoginUser())){ + String userName = tokenService.getLoginUser().getUser().getUserName(); + field.setAccessible(true); + field.set(parameter, userName); + field.setAccessible(false); + } + } + //注入创建时间 + if ("createTime".equals(field.getName())) { + field.setAccessible(true); +// field.set(parameter, new Date()); + field.setAccessible(false); + } + } catch (Exception e) { + log.error("failed to insert data, exception = ", e); + } + } + } + if (SqlCommandType.UPDATE == sqlCommandType) { + Field[] fields = getAllFields(parameter); + for (Field field : fields) { + try { + if ("updateBy".equals(field.getName())) { + // 获取当前登录用户信息 + if(Objects.nonNull(tokenService.getLoginUser())){ + String userName = tokenService.getLoginUser().getUser().getUserName(); + field.setAccessible(true); + field.set(parameter, userName); + field.setAccessible(false); + } + } + if ("updateTime".equals(field.getName())) { + field.setAccessible(true); +// field.set(parameter, new Date()); + field.setAccessible(false); + } + } catch (Exception e) { + log.error("failed to update data, exception = ", e); + } + } + } + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + // TODO Auto-generated method stub + } + + /** + * 获取类的所有属性,包括父类 + * + * @param object + * @return + */ + private Field[] getAllFields(Object object) { + Class<?> clazz = object.getClass(); + List<Field> fieldList = new ArrayList<>(); + while (clazz != null) { + fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); + clazz = clazz.getSuperclass(); + } + Field[] fields = new Field[fieldList.size()]; + fieldList.toArray(fields); + return fields; + } + +} \ No newline at end of file diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..ef8c5dd --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java @@ -0,0 +1,113 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysCache; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate<String, String> redisTemplate; + + private final static List<SysCache> caches = new ArrayList<SysCache>(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + } + + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Properties info = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback<Object>) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback<Object>) connection -> connection.dbSize()); + + Map<String, Object> result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List<Map<String, String>> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map<String, String> data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + + @GetMapping("/getNames") + public AjaxResult cache() + { + return AjaxResult.success(caches); + } + + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) + { + Set<String> cacheKeys = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(cacheKeys); + } + + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) + { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue); + return AjaxResult.success(sysCache); + } + + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) + { + Collection<String> cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } + + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) + { + redisTemplate.delete(cacheKey); + return AjaxResult.success(); + } + + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() + { + Collection<String> cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..60f09bb --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -0,0 +1,26 @@ +package com.ruoyi.web.controller.monitor; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/server") +public class ServerController +{ + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..eb748ba --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,77 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +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.framework.web.service.SysPasswordService; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { +// startPage(); + List<SysLogininfor> list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + +// @Log(title = "登录日志", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysLogininfor logininfor) +// { +// List<SysLogininfor> list = logininforService.selectLogininforList(logininfor); +// ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class); +// util.exportExcel(response, list, "登录日志"); +// } + + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return success(); + } + + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) + { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..4de770c --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,112 @@ +package com.ruoyi.web.controller.monitor; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.service.ISysOperLogService; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@Slf4j +@Api(tags = "操作日志记录") +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private ISysOperLogService operLogService; + +// @ApiOperation(value = "操作日志分页列表") +// @PostMapping("/list") +// public AjaxResult list(@RequestBody SysOperLogQuery query) +// { +// startPage(query.getPageNum(), query.getPageSize()); +// List<SysOperLogVO> list = operLogService.selectOperLogPageList(query); +// operLogService.getLogDetail(list); +// return AjaxResult.success(getDataTable(list)); +// } + + /** + * 查询操作日志列表导出 + */ +// @ApiOperation(value = "查询操作日志列表导出") +// @Log(title = "操作日志-查询操作日志列表导出", businessType = BusinessType.EXPORT) +// @PostMapping("/exportOperLog") +// public void exportOperLog(@RequestBody SysOperLogQuery query) +// { +// List<SysOperLogVO> list = operLogService.selectOperLogPageList(query); +// List<TOperLogExport> operLogExports = new ArrayList<>(); +// for (SysOperLogVO sysOperLogVO : list) { +// TOperLogExport operLogExport = new TOperLogExport(); +// BeanUtils.copyProperties(sysOperLogVO,operLogExport); +// operLogService.getLogDetail(list); +// operLogExports.add(operLogExport); +// } +// Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), TOperLogExport.class, operLogExports); +// HttpServletResponse response = WebUtils.response(); +// response.setContentType("application/vnd.ms-excel"); +// response.setCharacterEncoding("utf-8"); +// ServletOutputStream outputStream = null; +// try { +// String fileName = URLEncoder.encode("操作日志信息.xls", "utf-8"); +// response.setHeader("Content-Disposition", "attachment;filename=" + fileName); +// response.setContentType("application/vnd.ms-excel;charset=UTF-8"); +// response.setHeader("Pragma", "no-cache"); +// response.setHeader("Cache-Control", "no-cache"); +// outputStream = response.getOutputStream(); +// workbook.write(outputStream); +// } catch (IOException e) { +// e.printStackTrace(); +// log.error("操作日志导出信息导出失败!"); +// } finally { +// try { +// outputStream.close(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// } + +// @Log(title = "操作日志", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysOperLog operLog) +// { +// List<SysOperLog> list = operLogService.selectOperLogList(operLog); +// ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class); +// util.exportExcel(response, list, "操作日志"); +// } + + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteById/{operIds}") + public AjaxResult remove(@PathVariable String operIds) + { + String[] split = operIds.split(","); + List<Long> id = new ArrayList<>(); + for (String s : split) { + id.add(Long.valueOf(s)); + } + return AjaxResult.success(operLogService.deleteOperLogByIds(id)); + } + + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return success(); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..c13bcfc --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,81 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private RedisCache redisCache; + + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) + { + Collection<String> keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List<SysUserOnline> userOnlineList = new ArrayList<SysUserOnline>(); + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) + { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + else if (StringUtils.isNotEmpty(ipaddr)) + { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) + { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + else + { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); + return success(); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..706ccf2 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java @@ -0,0 +1,115 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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 + */ +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + +// @Log(title = "参数管理", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysConfig config) +// { +// List<SysConfig> list = configService.selectConfigList(config); +// ExcelUtil<SysConfig> util = new ExcelUtil<SysConfig>(SysConfig.class); +// util.exportExcel(response, list, "参数数据"); +// } + + /** + * 根据参数编号获取详细信息 + */ + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..990d0f7 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java @@ -0,0 +1,126 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.entity.SysDept; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @GetMapping("/list") + public AjaxResult list(SysDept dept) + { + List<SysDept> depts = deptService.selectDeptList(dept); + return AjaxResult.success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) + { + List<SysDept> depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return AjaxResult.success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return AjaxResult.success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) + { + if (!deptService.checkDeptNameUnique(dept)) + { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return AjaxResult.success(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody 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 error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return AjaxResult.success(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) + { + if (deptService.hasChildByDeptId(deptId)) + { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return AjaxResult.success(deptService.deleteDeptById(deptId)); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..23b0c23 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -0,0 +1,115 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictDataService; +import com.ruoyi.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController +{ + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) + { +// startPage(); + List<SysDictData> list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + +// @Log(title = "字典数据", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysDictData dictData) +// { +// List<SysDictData> list = dictDataService.selectDictDataList(dictData); +// ExcelUtil<SysDictData> util = new ExcelUtil<SysDictData>(SysDictData.class); +// util.exportExcel(response, list, "字典数据"); +// } + + /** + * 查询字典数据详细 + */ + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List<SysDictData> data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList<SysDictData>(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) + { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) + { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable Long[] dictCodes) + { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..1c5ab6b --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java @@ -0,0 +1,124 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +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 + */ +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) + { +// startPage(); + List<SysDictType> list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + +// @Log(title = "字典类型", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysDictType dictType) +// { +// List<SysDictType> list = dictTypeService.selectDictTypeList(dictType); +// ExcelUtil<SysDictType> util = new ExcelUtil<SysDictType>(SysDictType.class); +// util.exportExcel(response, list, "字典类型"); +// } + + /** + * 查询字典类型详细 + */ + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) + { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List<SysDictType> dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..13007eb --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java @@ -0,0 +1,29 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.StringUtils; + +/** + * 首页 + * + * @author ruoyi + */ +@RestController +public class SysIndexController +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() + { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..eda9119 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -0,0 +1,167 @@ +package com.ruoyi.web.controller.system; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.domain.model.LoginUserApplet; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.SmsUtil; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.web.controller.tool.MsgUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import com.ruoyi.common.constant.Constants; +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.domain.model.LoginBody; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author ruoyi + */ +@Api(tags = "登录") +@RestController +public class SysLoginController +{ + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + @Autowired + private RedisCache redisCache; + @Autowired + private TokenService tokenService; + @Autowired + private ISysRoleService roleService; + @Autowired + private MsgUtils msgUtils; + @Autowired + private SmsUtil smsUtil; + + /** + * 账号密码登录 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @ApiOperation(value = "账号密码登录",notes = "账号密码登录") + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + LoginUser loginUser = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, tokenService.createToken(loginUser)); + List<SysRole> roles = loginUser.getUser().getRoles(); + if(CollectionUtils.isEmpty(roles)){ + return AjaxResult.error("请关联角色!"); + } + if(roles.get(0).getStatus() == 1){ + return AjaxResult.error("该账号角色已被禁用!"); + } + + List<SysMenu> menus = roleService.roleInfoFromUserId(loginUser.getUserId()); + + ajax.put("menus",menus); + ajax.put("roleName",roles.get(0).getRoleName()); + ajax.put("userInfo",loginUser); + return ajax; + } + + /** + * 账号密码登录 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @ApiOperation(value = "短信登录",notes = "短信登录") + @PostMapping("/loginCode") + public AjaxResult loginCode(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + LoginUserApplet loginUser = loginService.loginCodeApplet(loginBody.getUsername(), loginBody.getCode()); + ajax.put(Constants.TOKEN, tokenService.createTokenApplet(loginUser)); + ajax.put("userInfo",loginUser); + return ajax; + } + + /** + * 获取验证码 + * + * @param phone 手机号 + * @return 结果 + */ + @ApiOperation(value = "获取验证码",notes = "获取验证码") + @GetMapping("/getCode") + public AjaxResult getCode(@RequestParam String phone) + { + // 发送验证码并存储到redis + if (StringUtils.hasLength(phone)) { + String code = String.valueOf((int) (Math.random() * 1000000)); + redisCache.setCacheObject(phone, code,5*60,TimeUnit.SECONDS); + try { + smsUtil.sendSms(phone, "2369926", new String[]{code}); + } catch (Exception e) { + throw new RuntimeException(e); + } + return AjaxResult.success("发送短信验证码成功!5分钟内有效"); + } + return AjaxResult.error(500, "发送短信验证码失败,请确认手机号码!"); + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("getInfo") + public AjaxResult getInfo() + { + SysUser user = SecurityUtils.getLoginUser().getUser(); + // 角色集合 + Set<String> roles = permissionService.getRolePermission(user); + // 权限集合 + Set<String> permissions = permissionService.getMenuPermission(user); + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..2b5e89c --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java @@ -0,0 +1,163 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +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.entity.SysMenu; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@Api(tags = "菜单信息") +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + @ApiOperation("菜单权限(有层级)") + @GetMapping("/levelList") + public AjaxResult levelList() + { + // 获取当前角色的菜单列表 + List<SysMenu> menus = menuService.selectList(); + if(menus.size()==0){ + return AjaxResult.success(new ArrayList<>()); + } + // 第三级 + List<SysMenu> s3 = menus.stream().filter(e -> e.getMenuType().equals("F")).collect(Collectors.toList()); + // 第二级 + List<SysMenu> s2 = menus.stream().filter(e -> e.getMenuType().equals("C")).collect(Collectors.toList()); + // 第一级 + List<SysMenu> s1 = menus.stream().filter(e -> e.getMenuType().equals("M")).collect(Collectors.toList()); + + for (SysMenu menu : s2) { + List<SysMenu> collect = s3.stream().filter(e -> e.getParentId().equals(menu.getMenuId())).collect(Collectors.toList()); + menu.setChildren(collect); + } + for (SysMenu menu : s1) { + List<SysMenu> collect = s2.stream().filter(e -> e.getParentId().equals(menu.getMenuId())).collect(Collectors.toList()); + menu.setChildren(collect); + } + + return AjaxResult.success(s1); + } + + /** + * 获取菜单列表 + */ + @GetMapping("/list") + public AjaxResult list(SysMenu menu) + { + List<SysMenu> menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return AjaxResult.success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List<SysMenu> menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List<SysMenu> menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return AjaxResult.success(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return AjaxResult.success(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) + { + if (menuService.hasChildByMenuId(menuId)) + { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return warn("菜单已分配,不允许删除"); + } + return AjaxResult.success(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..92a1d6b --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java @@ -0,0 +1,86 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) + { +// startPage(); + List<SysNotice> list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) + { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) + { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + */ + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable Long[] noticeIds) + { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysPostController.java new file mode 100644 index 0000000..a7a3e13 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysPostController.java @@ -0,0 +1,123 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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 + */ +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @GetMapping("/list") + public TableDataInfo list(SysPost post) + { +// startPage(); + List<SysPost> list = postService.selectPostList(post); + return getDataTable(list); + } + +// @Log(title = "岗位管理", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysPost post) +// { +// List<SysPost> list = postService.selectPostList(post); +// ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class); +// util.exportExcel(response, list, "岗位数据"); +// } + + /** + * 根据岗位编号获取详细信息 + */ + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) + { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List<SysPost> posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..be5af6a --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -0,0 +1,136 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +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.core.domain.model.LoginUser; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.MimeTypeUtils; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @GetMapping + public AjaxResult profile() + { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + return ajax; + } + + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) + { + LoginUser loginUser = getLoginUser(); + SysUser currentUser = loginUser.getUser(); + currentUser.setNickName(user.getNickName()); + currentUser.setEmail(user.getEmail()); + currentUser.setPhonenumber(user.getPhonenumber()); + currentUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserProfile(currentUser) > 0) + { + // 更新缓存用户信息 + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(String oldPassword, String newPassword) + { + LoginUser loginUser = getLoginUser(); + String userName = loginUser.getUsername(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) + { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return error("新密码不能与旧密码相同"); + } + if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) + { + // 更新缓存用户密码 + loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception + { + if (!file.isEmpty()) + { + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); + if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..fe19249 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java @@ -0,0 +1,38 @@ +package com.ruoyi.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysRegisterService; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author ruoyi + */ +@RestController +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) + { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..7769715 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java @@ -0,0 +1,304 @@ +package com.ruoyi.web.controller.system; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.system.dto.SysRoleDTO; +import com.ruoyi.system.query.SysRoleQuery; +import com.ruoyi.system.service.ISysMenuService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +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.SysDept; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.service.ISysDeptService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author ruoyi + */ +@Api(tags = "角色信息") +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + @Autowired + private ISysMenuService menuService; + + @ApiOperation(value = "角色列表") + @PostMapping("/list") + public AjaxResult list(@RequestBody SysRoleQuery query) + { + PageInfo<SysRole> list = roleService.selectPageList(query); + return AjaxResult.success(list); + } + + @ApiOperation(value = "角色列表不分页") + @PostMapping("/listNotPage") + public AjaxResult list() + { + List<SysRole> list = roleService.selectRoleList(new SysRole()); + return AjaxResult.success(list); + } + + @ApiOperation(value = "角色数量统计") + @PostMapping("/roleCount") + public AjaxResult roleCount() + { + int all = roleService.selectCount(null); + int normal = roleService.selectCount(0); + int stop = roleService.selectCount(1); + + Map<String,Integer> map = new HashMap<>(); + map.put("all",all); + map.put("normal",normal); + map.put("stop",stop); + return AjaxResult.success(map); + } + +// @Log(title = "角色管理", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysRole role) +// { +// List<SysRole> list = roleService.selectRoleList(role); +// ExcelUtil<SysRole> util = new ExcelUtil<SysRole>(SysRole.class); +// util.exportExcel(response, list, "角色数据"); +// } + + /** + * 根据角色编号获取详细信息 + */ + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return AjaxResult.success(roleService.selectRoleById(roleId)); + } + + + @ApiOperation("角色详情") + @GetMapping("/roleInfo") + public AjaxResult roleInfo(@RequestParam Long roleId) + { + SysRole role = roleService.selectRoleById(roleId); + RoleInfoVO roleInfoVo = new RoleInfoVO(); + roleInfoVo.setRoleId(role.getRoleId()); + roleInfoVo.setRoleName(role.getRoleName()); + + // 获取当前角色的菜单列表 + List<SysMenu> menus = menuService.selectListByRoleId(roleId); + if(menus.size()==0){ + return AjaxResult.success(new ArrayList<>()); + } + List<Long> menusId = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + + // 获取当前的权限菜单(有层级) + List<SysMenu> levelMenus = roleService.getMenuLevelList(menusId); + + roleInfoVo.setMenus(menusId); + return AjaxResult.success(roleInfoVo); + } + + + @ApiOperation("用户获取权限菜单") + @GetMapping("/roleInfoFromUserId") + public AjaxResult roleInfoFromUserId(@RequestParam Long userId) + { + return AjaxResult.success(roleService.roleInfoFromUserId(userId)); + } + + + /** + * 新增角色 + */ + @ApiOperation(value = "新增角色") + @Log(title = "角色信息-新增角色", businessType = BusinessType.INSERT) + @PostMapping("/add") + public AjaxResult add(@Validated @RequestBody SysRoleDTO dto) + { + Boolean flag= roleService.isExit(dto.getRoleId(),dto.getRoleName()); + if(flag){ + return error("新增角色'" + dto.getRoleName() + "'失败,角色名称已存在"); + } + roleService.saveRole(dto); + return AjaxResult.success(); + + } + + /** + * 修改保存角色 + */ + @ApiOperation(value = "编辑角色") + @Log(title = "角色信息-编辑角色", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRoleDTO dto) + { + Boolean flag= roleService.isExit(dto.getRoleId(),dto.getRoleName()); + if (flag){ + return error("修改角色'" + dto.getRoleName() + "'失败,角色名称已存在"); + } + if (roleService.editRole(dto) > 0) + { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) + { + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + tokenService.setLoginUser(loginUser); + } + return AjaxResult.success(); + } + return error("修改角色'" + dto.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return AjaxResult.success(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @ApiOperation(value = "状态修改") + @Log(title = "角色信息-角色状态修改", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) + { + role.setUpdateBy(getUsername()); + roleService.updateStatus(role); + return AjaxResult.success(); + } + + /** + * 删除角色 + */ + @ApiOperation(value = "删除角色") + @Log(title = "角色信息-角色删除角色", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteById/{ids}") + public AjaxResult remove(@PathVariable String ids) + { + String[] split = ids.split(","); + List<Long> id = new ArrayList<>(); + for (String s : split) { + id.add(Long.valueOf(s)); + } + return AjaxResult.success(roleService.deleteRoleByIds(id)); + } + + /** + * 获取角色选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return AjaxResult.success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { +// startPage(); + List<SysUser> list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { +// startPage(); + List<SysUser> list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) + { + return AjaxResult.success(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) + { + return AjaxResult.success(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) + { + roleService.checkRoleDataScope(roleId); + return AjaxResult.success(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysUserController.java new file mode 100644 index 0000000..f3465ce --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -0,0 +1,391 @@ +package com.ruoyi.web.controller.system; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +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.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.dto.SysUserUpdateStatusDTO; +import com.ruoyi.system.query.SysUserQuery; +import com.ruoyi.system.service.*; +import com.ruoyi.system.vo.SysUserVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 用户信息 + * + * @author ruoyi + */ +@Api(tags = "用户信息") +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + @Autowired + private TokenService tokenService; + + /** + * 获取用户列表 + */ + @ApiOperation(value = "获取用户列表") + @PostMapping("/list") + public AjaxResult list(@RequestBody SysUserQuery query) + { + PageInfo<SysUserVO> list = userService.pageList(query); + return AjaxResult.success(list); + } + + @ApiOperation(value = "获取用户列表-不分页") + @PostMapping("/listNotPage") + public AjaxResult listNotPage() + { + List<SysUser> list = userService.selectList(); + return AjaxResult.success(list); + } + + /** + * 获取用户黑名单列表 + */ +// @ApiOperation(value = "获取用户黑名单列表") +// @PostMapping("/blacklist") +// public AjaxResult blacklist(@RequestBody SysUserQuery query) +// { +// startPage(query.getPageNum(), query.getPageSize()); +// List<SysUserVO> list = userService.selectBlackPageList(query); +// return AjaxResult.success(getDataTable(list)); +// } + + /** + * 人员借用列表 + */ +// @ApiOperation(value = "人员借用列表") +// @GetMapping("/userBorrowList") +// public AjaxResult userBorrowList(@RequestParam(required = false) String name, +// @RequestParam(required = false) Integer type) +// { +// +// UserAddListVO userAddListVO = new UserAddListVO(); +// +// Long companyId = tokenService.getLoginUser().getUser().getCompanyId(); +// +// List<TCompany> companyList = new ArrayList<>(); +// List<TDept> deptList = new ArrayList<>(); +// List<SysUser> userList = new ArrayList<>(); +// // 查询公司 +// if(Objects.nonNull(type) && type == 1){ +// companyList = companyService.userAddListByCompanyName(name); +// } +// // 查询部门 +// if(Objects.nonNull(type) && type == 2){ +// deptList = tDeptService.userAddListByDeptName(name); +// } +// // 查询用户 +// if(Objects.nonNull(type) && type == 3){ +// userList = userService.selectListByNamePhone(name); +// } +// +// if(Objects.isNull(type)){ +// companyList = companyService.userAddListByCompanyName(name); +// deptList = tDeptService.userAddListByDeptName(name); +// userList = userService.selectListByNamePhone(name); +// } +// +// List<Long> companyIds = companyList.stream().map(TCompany::getId).collect(Collectors.toList()); +// List<Long> deptCompanyIds = deptList.stream().map(TDept::getCompanyId).collect(Collectors.toList()); +// List<Long> userCompanyIds = userList.stream().map(SysUser::getCompanyId).collect(Collectors.toList()); +// companyIds.addAll(deptCompanyIds); +// companyIds.addAll(userCompanyIds); +// +// companyIds = companyIds.stream().distinct().collect(Collectors.toList()); +// +// if(CollectionUtils.isEmpty(companyIds)){ +// return AjaxResult.success(userAddListVO); +// } +// SysUser user1 = tokenService.getLoginUser().getUser(); +// if(!user1.isAdmin()){ +// companyIds = companyIds.stream().filter(e->!e.equals(companyId)).collect(Collectors.toList()); +// } +// +// // 查询符合要求的公司 +// List<UserLevelVO> parent = companyService.userAddListByCompanyIds(companyIds); +// +// List<TDept> depts = tDeptService.selectList(); +// +// List<SysUser> sysUsers = userService.selectList(); +// +// for (UserLevelVO userLevelVO : parent) { +// +// // 找到公司下的部门 +// List<TDept> tDepts = depts.stream().filter(e -> userLevelVO.getKey().equals(e.getCompanyId())).collect(Collectors.toList()); +// List<UserLevelVO> children = new ArrayList<>(); +// // 封装部门 +// for (TDept dept : tDepts) { +// userLevelVO.setChildren(children); +// UserLevelVO userLevelVO1 = new UserLevelVO(); +// userLevelVO1.setKey(dept.getId()); +// userLevelVO1.setTitle(dept.getDeptName()); +// // 找到部门下的人员 +// List<SysUser> users; +// if(StringUtils.isNotEmpty(name) && type == 3){ +// users = sysUsers.stream().filter(e -> userLevelVO1.getKey().equals(e.getDeptId()) +// && ((StringUtils.isNotEmpty(e.getNickName()) && e.getNickName().contains(name))) +// || (StringUtils.isNotEmpty(e.getPhonenumber()) && e.getPhonenumber().contains(name))).collect(Collectors.toList()); +// }else { +// users = sysUsers.stream().filter(e -> userLevelVO1.getKey().equals(e.getDeptId())).collect(Collectors.toList()); +// } +// List<UserLevelVO> children1 = new ArrayList<>(); +// // 封装人员 +// for (SysUser user : users) { +// UserLevelVO userLevelVO2 = new UserLevelVO(); +// userLevelVO2.setKey(user.getUserId()); +// userLevelVO2.setTitle(user.getNickName()); +// userLevelVO2.setAvatar(user.getAvatar()); +// userLevelVO2.setFlag(true); +// children1.add(userLevelVO2); +// } +// userLevelVO1.setChildren(children1); +// +// children.add(userLevelVO1); +// } +// userLevelVO.setChildren(children); +// } +// userAddListVO.setUserLevelVOS(parent); +// userAddListVO.setUserList(sysUsers); +// return AjaxResult.success(userAddListVO); +// } + + /** + * 获取用户详情 + */ + @ApiOperation(value = "获取用户详情") + @GetMapping("/getDetail") + public AjaxResult getDetail(@RequestParam Long userId) + { + SysUser sysUser = userService.selectUserById(userId); + SysUserVO sysUserVO = new SysUserVO(); + BeanUtils.copyProperties(sysUser,sysUserVO); + + return AjaxResult.success(sysUser); + } + + + /** + * 获取用户数量统计 + */ + @ApiOperation(value = "获取用户数量统计") + @PostMapping("/getUserCount") + public AjaxResult getUserCount() + { + Map<String,Integer> map = new HashMap<>(); + + Integer userCountSum = userService.selectCount(null); + Integer normalCount = userService.selectCount(0);// 正常 + Integer stopCount = userService.selectCount(1);// 停用 + + map.put("all",userCountSum); + map.put("normal",normalCount); + map.put("stop",stopCount); + + return AjaxResult.success(map); + } + + /** + * 移除黑名单 + */ + @GetMapping("/removeBlackList") + public AjaxResult removeBlackList(@RequestParam String ids) + { + String[] split = ids.split(","); + List<Long> id = new ArrayList<>(); + for (String s : split) { + id.add(Long.valueOf(s)); + } + userService.updateUserIfBlack(id); + return AjaxResult.success(); + } + + +// @Log(title = "用户管理", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysUser user) +// { +// List<SysUser> list = userService.selectUserList(user); +// ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); +// util.exportExcel(response, list, "用户数据"); +// } + +// @Log(title = "用户管理", businessType = BusinessType.IMPORT) +// @PostMapping("/importData") +// public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception +// { +// ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); +// List<SysUser> userList = util.importExcel(file.getInputStream()); +// String operName = getUsername(); +// String message = userService.importUser(userList, updateSupport, operName); +// return AjaxResult.success(message); +// } + +// @PostMapping("/importTemplate") +// public void importTemplate(HttpServletResponse response) +// { +// ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); +// util.importTemplateExcel(response, "用户数据"); +// } + + + /** + * 新增用户 + */ + @ApiOperation(value = "新增用户管理") + @Log(title = "用户信息-新增用户", businessType = BusinessType.INSERT) + @PostMapping("/add") + public AjaxResult add(@Validated @RequestBody SysUser user) + { + user.setUserName(user.getPhonenumber()); + if (!userService.checkUserNameUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + userService.insertUser(user); + return AjaxResult.success(); + } + + /** + * 修改用户 + */ + @ApiOperation(value = "修改用户管理") + @Log(title = "用户信息-修改用户", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + user.setUserName(user.getPhonenumber()); +// userService.checkUserAllowed(user); +// userService.checkUserDataScope(user.getUserId()); + if (!userService.checkUserNameUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + + user.setUpdateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return AjaxResult.success(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @ApiOperation(value = "批量删除用户") + @Log(title = "用户信息-批量删除用户", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteById/{ids}") + public AjaxResult remove(@PathVariable String ids) + { + String[] split = ids.split(","); + List<Long> userIds = new ArrayList<>(); + for (String s : split) { + userIds.add(Long.valueOf(s)); + } + if (userIds.contains(getUserId())) + { + return error("当前用户不能删除"); + } + return AjaxResult.success(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @ApiOperation(value = "重置密码") + @Log(title = "用户信息-重置密码", businessType = BusinessType.UPDATE) + @PostMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); +// userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return AjaxResult.success(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @ApiOperation(value = "状态修改") + @Log(title = "用户信息-状态修改", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUserUpdateStatusDTO dto) + { + SysUser user = new SysUser(); + user.setUserId(dto.getUserId()); + user.setStatus(String.valueOf(dto.getStatus())); + user.setRemark(dto.getRemark()); + user.setUpdateBy(getUsername()); + return AjaxResult.success(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List<SysRole> roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + return AjaxResult.success(); + } + + /** + * 获取部门树列表 + */ + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) + { + return AjaxResult.success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java new file mode 100644 index 0000000..2da9a44 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/HttpClientUtil.java @@ -0,0 +1,79 @@ +package com.ruoyi.web.controller.tool; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * @author zhy + * @title: HttpClientUtil + * @projectName car_park + * @description: http连接工具类 + * @date 2019/10/2219:23 + */ +public class HttpClientUtil { + + + /** + * @param strUrl + * @return byte[] + * @throws + * @description: 获取网络图片转成字节流 + * @author zhy + * @date 2019/10/23 8:59 + */ + public static byte[] getImageFromNetByUrl(String strUrl) { + if (!isURL(strUrl)){ + return null; + } + try { + URL url = new URL(strUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(2 * 1000); + InputStream inStream = conn.getInputStream();// 通过输入流获取图片数据 + byte[] btImg = readInputStream(inStream);// 得到图片的二进制数据 + return btImg; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 从输入流中获取字节流数据 + * + * @param inStream 输入流 + * @return + * @throws Exception + */ + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[10240]; + int len = 0; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toByteArray(); + } + + public static boolean isURL(String str) { + str = str.toLowerCase(); + String regex = "^((https|http|ftp|rtsp|mms)?://)" + + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" + + "(([0-9]{1,3}\\.){3}[0-9]{1,3}" + + "|" + + "([0-9a-z_!~*'()-]+\\.)*" + + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\." + + "[a-z]{2,6})" + + "(:[0-9]{1,5})?" + + "((/?)|" + + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"; + return str.matches(regex); + } + + +} + \ No newline at end of file diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java new file mode 100644 index 0000000..e488f03 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/ImportExcelUtil.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.tool; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.core.domain.R; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.List; + +/** + * 导出返回信息 + */ +@Slf4j +public class ImportExcelUtil { + + /** + * @param errorLines 错误行数 + * @param successLines 成功行数 + * @param errorMessage 错误信息 + * @return + * @throws IOException + */ + public static R<String> importReturnMsg(int errorLines, int successLines, List<String> errorMessage) throws IOException { + if (errorLines == 0) { + return R.ok("共" + successLines + "行数据全部导入成功!"); + } else { + JSONObject result = new JSONObject(5); + int totalCount = successLines + errorLines; + result.put("totalCount", totalCount); + result.put("errorCount", errorLines); + result.put("errorMessage", errorMessage); + result.put("successCount", successLines); + result.put("msg", "总上传行数:" + totalCount + ",已导入行数:" + successLines + ",错误行数:" + errorLines); + return R.ok(JSON.toJSONString(result)); + } + } + +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java new file mode 100644 index 0000000..073f315 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgCodeUtil.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.controller.tool; + +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.system.code.SubmitTemplateReg; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; + +/** + * 短信工具类 + */ +public class MsgCodeUtil implements Serializable { + + /**接口账号用户名*/ + private static final String AP_ID = ""; + /**企业名称*/ + private static final String EC_NAME = ""; + /**签名*/ + private static final String SECRET_KEY = ""; + /**签名编码*/ + private static final String SIGN = ""; + /**模板ID*/ + private static final String TEMPLATE_ID = ""; + + + /** + * 实体封装 + * @param code + * @return + */ + public static SubmitTemplateReg getSubmitTemplateReg(String code,String mobiles) { + SubmitTemplateReg submitReg =new SubmitTemplateReg(); + String[] paramss = {code}; + submitReg.setApId(AP_ID); + submitReg.setEcName(EC_NAME); + submitReg.setSecretKey(SECRET_KEY); + submitReg.setParams(JSONObject.toJSONString(paramss)); + submitReg.setMobiles(mobiles); + submitReg.setAddSerial(""); + submitReg.setSign(SIGN); + submitReg.setTemplateId(TEMPLATE_ID); + submitReg.setMac(TEMPLATE_ID); + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(submitReg.getEcName( ));stringBuffer.append(submitReg.getApId()); + stringBuffer.append(submitReg.getSecretKey());stringBuffer.append(submitReg.getTemplateId());stringBuffer.append(submitReg.getMobiles()); + stringBuffer.append(submitReg.getParams());stringBuffer.append(submitReg.getSign());stringBuffer.append(submitReg.getAddSerial()); + submitReg.setMac(Hex.encodeHexString(stringBuffer.toString().getBytes(StandardCharsets.UTF_8))); + String regText = JSONObject.toJSONString(submitReg); + //加密 + String encode = Base64.encodeBase64String(regText.getBytes()); + System.err.println(encode); + return submitReg; + } + + public static void main(String[] args) { + getSubmitTemplateReg("123456","18398968484"); + } + +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java new file mode 100644 index 0000000..119a5ea --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MsgUtils.java @@ -0,0 +1,70 @@ +package com.ruoyi.web.controller.tool; + +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class MsgUtils { + + @Value("${code.config.accessKeyId}") + private String accessKeyId; + @Value("${code.config.accessKeySecret}") + private String accessKeySecret; + @Value("${code.config.signName}") + private String signName; + @Value("${code.config.templateCode}") + private String templateCode; + @Value("${code.config.signNameTest}") + private String signNameTest; + @Value("${code.config.templateCodeTest}") + private String templateCodeTest; + + /** + * 使用AK&SK初始化账号Client + * @param accessKeyId + * @param accessKeySecret + * @return Client + * @throws Exception + */ + public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception { + Config config = new Config() + // 您的 AccessKey ID + .setAccessKeyId(accessKeyId) + // 您的 AccessKey Secret + .setAccessKeySecret(accessKeySecret); + // 访问的域名 + config.endpoint = "dysmsapi.aliyuncs.com"; + return new com.aliyun.dysmsapi20170525.Client(config); + } + + public void sendMsg(String phone,String code) throws Exception { + com.aliyun.dysmsapi20170525.Client client = MsgUtils.createClient(accessKeyId,accessKeySecret); + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setSignName(signName) + .setTemplateCode(templateCode) + .setPhoneNumbers(phone) + .setTemplateParam("{\"code\":\""+code+"\"}"); + RuntimeOptions runtime = new RuntimeOptions(); + try { + // 复制代码运行请自行打印 API 的返回值 + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); + log.info("短信发送成功:{},{}",sendSmsResponse.getBody().getMessage(),sendSmsResponse.getStatusCode()); + } catch (TeaException error) { + // 如有需要,请打印 error + com.aliyun.teautil.Common.assertAsString(error.message); + log.info("短信发送失败:{}",error.message); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + // 如有需要,请打印 error + com.aliyun.teautil.Common.assertAsString(error.message); + log.info("短信发送失败:{}",error.message); + } + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MyFileUtil.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MyFileUtil.java new file mode 100644 index 0000000..40af28a --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/MyFileUtil.java @@ -0,0 +1,128 @@ +package com.ruoyi.web.controller.tool; + +import org.springframework.web.multipart.MultipartFile; +import java.io.*; +import java.nio.file.Files; + + +/** + * @author RainbowCloud + */ +public class MyFileUtil { + + /** + * 将 File 转换为 MultipartFile。 + * + * @param file 要转换的文件 + * @param fieldName 字段名,通常用于表单中的文件字段名 + * @return 转换后的 MultipartFile + * @throws IOException 如果发生I/O错误 + */ + public static MultipartFile fileToMultipartFile(File file, String fieldName) throws IOException { + try { + if (file == null || !file.exists()) { + throw new FileNotFoundException("文件未找到:" + file); + } + byte[] content = Files.readAllBytes(file.toPath()); + return new ByteArrayMultipartFile(content, file.getName(), fieldName, Files.probeContentType(file.toPath())); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // 删除临时文件 + file.delete(); + } + } + + /** + * 将 MultipartFile 转换为 File。 + * + * @param multipartFile 要转换的 MultipartFile + * @return 转换后的 File + * @throws IOException 如果发生I/O错误 + */ + public static File multipartFileToFile(MultipartFile multipartFile) throws IOException { + if (multipartFile.isEmpty()) { + throw new IOException("传入的MultipartFile为空"); + } + String originalFilename = multipartFile.getOriginalFilename(); + String tempFileSuffix = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf('.')) : ".tmp"; + File tempFile = File.createTempFile("temp", tempFileSuffix); + try (InputStream ins = multipartFile.getInputStream(); + OutputStream os = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = ins.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } + return tempFile; + } + + /** + * 内置一个简单的 MultipartFile 实现类,用于File转换 + */ + private static class ByteArrayMultipartFile implements MultipartFile { + private final byte[] content; + private final String name; + private final String originalFilename; + private final String contentType; + + /** + * 构造函数 + * + * @param content 文件内容 + * @param originalFilename 文件原始名字 + * @param name 字段名 + * @param contentType 文件类型 + */ + public ByteArrayMultipartFile(byte[] content, String originalFilename, String name, String contentType) { + this.content = content; + this.originalFilename = originalFilename; + this.name = name; + this.contentType = contentType; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getOriginalFilename() { + return this.originalFilename; + } + + @Override + public String getContentType() { + return this.contentType; + } + + @Override + public boolean isEmpty() { + return (this.content == null || this.content.length == 0); + } + + @Override + public long getSize() { + return this.content.length; + } + + @Override + public byte[] getBytes() { + return this.content; + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(this.content); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + try (OutputStream os = new FileOutputStream(dest)) { + os.write(this.content); + } + } + } + +} \ No newline at end of file diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/QRCodeUtil.java b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/QRCodeUtil.java new file mode 100644 index 0000000..9097589 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/QRCodeUtil.java @@ -0,0 +1,124 @@ +package com.ruoyi.web.controller.tool; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.ruoyi.common.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import javax.swing.filechooser.FileSystemView; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * 二维码工具 + * @Author:debug (SteadyJack) + * @Link: weixin-> debug0868 qq-> 1948831260 + * @Date: 2020/11/16 22:38 + **/ +public class QRCodeUtil { + private static final Logger log= LoggerFactory.getLogger(QRCodeUtil.class); + + //CODE_WIDTH:二维码宽度,单位像素 + private static final int CODE_WIDTH = 400; + //CODE_HEIGHT:二维码高度,单位像素 + private static final int CODE_HEIGHT = 400; + //FRONT_COLOR:二维码前景色,0x000000 表示黑色 + private static final int FRONT_COLOR = 0x000000; + //BACKGROUND_COLOR:二维码背景色,0xFFFFFF 表示白色 + //演示用 16 进制表示,和前端页面 CSS 的取色是一样的,注意前后景颜色应该对比明显,如常见的黑白 + private static final int BACKGROUND_COLOR = 0xFFFFFF; + + + public static void main(String[] args) { + createCodeToFile("5261548530",new File("C:\\Users\\Admin\\Desktop\\qrcode"),"5261548530.png"); + } + + public static void createCodeToFile(String content, File codeImgFileSaveDir, String fileName) { + try { + if (StringUtils.isBlank(content) || StringUtils.isBlank(fileName)) { + return; + } + content = content.trim(); + if (codeImgFileSaveDir==null || codeImgFileSaveDir.isFile()) { + //二维码图片存在目录为空,默认放在桌面... + codeImgFileSaveDir = FileSystemView.getFileSystemView().getHomeDirectory(); + } + if (!codeImgFileSaveDir.exists()) { + //二维码图片存在目录不存在,开始创建... + codeImgFileSaveDir.mkdirs(); + } + + //核心代码-生成二维码 + BufferedImage bufferedImage = getBufferedImage(content); + + File codeImgFile = new File(codeImgFileSaveDir, fileName); + ImageIO.write(bufferedImage, "png", codeImgFile); + + log.info("二维码图片生成成功:" + codeImgFile.getPath()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 生成二维码并输出到输出流, 通常用于输出到网页上进行显示,输出到网页与输出到磁盘上的文件中,区别在于最后一句 ImageIO.write + * write(RenderedImage im,String formatName,File output):写到文件中 + * write(RenderedImage im,String formatName,OutputStream output):输出到输出流中 + * @param content :二维码内容 + * @param outputStream :输出流,比如 HttpServletResponse 的 getOutputStream + */ + public static void createCodeToOutputStream(String content, OutputStream outputStream) { + try { + if (StringUtils.isBlank(content)) { + return; + } + content = content.trim(); + //核心代码-生成二维码 + BufferedImage bufferedImage = getBufferedImage(content); + + //区别就是这一句,输出到输出流中,如果第三个参数是 File,则输出到文件中 + ImageIO.write(bufferedImage, "png", outputStream); + + log.info("二维码图片生成到输出流成功..."); + } catch (Exception e) { + e.printStackTrace(); + } + } + + //核心代码-生成二维码 + private static BufferedImage getBufferedImage(String content) throws WriterException { + + //com.google.zxing.EncodeHintType:编码提示类型,枚举类型 + Map<EncodeHintType, Object> hints = new HashMap(); + + //EncodeHintType.CHARACTER_SET:设置字符编码类型 + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + + //EncodeHintType.ERROR_CORRECTION:设置误差校正 + //ErrorCorrectionLevel:误差校正等级,L = ~7% correction、M = ~15% correction、Q = ~25% correction、H = ~30% correction + //不设置时,默认为 L 等级,等级不一样,生成的图案不同,但扫描的结果是一样的 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + + //EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近 + hints.put(EncodeHintType.MARGIN, 1); + + MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); + BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints); + BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR); + for (int x = 0; x < CODE_WIDTH; x++) { + for (int y = 0; y < CODE_HEIGHT; y++) { + bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? FRONT_COLOR : BACKGROUND_COLOR); + } + } + return bufferedImage; + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java b/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java new file mode 100644 index 0000000..e27f2a5 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/DataUpdateHandlerConfig.java @@ -0,0 +1,61 @@ +package com.ruoyi.web.core.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.context.annotation.Configuration; + +/** + * @author xiaochen + * @ClassName DataUpdateInterceptor + * @Description 数据更新操作处理 + * @date 2021-12-15 + * <p> + * 注意,之前在此处注入了 JwtTokenUtils + * <p> + * 造成spring循环依赖,项目支棱不起来 + */ +@Slf4j +@Configuration +public class DataUpdateHandlerConfig implements MetaObjectHandler { + + /** + * 新增数据执行 + * + * @param metaObject + */ + @Override + public void insertFill(MetaObject metaObject) { + // 获取登录信息 +// String userName = SecurityUtils.getUsernameApplet(); +// if (StringUtils.isNotBlank(userName)) { +// this.setFieldValByName("createBy", userName, metaObject); +// this.setFieldValByName("updateBy", userName, metaObject); +// } else { +// this.setFieldValByName("createBy", userName, metaObject); +// this.setFieldValByName("updateBy", userName, metaObject); +// } + + } + + /** + * 修改数据执行 + * + * @param metaObject + */ + @Override + public void updateFill(MetaObject metaObject) { + // 获取登录信息 +// String userName = SecurityUtils.getUsernameApplet(); +// if (StringUtils.isNotBlank(userName)){ +// this.setFieldValByName("createBy", userName, metaObject); +// this.setFieldValByName("updateBy", userName, metaObject); +// } else { +// this.setFieldValByName("createBy", userName, metaObject); +// this.setFieldValByName("updateBy", userName, metaObject); +// } + + } +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java b/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java new file mode 100644 index 0000000..fac9a9f --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/MybatisPlusConfig.java @@ -0,0 +1,51 @@ +package com.ruoyi.web.core.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author liheng + * @ClassName MybatisPlusConfig + * @Description MybatisPlus相关配置 + * @date 2020-09-22 11:22、 + * 直接以实现类作为bean的注入(有事务管理的类) + * @EnableTransactionManagement(proxyTargetClass = true) + */ +@Configuration +public class MybatisPlusConfig { + private final DataUpdateHandlerConfig dataUpdateHandler; + + @Autowired + public MybatisPlusConfig(DataUpdateHandlerConfig dataUpdateHandler) { + this.dataUpdateHandler = dataUpdateHandler; + } + + /** + * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + /** + * 自动填充功能 + * + * @return + */ + @Bean + public GlobalConfig globalConfig() { + GlobalConfig globalConfig = new GlobalConfig(); + globalConfig.setMetaObjectHandler(dataUpdateHandler); + return globalConfig; + } + + +} diff --git a/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..b652bc3 --- /dev/null +++ b/ruoyi-applet/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -0,0 +1,125 @@ +package com.ruoyi.web.core.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.ruoyi.common.config.RuoYiConfig; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author ruoyi + */ +@Configuration +public class SwaggerConfig +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** 是否开启swagger */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** 设置请求的统一前缀 */ + @Value("${swagger.pathMapping}") + private String pathMapping; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() + { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List<SecurityScheme> securitySchemes() + { + List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List<SecurityContext> securityContexts() + { + List<SecurityContext> securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List<SecurityReference> defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List<SecurityReference> securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() + { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("标题:西藏国投管理系统_接口文档") + // 描述 + .description("西藏国投接口文档") + // 作者信息 + .contact(new Contact(ruoyiConfig.getName(), null, null)) + // 版本 + .version("版本号:" + ruoyiConfig.getVersion()) + .build(); + } +} diff --git a/ruoyi-applet/src/main/resources/META-INF/spring-devtools.properties b/ruoyi-applet/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..2b23f85 --- /dev/null +++ b/ruoyi-applet/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson.*.jar \ No newline at end of file diff --git a/ruoyi-applet/src/main/resources/application-prod.yml b/ruoyi-applet/src/main/resources/application-prod.yml new file mode 100644 index 0000000..10c4c27 --- /dev/null +++ b/ruoyi-applet/src/main/resources/application-prod.yml @@ -0,0 +1,230 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 3.8.6 + # 版权年份 + copyrightYear: 2023 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8082 + 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: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# Spring配置 +spring: + main: + allow-bean-definition-overriding: true + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 500MB + # 设置总上传的文件大小 + max-request-size: 2000MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # redis 配置 + redis: + # 地址 + # host: 127.0.0.1 + # # 端口,默认为6379 + # port: 6379 + # # 数据库索引 + # database: 0 + # # 密码 + # password: 123456 + host: 127.0.0.1 + # 端口,默认为6379 + port: 16379 + # 数据库索引 + database: 0 + # 密码 + password: 8f5z9g52gx4bg + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 数据源配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://172.27.0.13:3306/xizang?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai + username: xzgt + password: changyun!6f2gshj6h3j + # 从库数据源 + 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 +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 120 + +mybatis-plus: + # 此处在多数据源中生效 + config-location: classpath:/mybatis-config.xml + global-config: + banner: false + db-config: + logic-not-delete-value: 0 + logic-delete-value: 1 + type-aliases-package: com.ruoyi.**.domain,com.ruoyi.**.vo,com.ruoyi.**.model + # 指定Mapper文件位置 + mapper-locations: classpath*:mapper/**/*.xml + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: / + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* +# file upload +file: + upload: + location: /file/ + qrLocation: /file/qrCode/ + accessPath: /file/ + allowExt: .jpg|.png|.gif|.jpeg|.doc|.docx|.apk|.MP4|.mp4|.pdf|.PDF + url: + prefix: https://xzgt.test.591taxi.cn:${server.port}${server.servlet.context-path} +wx: + conf: + appId: wxe91f1af7638aa5dd + secretId: a787e1a462715604e0c9528b6d8960d1 +#OSS及短信配置 +code: + config: + templateCodeTest: "SMS_154950909" + signNameTest: "阿里云短信测试" + accessKeyId: LTAI5tAdba8HtT1C6UqtSxBt + accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8 + signName: "四川金达通信工程" + templateCode: "SMS_293985284" +cos: + client: + accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + bucket: xzgttest-1305134071 + bucketAddr: ap-chengdu + rootSrc: https://xzgttest-1305134071.cos.ap-chengdu.myqcloud.com/ + location: xizang +sms: + enable: true + appId: 1400957506 + secretid: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretkey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + sign: 畅云出行 diff --git a/ruoyi-applet/src/main/resources/application-test.yml b/ruoyi-applet/src/main/resources/application-test.yml new file mode 100644 index 0000000..40d4e3f --- /dev/null +++ b/ruoyi-applet/src/main/resources/application-test.yml @@ -0,0 +1,231 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: RuoYi + # 版本 + version: 3.8.6 + # 版权年份 + copyrightYear: 2023 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/ruoyi/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8082 + 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: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# Spring配置 +spring: + main: + allow-bean-definition-overriding: true + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 500MB + # 设置总上传的文件大小 + max-request-size: 2000MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # redis 配置 + redis: + # 地址 +# host: 127.0.0.1 +# # 端口,默认为6379 +# port: 6379 +# # 数据库索引 +# database: 0 +# # 密码 +# password: 123456 + host: xzgt.test.591taxi.cn + # 端口,默认为6379 + port: 16379 + # 数据库索引 + database: 0 + # 密码 + password: 8f5z9g52gx4bg + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms +# 数据源配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://xzgt.test.591taxi.cn:13306/xizang?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: 8f5z9g52gx4bg + # 从库数据源 + 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 +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 120 + +mybatis-plus: + # 此处在多数据源中生效 + config-location: classpath:/mybatis-config.xml + global-config: + banner: false + db-config: + logic-not-delete-value: 0 + logic-delete-value: 1 + type-aliases-package: com.ruoyi.**.domain,com.ruoyi.**.vo,com.ruoyi.**.model + # 指定Mapper文件位置 + mapper-locations: classpath*:mapper/**/*.xml + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: / + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* +# file upload +file: + upload: + location: /file/ + qrLocation: /file/qrCode/ + accessPath: /file/ + allowExt: .jpg|.png|.gif|.jpeg|.doc|.docx|.apk|.MP4|.mp4|.pdf|.PDF + url: +# prefix: http://localhost:${server.port}${server.servlet.context-path} + prefix: https://xzgt.test.591taxi.cn:${server.port}${server.servlet.context-path} +wx: + conf: + appId: wxe91f1af7638aa5dd + secretId: a787e1a462715604e0c9528b6d8960d1 +#OSS及短信配置 +code: + config: + templateCodeTest: "SMS_154950909" + signNameTest: "阿里云短信测试" + accessKeyId: LTAI5tAdba8HtT1C6UqtSxBt + accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8 + signName: "四川金达通信工程" + templateCode: "SMS_293985284" +cos: + client: + accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + bucket: xzgttest-1305134071 + bucketAddr: ap-chengdu + rootSrc: https://xzgttest-1305134071.cos.ap-chengdu.myqcloud.com/ + location: xizang +sms: + enable: true + appId: 1400957506 + secretid: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x + secretkey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU + sign: 畅云出行 diff --git a/ruoyi-applet/src/main/resources/application.yml b/ruoyi-applet/src/main/resources/application.yml new file mode 100644 index 0000000..dcc106d --- /dev/null +++ b/ruoyi-applet/src/main/resources/application.yml @@ -0,0 +1,4 @@ +# 项目相关配置 +spring: + profiles: + active: test diff --git a/ruoyi-applet/src/main/resources/banner.txt b/ruoyi-applet/src/main/resources/banner.txt new file mode 100644 index 0000000..0931cb8 --- /dev/null +++ b/ruoyi-applet/src/main/resources/banner.txt @@ -0,0 +1,24 @@ +Application Version: ${ruoyi.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/ruoyi-applet/src/main/resources/i18n/messages.properties b/ruoyi-applet/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..93de005 --- /dev/null +++ b/ruoyi-applet/src/main/resources/i18n/messages.properties @@ -0,0 +1,38 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] diff --git a/ruoyi-applet/src/main/resources/logback.xml b/ruoyi-applet/src/main/resources/logback.xml new file mode 100644 index 0000000..a360583 --- /dev/null +++ b/ruoyi-applet/src/main/resources/logback.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <!-- 日志存放路径 --> + <property name="log.path" value="/home/ruoyi/logs" /> + <!-- 日志输出格式 --> + <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /> + + <!-- 控制台输出 --> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + </appender> + + <!-- 系统日志输出 --> + <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${log.path}/sys-info.log</file> + <!-- 循环政策:基于时间创建日志文件 --> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- 日志文件名格式 --> + <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern> + <!-- 日志最大的历史 60天 --> + <maxHistory>60</maxHistory> + </rollingPolicy> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <!-- 过滤的级别 --> + <level>INFO</level> + <!-- 匹配时的操作:接收(记录) --> + <onMatch>ACCEPT</onMatch> + <!-- 不匹配时的操作:拒绝(不记录) --> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${log.path}/sys-error.log</file> + <!-- 循环政策:基于时间创建日志文件 --> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- 日志文件名格式 --> + <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern> + <!-- 日志最大的历史 60天 --> + <maxHistory>60</maxHistory> + </rollingPolicy> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <!-- 过滤的级别 --> + <level>ERROR</level> + <!-- 匹配时的操作:接收(记录) --> + <onMatch>ACCEPT</onMatch> + <!-- 不匹配时的操作:拒绝(不记录) --> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <!-- 用户访问日志输出 --> + <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${log.path}/sys-user.log</file> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <!-- 按天回滚 daily --> + <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern> + <!-- 日志最大的历史 60天 --> + <maxHistory>60</maxHistory> + </rollingPolicy> + <encoder> + <pattern>${log.pattern}</pattern> + </encoder> + </appender> + + <!-- 系统模块日志级别控制 --> + <logger name="com.ruoyi" level="info" /> + <!-- Spring日志级别控制 --> + <logger name="org.springframework" level="warn" /> + + <root level="info"> + <appender-ref ref="console" /> + </root> + + <!--系统操作日志--> + <root level="info"> + <appender-ref ref="file_info" /> + <appender-ref ref="file_error" /> + </root> + + <!--系统用户操作日志--> + <logger name="sys-user" level="info"> + <appender-ref ref="sys-user"/> + </logger> +</configuration> \ No newline at end of file diff --git a/ruoyi-applet/src/main/resources/mybatis-config.xml b/ruoyi-applet/src/main/resources/mybatis-config.xml new file mode 100644 index 0000000..53c5587 --- /dev/null +++ b/ruoyi-applet/src/main/resources/mybatis-config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> +<configuration> + + <settings> + <!-- 打印查询语句 不会写入到日志文件中--> + <setting name="logImpl" value="STDOUT_LOGGING"/> + <!--<setting name="logImpl" value="LOG4J" />--> + <!-- 控制全局缓存(二级缓存),按美团技术团队的说法,尽量别用缓存机制 emmmm.... --> + <setting name="cacheEnabled" value="true"/> + <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false --> + <!-- <setting name="lazyLoadingEnabled" value="true"/> --> + <setting name="mapUnderscoreToCamelCase" value="true"/><!--是否将map下划线方式转为驼峰式命名--> + <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖--> + <!-- <setting name="aggressiveLazyLoading" value="false"/>--> + <!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST --> + <!--<setting name="proxyFactory" value="CGLIB" />--> + <!-- 关于mybatis的一二级缓存 请参照:https://tech.meituan.com/2018/01/19/mybatis-cache.html --> + <!-- 一级缓存范围默认:SESSION ,此范围在复杂应用场景中可能会出现脏读数据--> + <!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 --> + <!--<setting name="localCacheScope" value="STATEMENT"/>--> + <setting name="localCacheScope" value="STATEMENT"/> + </settings> + +</configuration> diff --git a/ruoyi-applet/src/main/resources/mybatis/mybatis-config.xml b/ruoyi-applet/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/ruoyi-applet/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration +PUBLIC "-//mybatis.org//DTD Config 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-config.dtd"> +<configuration> + <!-- 全局参数 --> + <settings> + <!-- 使全局的映射器启用或禁用缓存 --> + <setting name="cacheEnabled" value="true" /> + <!-- 允许JDBC 支持自动生成主键 --> + <setting name="useGeneratedKeys" value="true" /> + <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 --> + <setting name="defaultExecutorType" value="SIMPLE" /> + <!-- 指定 MyBatis 所用日志的具体实现 --> + <setting name="logImpl" value="SLF4J" /> + <!-- 使用驼峰命名法转换字段 --> + <!-- <setting name="mapUnderscoreToCamelCase" value="true"/> --> + </settings> + +</configuration> diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml new file mode 100644 index 0000000..4d6fbd9 --- /dev/null +++ b/ruoyi-common/pom.xml @@ -0,0 +1,224 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>ruoyi-common</artifactId> + + <description> + common通用工具 + </description> + <properties> + <swagger.fox.version>3.0.0</swagger.fox.version> + <swagger.core.version>1.6.2</swagger.core.version> + </properties> + <dependencies> + <dependency> + <groupId>com.documents4j</groupId> + <artifactId>documents4j-local</artifactId> + <version>1.0.3</version> + </dependency> + <dependency> + <groupId>com.documents4j</groupId> + <artifactId>documents4j-transformer-msoffice-word</artifactId> + <version>1.0.3</version> + </dependency> + <dependency> + <groupId>org.freemarker</groupId> + <artifactId>freemarker</artifactId> + <version>2.3.33</version> + </dependency> + <!-- Spring框架基本的核心工具 --> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context-support</artifactId> + </dependency> + + <!-- SpringWeb模块 --> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + </dependency> + + <!-- spring security 安全认证 --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + + <!-- pagehelper 分页插件 --> +<!-- <dependency>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </dependency>--> + + <!-- 自定义验证注解 --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + + <!--常用工具类 --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + + <!-- JSON工具类 --> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + + <!-- 动态数据源 --> + <dependency> + <groupId>com.baomidou</groupId> + <artifactId>dynamic-datasource-spring-boot-starter</artifactId> + <version>3.5.2</version> + </dependency> + + <!-- 阿里JSON解析器 --> + <dependency> + <groupId>com.alibaba.fastjson2</groupId> + <artifactId>fastjson2</artifactId> + </dependency> + + <!-- io常用工具类 --> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + + <!-- excel工具 --> +<!-- <dependency>--> +<!-- <groupId>org.apache.poi</groupId>--> +<!-- <artifactId>poi-ooxml</artifactId>--> +<!-- </dependency>--> + + <!-- yml解析器 --> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + </dependency> + + <!-- Token生成与解析--> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + </dependency> + + <!-- Jaxb --> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + </dependency> + + <!-- redis 缓存操作 --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-redis</artifactId> + </dependency> + + <!-- pool 对象池 --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-pool2</artifactId> + </dependency> + + <!-- 解析客户端操作系统、浏览器等 --> + <dependency> + <groupId>eu.bitwalker</groupId> + <artifactId>UserAgentUtils</artifactId> + </dependency> + + <!-- servlet包 --> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + </dependency> + <!--mybatis-plus--> +<!-- <dependency>--> +<!-- <groupId>com.baomidou</groupId>--> +<!-- <artifactId>mybatis-plus-boot-starter</artifactId>--> +<!-- <version>3.5.2</version>--> +<!-- </dependency>--> + <dependency> + <groupId>com.baomidou</groupId> + <artifactId>mybatis-plus-boot-starter</artifactId> + <version>3.5.7</version> + </dependency> + <!--lombok--> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-swagger-ui</artifactId> + <version>3.0.0</version> + </dependency> + <dependency> + <groupId>com.github.xiaoymin</groupId> + <artifactId>knife4j-spring-boot-starter</artifactId> + <version>3.0.3</version> + </dependency> +<!-- <dependency>--> +<!-- <groupId>io.swagger</groupId>--> +<!-- <artifactId>swagger-models</artifactId>--> +<!-- <version>${swagger.core.version}</version>--> +<!-- </dependency>--> +<!-- <dependency>--> +<!-- <groupId>io.swagger</groupId>--> +<!-- <artifactId>swagger-annotations</artifactId>--> +<!-- <version>${swagger.core.version}</version>--> +<!-- </dependency>--> + + <dependency> + <groupId>ws.schild</groupId> + <artifactId>jave-all-deps</artifactId> + <version>2.5.1</version> + </dependency> + <dependency> + <groupId>com.tencentcloudapi</groupId> + <artifactId>tencentcloud-sdk-java</artifactId> + <version>4.0.11</version> + </dependency> + + <!-- 工作流--> + <dependency> + <groupId>com.aizuda</groupId> + <artifactId>flowlong-spring-boot-starter</artifactId> + <version>1.0.4</version> + </dependency> + + <!-- hutool--> + <dependency> + <groupId>cn.hutool</groupId> + <artifactId>hutool-all</artifactId> + <version>5.8.4</version> + </dependency> + <dependency> + <groupId>com.sun.mail</groupId> + <artifactId>javax.mail</artifactId> + <version>1.6.2</version> <!-- 请检查是否有更新的版本 --> + </dependency> + + <!--国密加密依赖--> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>1.68</version> + </dependency> + + <!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api --> + + </dependencies> + +</project> \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java new file mode 100644 index 0000000..1d6d4f4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +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 +{ +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java new file mode 100644 index 0000000..be49c80 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java @@ -0,0 +1,33 @@ +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 ""; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java new file mode 100644 index 0000000..79cd191 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +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; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java new file mode 100644 index 0000000..9b4411d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java @@ -0,0 +1,187 @@ +//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 +// */ +// 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 String[] combo() default {}; +// +// /** +// * 是否需要纵向合并单元格,应对需求:含有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); +// private final int value; +// +// ColumnType(int value) +// { +// this.value = value; +// } +// +// public int value() +// { +// return this.value; +// } +// } +//} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java new file mode 100644 index 0000000..fa5c430 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java @@ -0,0 +1,18 @@ +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 +{ +// public Excel[] value(); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java new file mode 100644 index 0000000..1eb8e49 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java @@ -0,0 +1,51 @@ +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 {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java new file mode 100644 index 0000000..0f024c7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +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.constant.CacheConstants; +import com.ruoyi.common.enums.LimitType; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java new file mode 100644 index 0000000..b769748 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +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; + +/** + * 自定义注解防止表单重复提交 + * + * @author ruoyi + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/basic/PageInfo.java b/ruoyi-common/src/main/java/com/ruoyi/common/basic/PageInfo.java new file mode 100644 index 0000000..9de94e9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/basic/PageInfo.java @@ -0,0 +1,70 @@ +package com.ruoyi.common.basic; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.io.Serializable; +import java.util.List; + +/** + * @Author liheng + * @Date 2019/08/26 10:28 AM + * @Description + */ +@JsonIgnoreProperties({"orders", "optimizeCountSql", "hitCount", "countId", "maxLimit", "searchCount"}) +public class PageInfo<T> extends Page<T> implements Serializable { + + private static final long serialVersionUID = 1L; + private boolean hasNextPage; + private boolean hasPrevPage; + private long startIndex; + + public PageInfo(long currentPage, long pageShowCount) { + super(currentPage, pageShowCount); + this.startIndex = super.offset(); + } + + public PageInfo() { + super(); + } + + private static boolean hasPrevPage(long currentPage) { + return currentPage != 1; + } + + @Override + public PageInfo<T> setRecords(List<T> records) { + super.setRecords(records); + this.hasNextPage = super.hasNext(); + this.hasPrevPage = super.hasPrevious(); + return this; + } + + public boolean getHasNextPage() { + return hasNextPage; + } + + public void setHasNextPage(boolean hasNextPage) { + this.hasNextPage = hasNextPage; + } + + public boolean getHasPrevPage() { + return hasPrevPage; + } + + public void setHasPrevPage(boolean hasPrevPage) { + this.hasPrevPage = hasPrevPage; + } + + private boolean hasNextPage(long currentPage, long totalPage) { + return currentPage < totalPage && totalPage != 0; + } + + public long getStartIndex() { + return startIndex; + } + + public void setStartIndex(long startIndex) { + this.startIndex = startIndex; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/FileUploadConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/FileUploadConfig.java new file mode 100644 index 0000000..32c0b2a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/FileUploadConfig.java @@ -0,0 +1,23 @@ +package com.ruoyi.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * <p>文件上传配置</p> + * + * @author mouseyCat + * @date 2020/10/13 16:10 + */ +@Data +@Component +@ConfigurationProperties(prefix = "file.upload") +public class FileUploadConfig { + private String accessPath; + private String allowExt; + private String location; + private String qrLocation; + private String fileUrlPrefix; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/MailProperties.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/MailProperties.java new file mode 100644 index 0000000..b3cc7e2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/MailProperties.java @@ -0,0 +1,32 @@ +package com.ruoyi.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "mail") +public class MailProperties { + + private String smtpHost="gz-smtp.qcloudmail.com"; + + private Integer smtpPort = 465; + + private String userAddr = "test@xzgtmail.591taxi.cn"; + + private String password = "CY20250226pass"; + + private String userName = "测试"; + + /** + * 账单提醒 ,同一个用户离上次发送短信的最小间隔 + * 单位分钟 + */ + private Integer billSmsDelayPeriod = 60; + /** + * 账单提醒 ,同一个用户离上次发送邮件的最小间隔 + * 单位分钟 + */ + private Integer billMailDelayPeriod = 60; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java new file mode 100644 index 0000000..00f70f6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -0,0 +1,135 @@ +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 String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 实例演示开关 */ + private boolean demoEnabled; + + /** 上传路径 */ + private static String profile; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + /** 验证码类型 */ + private static String captchaType; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + this.copyrightYear = copyrightYear; + } + + public boolean isDemoEnabled() + { + return demoEnabled; + } + + public void setDemoEnabled(boolean demoEnabled) + { + this.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 getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RuoYiConfig.captchaType = captchaType; + } + + /** + * 获取导入上传路径 + */ + 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"; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsProperties.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsProperties.java new file mode 100644 index 0000000..7a45914 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsProperties.java @@ -0,0 +1,38 @@ +package com.ruoyi.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "sms") +public class SmsProperties { + + public static final String ENABLE_KEY = "sms.enable"; + + private Boolean enable; + + private String appId; + + private String secretid; + + private String secretkey; + /** + * 短信签名 + */ + private String sign; + + /** + * 账单提醒 ,同一个用户离上次发送短信的最小间隔 + * 单位分钟 + */ + private Integer billSmsDelayPeriod = 60; + /** + * 账单提醒 ,同一个用户离上次发送邮件的最小间隔 + * 单位分钟 + */ + private Integer billMailDelayPeriod = 60; + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/WxConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/WxConfig.java new file mode 100644 index 0000000..770412a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/WxConfig.java @@ -0,0 +1,37 @@ +package com.ruoyi.common.config; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + + +/** + * 项目中需继承此类 + * + * @author lihen + */ +@Data +@Component +@ConfigurationProperties(prefix = "wx.config") +public class WxConfig { + + /** + * 获取 App ID + * + * @return App ID + */ + @JsonProperty("appId") + private String appId; + + /** + * 获取 Secret + * + * @return Secret + */ + @JsonProperty("secret") + private String secret; + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/AmountConstant.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/AmountConstant.java new file mode 100644 index 0000000..64c916f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/AmountConstant.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.constant; + +import java.math.BigDecimal; + +public class AmountConstant { + + public static final BigDecimal b100 = new BigDecimal("100"); + + public static final BigDecimal b1000 = new BigDecimal("1000"); + + public static final BigDecimal b001 = new BigDecimal("0.01"); + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java new file mode 100644 index 0000000..a3e4868 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.constant; + +/** + * 缓存的key 常量 + * + * @author ruoyi + */ +public class CacheConstants +{ + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; + + + public static final String BILL_UPDATE_LOCK_KEY = "bill_update_lock:"; + + + public static final String COMPLETE_PAY_LOCK_KEY = "complete_pay_lock:"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java new file mode 100644 index 0000000..82b912c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -0,0 +1,165 @@ +package com.ruoyi.common.constant; + +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * 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 Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + /** + * 小程序 + */ + public static final String LOGIN_USER_APPLET_KEY = "login_user_applet_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + 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:"; + /** + * LDAPS 远程方法调用 + */ + public static final String INFORMATION_VIEW = "information_view:"; + + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" }; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" }; + + /** + * 定时任务违规的字符 + */ + 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" }; + + /** + * 时间格式化 + */ + public static final String DATE_FORMATTER_TIME = "yyyy-MM-dd HH:mm:ss"; + + /** + * 用户类型 + */ + public static final String USER_TYPE = "用户类型"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/DictConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/DictConstants.java new file mode 100644 index 0000000..95169c0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/DictConstants.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.constant; + +/** + * 缓存的key 常量 + * + * @author ruoyi + */ +public class DictConstants +{ + /** + * 租户属性 + */ + public static final String DICT_TYPE_TENANT_ATTRIBUTE = "t_tenant_attribute"; + /** + * 租户类型 + */ + public static final String DICT_TYPE_TENANT_TYPE = "t_tenant_type"; + /** + * 租赁状态 1=待出租 2=已出租 3=维修中 + */ + public static final String DICT_TYPE_LEASE_STATUS = "t_lease_status"; + /** + * 租金支付方式 1=月付 季付 年付 + */ + public static final String DICT_TYPE_CONTRACT_PAY_TYPE = "t_contract_pay_type"; + /** + * 业务属性 1住宅2商业3工业4车位5办公6仓储 + */ + public static final String DICT_TYPE_BUSINESS_ATTRIBUTES = "t_business_attributes"; + /** + * 合同状态 1=待提交 2=待审批 3=未签订 4=已签订 5=已驳回 6=已终止 7=待结算 8=已结算 9合同已签订待审 + */ + public static final String DICT_TYPE_CONTRACT_STATUS = "t_contract_status"; + /** + * 验收状态 1=待验收 2=已验收 + */ + public static final String DICT_TYPE_CHECK_STATUS = "t_check_status"; + /** + * 缴费状态 1=未缴费 2=待确认 3=已缴费 4=已逾期 5=已失效 + */ + public static final String DICT_TYPE_PAY_FEES_STATUS = "t_pay_fees_status"; + /** + * 账单类型 1=租金 2=押金 3=生活费用 4=房屋验收 + */ + public static final String DICT_TYPE_BILL_TYPE = "t_bill_type"; + /** + * 验收记录情况 1=良好 2=一般 3=较差 + */ + public static final String DICT_TYPE_CHECK_SITUATION = "t_check_situation"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java new file mode 100644 index 0000000..7d899d4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java @@ -0,0 +1,117 @@ +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", "children" }; + + /** 文本框 */ + 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_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** 富文本控件 */ + public static final String HTML_EDITOR = "editor"; + + /** 字符串类型 */ + 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"; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java new file mode 100644 index 0000000..a983c77 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.constant; + +/** + * 返回状态码 + * + * @author ruoyi + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java new file mode 100644 index 0000000..62ad815 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +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; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java new file mode 100644 index 0000000..96b149c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java @@ -0,0 +1,78 @@ +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_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 static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验是否唯一的返回标识 */ + 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; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/WxConstant.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/WxConstant.java new file mode 100644 index 0000000..3e54fb9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/WxConstant.java @@ -0,0 +1,22 @@ +package com.ruoyi.common.constant; + +/** + * @Description 对接微信小程序的常量 + * @Author xiaochen + * @Date 2021/10/19/01914:01 + */ +public class WxConstant { + + /** + * 生成小程序码地址 + */ + public static final String CREATE_CODE_URL = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=ACCESS_TOKEN"; + + /** + * 高德地图坐标转换 + */ + public static final String ADDRESS_CONVERT_TO_COORDINATE = "https://restapi.amap.com/v3/geocode/geo?key=KEY&address=ADDRESS"; + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java new file mode 100644 index 0000000..7497d8c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java @@ -0,0 +1,205 @@ +package com.ruoyi.common.core.controller; + +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; + +/** + * 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 startPage(Integer pageNum,Integer pageSize) +// { +// PageUtils.startPage(pageNum,pageSize); +// } + + /** + * 设置请求排序数据 + */ + 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(); +// } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TableDataInfo getDataTable(List<?> list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + rspData.setRecords(list); +// rspData.setTotal(new PageInfo(list).getTotal()); + return rspData; + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(Object data) + { + return AjaxResult.success(data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } + + /** + * 返回警告消息 + */ + public AjaxResult warn(String message) + { + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + */ + public LoginUser getLoginUser() + { + return SecurityUtils.getLoginUser(); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() + { + return getLoginUser().getUsername(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/FileController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/FileController.java new file mode 100644 index 0000000..c02d68a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/FileController.java @@ -0,0 +1,140 @@ +package com.ruoyi.common.core.controller; + +import com.ruoyi.common.config.FileUploadConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 文件上传控制类 + * + * @author junelee + * @date 2020/3/20 20:21 + */ +@Api(tags = "服务器文件上传") +@RestController +@CrossOrigin +@RequestMapping("/file/") +public class FileController { + + @Autowired + private FileUploadConfig fileUploadConfig; + + + + @ApiOperation(value = "单文件上传", notes = "单文件上传,rename 默认不重命名") + @PostMapping(value = "upload", headers = "content-type=multipart/form-data") + public AjaxResult uploadImageMany(@RequestParam(value = "file") MultipartFile mf) throws IOException { + if (mf.isEmpty()) { + return AjaxResult.error("请传入文件!"); + } + String TimeDir =new SimpleDateFormat("yyyy-MM-dd").format(new Date()); +// String realPath = fileUploadConfig.getLocation() + TimeDir; + String realPath = "C:\\Users\\Admin\\Desktop\\qrcode\\" + TimeDir; + File file = new File(realPath); + // 没有目录就创建 + if (!file.exists()) { + file.mkdirs(); + } + // 获取文件名称 + String filename = mf.getOriginalFilename(); + // 获取文件后缀 + String ext = filename.substring(filename.lastIndexOf("."), filename.length()); + // 检查文件类型 + if (!fileUploadConfig.getAllowExt().contains(ext)) { + return AjaxResult.error("上传文件格式不正确,仅支持" + fileUploadConfig.getAllowExt()); + } + File targetFile = new File(realPath, filename);//目标文件 + //开始从源文件拷贝到目标文件 + //传图片一步到位 + mf.transferTo(targetFile); + //拼接数据 +// String imgstr = fileUploadConfig.getAccessPath() + TimeDir +"\\"+ filename; + String imgstr = TimeDir +"/"+ filename; + return AjaxResult.success(imgstr); + } + + +// @ApiOperation(value = "单文件上传", notes = "单文件上传,rename 默认不重命名") +// @PostMapping(value = "strUpload", headers = "content-type=multipart/form-data") +// public String strUpload(@RequestParam(value = "file") MultipartFile mf,@RequestParam(value = "fileName")String fileName) throws IOException { +// if (mf.isEmpty()) { +// return "请传入文件!"; +// } +// String TimeDir =new SimpleDateFormat("yyyy-MM-dd").format(new Date()); +// String realPath = fileUploadConfig.getQrLocation() + TimeDir; +// File file = new File(realPath); +// // 没有目录就创建 +// if (!file.exists()) { +// file.mkdirs(); +// } +// File targetFile = new File(realPath, fileName);//目标文件 +// //开始从源文件拷贝到目标文件 +// //传图片一步到位 +// mf.transferTo(targetFile); +// //拼接数据 +// return fileUploadConfig.getQrLocation() + TimeDir +"\\"+ fileName; +// } + + @ApiOperation(value = "单文件上传", notes = "单文件上传,rename 默认不重命名") + @PostMapping(value = "strUpload", headers = "content-type=multipart/form-data") + public String strUpload(@RequestParam(value = "file") MultipartFile mf,@RequestParam(value = "fileName")String fileName) throws IOException { + if (mf.isEmpty()) { + return "请传入文件!"; + } + String TimeDir =new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + String realPath = "C:\\Users\\Admin\\Desktop\\qrcode\\" + TimeDir; + File file = new File(realPath); + // 没有目录就创建 + if (!file.exists()) { + file.mkdirs(); + } + File targetFile = new File(realPath, fileName);//目标文件 + //开始从源文件拷贝到目标文件 + //传图片一步到位 + mf.transferTo(targetFile); + //拼接数据 + return TimeDir +"/"+ fileName; + } + + @ApiOperation(value = "单文件上传(覆盖服务器原文件)", notes = "单文件上传,rename 默认不重命名") + @PostMapping(value = "test/upload", headers = "content-type=multipart/form-data") + public AjaxResult uploadTest(@RequestParam(value = "file") MultipartFile mf) throws IOException { + if (mf.isEmpty()) { + return AjaxResult.error("请传入文件!"); + } + String TimeDir = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + String realPath = fileUploadConfig.getLocation() + "2021-11-17"; + File file = new File(realPath); + // 没有目录就创建 + if (!file.exists()) { + file.mkdirs(); + } + // 判断文件大小 + String filename = "6u2mGlqHkeE75e2ab51b4a03c6982ff7d68f4c024d43.jpg"; + // 获取文件后缀 + String ext = filename.substring(filename.lastIndexOf("."), filename.length()); + // 检查文件类型 + if (!fileUploadConfig.getAllowExt().contains(ext)) { + return AjaxResult.error("上传文件格式不正确,仅支持" + fileUploadConfig.getAllowExt()); + } + File targetFile = new File(realPath, filename);//目标文件 + //开始从源文件拷贝到目标文件 + //传图片一步到位 + mf.transferTo(targetFile); + //拼接数据 + String imgstr = fileUploadConfig.getAccessPath() + "2021-11-17" + "/" + filename; + return AjaxResult.success(imgstr); + } + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java new file mode 100644 index 0000000..908ea18 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java @@ -0,0 +1,216 @@ +package com.ruoyi.common.core.domain; + +import java.util.HashMap; +import java.util.Objects; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author ruoyi + */ +public class AjaxResult<T> extends HashMap<String, Object> +{ + 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"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + 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(HttpStatus.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(HttpStatus.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(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } + + /** + * 是否为成功消息 + * + * @return 结果 + */ + public boolean isSuccess() + { + return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG)); + } + + /** + * 是否为警告消息 + * + * @return 结果 + */ + public boolean isWarn() + { + return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG)); + } + + /** + * 是否为错误消息 + * + * @return 结果 + */ + public boolean isError() + { + return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG)); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java new file mode 100644 index 0000000..4b27433 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java @@ -0,0 +1,159 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModelProperty; + +/** + * 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") + private Date createTime; + + /** 更新者 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + @ApiModelProperty(value = "禁用备注") + @TableField("disable_remark") + private String disableRemark; + + @ApiModelProperty(value = "操作时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("operating_time") + private LocalDateTime operatingTime; + + @ApiModelProperty(value = "操作人 登录名(登录账号)") + @TableField("operating_person") + private String operatingPerson; + + public String getDisableRemark() { + return disableRemark; + } + + public void setDisableRemark(String disableRemark) { + this.disableRemark = disableRemark; + } + + public LocalDateTime getOperatingTime() { + return operatingTime; + } + + public void setOperatingTime(LocalDateTime operatingTime) { + this.operatingTime = operatingTime; + } + + public String getOperatingPerson() { + return operatingPerson; + } + + public void setOperatingPerson(String operatingPerson) { + this.operatingPerson = operatingPerson; + } + + /** 请求参数 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map<String, Object> 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<String, Object> getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map<String, Object> params) + { + this.params = params; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseModel.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseModel.java new file mode 100644 index 0000000..94db5c6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseModel.java @@ -0,0 +1,107 @@ +package com.ruoyi.common.core.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author xiaochen + * @ClassName BaseModel + * @Description + * @date 2024-04-02 19:38 + */ +@Data +public class BaseModel implements Serializable { + /** + * 字段常量属性 + */ + public static final String ID = "id"; + + public static final String CREATE_TIME = "create_time"; + + public static final String LAST_TIME = "last_time"; + + private static final long serialVersionUID = 2553749188490103197L; + /** + * 新增执行 + */ + @ApiModelProperty(value = "记录创建人,前端忽略") + @JsonIgnore + @TableField(value = "create_by", fill = FieldFill.INSERT) + private String createBy; + + /** + * 新增和更新执行 + */ + @ApiModelProperty(value = "记录修改人,前端忽略") + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) + private String updateBy; + /** + * 删除 未删除 + */ + @JsonIgnore + @TableField("`disabled`") + @TableLogic + private Boolean disabled; + + @ApiModelProperty(value = "记录创建时间,前端忽略") + @TableField("create_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + + /** + * 最后修改时间 + */ + @ApiModelProperty(value = "记录修改时间,前端忽略") + @TableField("update_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public String getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(String updateBy) { + this.updateBy = updateBy; + } + + public Boolean getDisabled() { + return disabled; + } + + public void setDisabled(Boolean disabled) { + this.disabled = disabled; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BasePage.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BasePage.java new file mode 100644 index 0000000..b7d844c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BasePage.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.core.domain; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + +/** + * <p></p> + * + * @author mouseyCat + * @date 2020/8/24 16:39 + */ +@ApiModel(value = "基础查询列表dto") +public class BasePage implements Serializable { + /** + * 分页参数,当前页码 + */ + @ApiModelProperty(value = "分页参数,当前页码") + private Integer pageNum = 1; + /** + * 分页参数,每页数量 + */ + @ApiModelProperty(value = "分页参数,每页数量,默认为10") + private Integer pageSize = 10; + + 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; + } + + @Override + public String toString() { + return "BasePage{" + + "pageNum=" + pageNum + + ", pageSize=" + pageSize + + '}'; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java new file mode 100644 index 0000000..ef15802 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java @@ -0,0 +1,115 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import com.ruoyi.common.constant.HttpStatus; + +/** + * 响应信息主体 + * + * @author ruoyi + */ +public class R<T> implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = HttpStatus.SUCCESS; + + /** 失败 */ + public static final int FAIL = HttpStatus.ERROR; + + private int code; + + private String msg; + + private T data; + + public static <T> R<T> ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static <T> R<T> ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static <T> R<T> ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static <T> R<T> fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static <T> R<T> fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static <T> R<T> fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static <T> R<T> fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static <T> R<T> fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static <T> R<T> restResult(T data, int code, String msg) + { + R<T> 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 <T> Boolean isError(R<T> ret) + { + return !isSuccess(ret); + } + + public static <T> Boolean isSuccess(R<T> ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java new file mode 100644 index 0000000..a180a18 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.ruoyi.common.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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; + + /** 子部门 */ + private List<?> children = new ArrayList<>(); + + 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; + } + + public List<?> getChildren() + { + return children; + } + + public void setChildren(List<?> children) + { + this.children = children; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java new file mode 100644 index 0000000..bd835db --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.core.domain.entity.SysMenu; + +/** + * Treeselect树结构实体类 + * + * @author ruoyi + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List<TreeSelect> children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public List<TreeSelect> getChildren() + { + return children; + } + + public void setChildren(List<TreeSelect> children) + { + this.children = children; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java new file mode 100644 index 0000000..fb18c5c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java @@ -0,0 +1,203 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +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 List<SysDept> children = new ArrayList<SysDept>(); + + 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; + } + + public List<SysDept> getChildren() + { + return children; + } + + public void setChildren(List<SysDept> children) + { + this.children = children; + } + + @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(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..9e921ff --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java @@ -0,0 +1,174 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +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; + + /** 样式属性(其他样式扩展) */ + 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(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..03504a7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +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(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..94e1a18 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java @@ -0,0 +1,259 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +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 Integer orderNum; + + /** 路由地址 */ + private String path; + + /** 组件路径 */ + private String component; + + /** 路由参数 */ + private String query; + + /** 是否为外链(0是 1否) */ + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + private String visible; + + /** 菜单状态(0正常 1停用) */ + private String status; + + /** 权限字符串 */ + private String perms; + + /** 菜单图标 */ + private String icon; + + /** 子菜单 */ + private List<SysMenu> children = new ArrayList<SysMenu>(); + + 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; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public String getIsFrame() + { + return isFrame; + } + + public void setIsFrame(String isFrame) + { + this.isFrame = isFrame; + } + + public String getIsCache() + { + return isCache; + } + + public void setIsCache(String isCache) + { + this.isCache = isCache; + } + + @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 getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @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<SysMenu> getChildren() + { + return children; + } + + public void setChildren(List<SysMenu> 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("path", getPath()) + .append("component", getComponent()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java new file mode 100644 index 0000000..b889f27 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java @@ -0,0 +1,279 @@ +package com.ruoyi.common.core.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.ruoyi.common.core.domain.BaseModel; +import lombok.Data; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.Set; + +/** + * 角色表 sys_role + * + * @author ruoyi + */ +@Data +public class SysRole extends BaseModel +{ + 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 = "角色排序") + private Integer roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + //@Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + //@Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private Integer status; + + /** 删除标志(0代表存在 2代表删除) */ + private Integer delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + private Long[] deptIds; + + /** 角色菜单权限 */ + private Set<String> permissions; + + private String remark; + + /** + * 删除天数 + */ + private Integer removeDays; + /** + * 岗位类型 1=经理 2=负责人 3=专员 + */ + private Integer postType; + /** + * 角色人数 + */ + @TableField(exist = false) + private Integer userCount; + + public Integer getPostType() { + return postType; + } + + public void setPostType(Integer postType) { + this.postType = postType; + } + + 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; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + public Integer getRoleSort() + { + return roleSort; + } + + public void setRoleSort(Integer roleSort) + { + this.roleSort = roleSort; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getDelFlag() + { + return delFlag; + } + + public void setDelFlag(Integer delFlag) + { + this.delFlag = delFlag; + } + + 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<String> getPermissions() + { + return permissions; + } + + public void setPermissions(Set<String> permissions) + { + this.permissions = permissions; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public Integer getRemoveDays() { + return removeDays; + } + + public void setRemoveDays(Integer removeDays) { + this.removeDays = removeDays; + } + + @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("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java new file mode 100644 index 0000000..246a26d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -0,0 +1,393 @@ +package com.ruoyi.common.core.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.Email; +import javax.validation.constraints.Size; +import java.util.Date; +import java.util.List; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +@Data +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + //@Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") + @ApiModelProperty(value = "用户id") + @TableField("user_id") + private Long userId; + + /** 部门ID */ + //@Excel(name = "部门编号", type = Type.IMPORT) + @ApiModelProperty(value = "部门id") + private Long deptId; + + /** 用户账号 */ + //@Excel(name = "登录名称") + @ApiModelProperty(value = "登录名称") + private String userName; + + /** 用户昵称 */ + //@Excel(name = "用户名称") + @ApiModelProperty(value = "用户名称") + private String nickName; + + /** 用户邮箱 */ + //@Excel(name = "用户邮箱") + @ApiModelProperty(value = "用户邮箱") + private String email; + + /** 手机号码 */ + //@Excel(name = "手机号码") + @ApiModelProperty(value = "手机号码") + private String phonenumber; + + /** 用户性别 */ + //@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + @ApiModelProperty(value = "用户性别 0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + @ApiModelProperty(value = "用户头像") + private String avatar; + + /** 密码 */ + @ApiModelProperty(value = "密码") + private String password; + + /** 帐号状态(0正常 1停用) */ + //@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") + @ApiModelProperty(value = "帐号状态 0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty(value = "删除标志(0代表存在 2代表删除)") + private String delFlag; + + /** 最后登录IP */ + //@Excel(name = "最后登录IP", type = Type.EXPORT) + @ApiModelProperty(value = "最后登录IP") + private String loginIp; + + /** 最后登录时间 */ + //@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "最后登录时间") + private Date loginDate; + + /** 部门对象 */ + //@Excels({ + //@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + //@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) +// }) + @ApiModelProperty(value = "部门对象") + private SysDept dept; + + /** 角色对象 */ + @ApiModelProperty(value = "角色对象") + private List<SysRole> roles; + + /** 角色组 */ + @ApiModelProperty(value = "角色组") + private Long[] roleIds; + + /** 岗位组 */ + @ApiModelProperty(value = "岗位组") + private Long[] postIds; + + /** 角色ID */ + @ApiModelProperty(value = "角色ID") + private Long roleId; + /** + * 是否为黑名单 1是 0否 + */ + @ApiModelProperty(value = "是否为黑名单 1是 0否") + private Integer ifBlack; + + /** + * 区县id + */ + @ApiModelProperty(value = "区县id") + private Integer districtId; + + @TableField(exist = false) + private String roleName; + @TableField(exist = false) + private String deptName; + @ApiModelProperty(value = "部门id集合") + @TableField(exist = false) + private List<String> deptIds; + + @ApiModelProperty(value = "营业部id") + @TableField("business_dept_id") + private String businessDeptId; + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public Integer getDistrictId() { + return districtId; + } + + public void setDistrictId(Integer districtId) { + this.districtId = districtId; + } + + 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 isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + @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; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + 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 SysDept getDept() + { + return dept; + } + + public void setDept(SysDept dept) + { + this.dept = dept; + } + + public List<SysRole> getRoles() + { + return roles; + } + + public void setRoles(List<SysRole> 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; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Integer getIfBlack() { + return ifBlack; + } + + public void setIfBlack(Integer ifBlack) { + this.ifBlack = ifBlack; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .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()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/TTenantResp.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/TTenantResp.java new file mode 100644 index 0000000..33b6fec --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/TTenantResp.java @@ -0,0 +1,70 @@ +package com.ruoyi.common.core.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseModel; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.time.LocalDate; + +/** + * <p> + * 租户 + * </p> + * + * @author xiaochen + * @since 2025-01-20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value="TTenant对象", description="登录返回") +public class TTenantResp { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + @ApiModelProperty(value = "住户名称") + private String residentName; + + @ApiModelProperty(value = "入住时间") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate checkinTime; + + @ApiModelProperty(value = "租户属性") + private String tenantAttributes; + + @ApiModelProperty(value = "租户类型") + private String tenantType; + + @ApiModelProperty(value = "联系电话") + private String phone; + + @ApiModelProperty(value = "证件号码") + private String idCard; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "银行转账专号") + private String bankNumber; + + @ApiModelProperty(value = "通讯地址") + private String mailAddress; + + @ApiModelProperty(value = "登录账号") + private String account; + + @ApiModelProperty(value = "登录密码") + private String password; + @ApiModelProperty(value = "微信openid") + private String openId; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java new file mode 100644 index 0000000..1bcaada --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.core.domain.model; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +@ApiModel(value = "登录对象Body") +public class LoginBody +{ + /** + * 用户名 + */ + @ApiModelProperty(value = "用户名",notes = "用户名") + private String username; + + /** + * 用户密码 + */ + @ApiModelProperty(value = "用户密码") + private String password; + + /** + * 验证码 + */ + @ApiModelProperty(value = "验证码") + private String code; + + /** + * 唯一标识 + */ + private String uuid; + + 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 getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java new file mode 100644 index 0000000..670e6b3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.ruoyi.common.core.domain.model; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.ruoyi.common.core.domain.entity.SysUser; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Collection; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author ruoyi + */ +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set<String> permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set<String> permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + 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 Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set<String> getPermissions() + { + return permissions; + } + + public void setPermissions(Set<String> permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() + { + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUserApplet.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUserApplet.java new file mode 100644 index 0000000..186be08 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUserApplet.java @@ -0,0 +1,263 @@ +package com.ruoyi.common.core.domain.model; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.entity.TTenantResp; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author ruoyi + */ +@Data +public class LoginUserApplet implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private String userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set<String> permissions; + + /** + * 用户信息 + */ + private TTenantResp user; + + + public LoginUserApplet() + { + } + + public LoginUserApplet(TTenantResp user, Set<String> permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUserApplet(String userId, Long deptId, TTenantResp user, Set<String> permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + public String getUserId() + { + return userId; + } + + public void setUserId(String userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getResidentName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + 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 Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set<String> getPermissions() + { + return permissions; + } + + public void setPermissions(Set<String> permissions) + { + this.permissions = permissions; + } + + + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() + { + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java new file mode 100644 index 0000000..868a1fc --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.ruoyi.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/TimeRangeQueryBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/TimeRangeQueryBody.java new file mode 100644 index 0000000..7a9239f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/TimeRangeQueryBody.java @@ -0,0 +1,45 @@ +package com.ruoyi.common.core.domain.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BasePage; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; + +@ApiModel("时间范围分页dto") +public class TimeRangeQueryBody extends BasePage { + + @ApiModelProperty("开始时间 格式 yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date startTime; + + @ApiModelProperty("结束时间 格式 yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date endTime; + + public String getStartTime() { + if (Objects.nonNull(startTime)) { + return new SimpleDateFormat("yyyy-MM-dd").format(startTime) + " 00:00:00"; + } + return null; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + if (Objects.nonNull(endTime)) { + return new SimpleDateFormat("yyyy-MM-dd").format(endTime) + " 23:59:59"; + } + return null; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/exception/ServiceException.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/exception/ServiceException.java new file mode 100644 index 0000000..4983866 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/exception/ServiceException.java @@ -0,0 +1,74 @@ +package com.ruoyi.common.core.exception; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java new file mode 100644 index 0000000..8966cb4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +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) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java new file mode 100644 index 0000000..cea2364 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java @@ -0,0 +1,122 @@ +package com.ruoyi.common.core.page; + + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +public class TableDataInfo<T> implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 列表数据 */ + private List<?> records; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + +// private boolean hasNextPage; +// private boolean hasPrevPage; +// private long startIndex; + +// public boolean getHasNextPage() { +// return hasNextPage; +// } +// +// public void setHasNextPage(boolean hasNextPage) { +// this.hasNextPage = hasNextPage; +// } +// +// public boolean getHasPrevPage() { +// return hasPrevPage; +// } +// +// public void setHasPrevPage(boolean hasPrevPage) { +// this.hasPrevPage = hasPrevPage; +// } +// +// private boolean hasNextPage(long currentPage, long totalPage) { +// return currentPage < totalPage && totalPage != 0; +// } +// private static boolean hasPrevPage(long currentPage) { +// return currentPage != 1; +// } +// +// public long getStartIndex() { +// return startIndex; +// } +// +// public void setStartIndex(long startIndex) { +// this.startIndex = startIndex; +// } + + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List<?> list, int total) + { + this.records = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List<?> getRecords() + { + return records; + } + + public void setRecords(List<?> records) + { + this.records = records; + } + + 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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java new file mode 100644 index 0000000..a120c30 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +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(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java new file mode 100644 index 0000000..96864ac --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -0,0 +1,343 @@ +package com.ruoyi.common.core.redis; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisCache +{ + private static final String LOCK_PREFIX = "lock:"; + private static final RedisScript<Long> UNLOCK_SCRIPT = new DefaultRedisScript<>( + "if redis.call('get', KEYS[1]) == ARGV[1] then " + + " return redis.call('del', KEYS[1]) " + + "else " + + " return 0 " + + "end", + Long.class + ); + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public <T> void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public <T> T getCacheObject(final String key) + { + ValueOperations<String, T> operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) + { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public <T> long setCacheList(final String key, final List<T> dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public <T> List<T> getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) + { + BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); + Iterator<T> it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public <T> Set<T> getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public <T> void setCacheMap(final String key, final Map<String, T> dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public <T> Map<String, T> getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public <T> void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public <T> T getCacheMapValue(final String key, final String hKey) + { + HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) + { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection<String> keys(final String pattern) + { + return redisTemplate.keys(pattern); + } + + /** + * 尝试加锁 + * + * @param lockKey 锁的key + * @param requestId 锁的持有者标识符(如UUID) + * @param expireTime 锁的过期时间(秒) + * @return 加锁成功返回true,否则返回false + */ + public boolean tryLock(String lockKey, String requestId, long expireTime) { + String lockKeyWithPrefix = LOCK_PREFIX + lockKey; + // 使用SET命令的NX和PX选项来加锁 + Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKeyWithPrefix, requestId, expireTime, TimeUnit.SECONDS); + return result != null && result; + } + + /** + * 尝试加锁 + * + * @param lockKey 锁的key + * @param requestId 锁的持有者标识符(如UUID) + * @param expireTime 锁的过期时间(秒) + * @return 加锁成功返回true,否则返回false + */ + public boolean trylockLoop(String lockKey, String requestId,long expireTime) { + String lockKeyWithPrefix = LOCK_PREFIX + lockKey; + // 使用SET命令的NX和PX选项来加锁 + Boolean result = false; + int num = 50; + while (num>0){ + result = redisTemplate.opsForValue().setIfAbsent(lockKeyWithPrefix, requestId, expireTime, TimeUnit.SECONDS); + if (result){ + break; + } + try { + TimeUnit.MILLISECONDS.sleep(200); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + num--; + } + return result; + } + + + /** + * 解锁 + * + * @param lockKey 锁的key + * @param requestId 锁的持有者标识符(如UUID) + * @return 解锁成功返回true,否则返回false + */ + public boolean unlock(String lockKey, String requestId) { + String lockKeyWithPrefix = LOCK_PREFIX + lockKey; + // 使用Lua脚本来验证锁的持有者并解锁 + Long result = (Long) redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKeyWithPrefix), requestId); + return result != null && result == 1L; + } + + /** + * 自增 + * @param key 要加一的键 + */ + public int increment(String key) { + // +1 操作 + return redisTemplate.opsForValue().increment(key).intValue(); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java new file mode 100644 index 0000000..84124aa --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +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(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java new file mode 100644 index 0000000..c918e36 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java @@ -0,0 +1,1000 @@ +package com.ruoyi.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +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 +{ + /** + * 转换为字符串<br> + * 如果给定的值为null,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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(); + } + + /** + * 转换为字符串<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符<br> + * 如果给定的值为null,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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); + } + + /** + * 转换为字符<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组<br> + * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组<br> + * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组<br> + * + * @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数组<br> + * + * @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数组<br> + * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + return toStrArray(",", str); + } + + /** + * 转换为String数组<br> + * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean<br> + * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static <E extends Enum<E>> E toEnum(Class<E> 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对象<br> + * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br> + * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @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<br> + * 如果给定的值为空,或者转换失败,返回默认值<br> + * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串<br> + * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串<br> + * 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)); + } + + /** + * 将对象转为字符串<br> + * 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<Character> 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<Character> 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++) + { + s += (digit[(int) (Math.floor(n * 10 * 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("^整$", "零元整"); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java new file mode 100644 index 0000000..c78ac77 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +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 = '}'; + + /** + * 格式化字符串<br> + * 此方法只是简单将占位符 {} 按照顺序替换为参数<br> + * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br> + * 例:<br> + * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br> + * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br> + * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br> + * + * @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(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/utils/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/utils/Constants.java new file mode 100644 index 0000000..8144c3a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/utils/Constants.java @@ -0,0 +1,143 @@ +package com.ruoyi.common.core.utils; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 成功标记 + */ + public static final Integer SUCCESS = 200; + + /** + * 失败标记 + */ + public static final Integer FAIL = 500; + + /** + * 登录成功状态 + */ + public static final String LOGIN_SUCCESS_STATUS = "1"; + + /** + * 登录失败状态 + */ + public static final String LOGIN_FAIL_STATUS = "2"; + + /** + * 登录成功 + */ + 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 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 long CAPTCHA_EXPIRATION = 2; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" }; + + /** + * 时间格式化 + */ + public static final String DATE_FORMATTER_TIME = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_FORMATTER_DATE = "yyyy-MM-dd"; + /** + * 修改手机号后缀 + */ + public static final String UPDATE_PHONE = "_updatePhone"; + /** + * 申请建桩后缀 + */ + public static final String APPLY_CHARGING = "_applyCharging"; + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.ruoyi.common.core.utils.file" }; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/utils/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/utils/HttpUtils.java new file mode 100644 index 0000000..7d25f7a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/utils/HttpUtils.java @@ -0,0 +1,311 @@ +package com.ruoyi.common.core.utils; + +import com.ruoyi.common.core.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; + +/** + * 通用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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + 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) + { + 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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + 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) + { + 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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + 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(); + } + + public static String post(String strURL, String params) { + String result = ""; + BufferedReader reader = null; + try { + URL url = new URL(strURL);// 创建连接 + HttpURLConnection connection = (HttpURLConnection) url + .openConnection(); + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setUseCaches(false); + connection.setInstanceFollowRedirects(true); + connection.setRequestMethod("POST"); // 设置请求方式 + connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式 + connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式 + connection.connect(); + if (params != null && !StringUtils.isEmpty(params)) { + byte[] writebytes = params.getBytes(); + // 设置文件长度 + // connection.setRequestProperty("Content-Length", String.valueOf(writebytes.length)); + OutputStream outwritestream = connection.getOutputStream(); + outwritestream.write(params.getBytes()); + outwritestream.flush(); + outwritestream.close(); + // Log.d("hlhupload", "doJsonPost: conn"+connection.getResponseCode()); + } + if (connection.getResponseCode() == 200) { + log.info("<<<<<<<<<<<<<请求响应:{}", connection.getResponseMessage()); + reader = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + result = reader.readLine(); + log.info("<<<<<<<<<<<<<请求响应:{}", result); + } else { + throw new ServiceException(connection.getResponseMessage()); + } + } catch (Exception e) { + throw new ServiceException("http的post请求异常!" + e.getMessage()); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return result; + } + + 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; + } + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BillTypeEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BillTypeEnum.java new file mode 100644 index 0000000..3145e60 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BillTypeEnum.java @@ -0,0 +1,32 @@ +package com.ruoyi.common.enums; + +import lombok.Getter; + +@Getter +public enum BillTypeEnum { + + Zujin(1,"租金"), + Yajin(2,"押金"), + ShenghuoFee(3,"生活费用"), + FangwuYanshou(4,"房屋验收"), + ; + + private Integer code; + + private String name; + BillTypeEnum(Integer code,String name){ + this.code = code; + this.name = name; + } + + public static BillTypeEnum getByCode(Integer code){ + BillTypeEnum[] values = BillTypeEnum.values(); + for (BillTypeEnum value : values) { + if (value.code==code){ + return value; + } + } + return null; + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java new file mode 100644 index 0000000..10b7306 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.enums; + +/** + * 操作状态 + * + * @author ruoyi + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java new file mode 100644 index 0000000..5a5ce48 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java @@ -0,0 +1,63 @@ +package com.ruoyi.common.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, + /** + * 查询 + */ + SELECT, +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java new file mode 100644 index 0000000..0d945be --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/DisabledEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DisabledEnum.java new file mode 100644 index 0000000..46e7bca --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/DisabledEnum.java @@ -0,0 +1,43 @@ +package com.ruoyi.common.enums; + +import lombok.Getter; + +/** + * @author xiaochen + * @ClassName Disable + * @Description + * @date 2022-06-08 16:55 + */ +public enum DisabledEnum { + NO(0, "否"), + YES(1, "是"); + + @Getter + private String desc; + + + @Getter + private int code; + + + DisabledEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 通过code获取枚举 + * + * @param code + * @return + */ + public static DisabledEnum fromCode(Integer code) { + DisabledEnum[] resultTypes = DisabledEnum.values(); + for (DisabledEnum resultType : resultTypes) { + if (code.equals(resultType.getCode())) { + return resultType; + } + } + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java new file mode 100644 index 0000000..be6f739 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.ruoyi.common.enums; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @author ruoyi + */ +public enum HttpMethod +{ + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + private static final Map<String, HttpMethod> mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java new file mode 100644 index 0000000..c609fd8 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java new file mode 100644 index 0000000..bdd143c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/ProcessCategoryEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/ProcessCategoryEnum.java new file mode 100644 index 0000000..5aef891 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/ProcessCategoryEnum.java @@ -0,0 +1,51 @@ +package com.ruoyi.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 1. 入户调查 + * 2. 价格评估 + * 3. 协议签订 + * 固定对应表 state_process_module + */ +@Getter +@AllArgsConstructor +public enum ProcessCategoryEnum { + CATEGORY0(0, "错误分类"), + CATEGORY1(1, "合同新增审批"), + CATEGORY2(2, "合同签订审批"), + CATEGORY3(3, "合同提前终止审批"), + ; + + + private final Integer value; + private final String text; + + public static Integer getValue(String text) { + for (ProcessCategoryEnum v : ProcessCategoryEnum.values()) { + if (v.text.equals(text)) { + return v.value; + } + } + return 0; + } + + public static String getValueByKey(Integer key) { + for (ProcessCategoryEnum v : ProcessCategoryEnum.values()) { + if (v.getValue().equals(key)) { + return v.getText(); + } + } + return ""; + } + + public static ProcessCategoryEnum getEnumByKey(Integer key) { + for (ProcessCategoryEnum v : ProcessCategoryEnum.values()) { + if (v.getValue().equals(key)) { + return v; + } + } + return CATEGORY0; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/StateProcessActionEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/StateProcessActionEnum.java new file mode 100644 index 0000000..d61daea --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/StateProcessActionEnum.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程动作枚举 + * + */ +@Getter +@AllArgsConstructor +public enum StateProcessActionEnum { + + START(0), // "发起" + REJECTED(3), // "拒绝" + APPROVED(4), // "通过" + CANCELED(5), // "撤销" + BACK(7), // "回退" + COPY(12), // "抄送" + FORWARD(13), // "转发" + COMMENT(14), // "评论" + TRANSACT(15); // "办理" + + private final int value; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/SubmitStatusEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..9e231f3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/SubmitStatusEnum.java @@ -0,0 +1,33 @@ +package com.ruoyi.common.enums; + +import lombok.Getter; + +import java.util.Objects; + +@Getter +public enum SubmitStatusEnum { + SUBMITTED(-1, ""), + REJECT(0, "已退回"), + PENDING_REVIEW(1, "待审核"), + ACCEPT(3, "已接收"); + + private final int value; + private final String text; + + public static String getTextByValue(Integer value) { + if (Objects.isNull(value)) { + return ""; + } + for (SubmitStatusEnum v : SubmitStatusEnum.values()) { + if (v.getValue() == (value)) { + return v.getText(); + } + } + return ""; + } + + SubmitStatusEnum(int value, String text) { + this.text = text; + this.value = value; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/TaskEventType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/TaskEventType.java new file mode 100644 index 0000000..f96fc9c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/TaskEventType.java @@ -0,0 +1,146 @@ +/* + * Copyright 2023-2025 Licensed under the Dual Licensing + * website: https://aizuda.com + */ +package com.ruoyi.common.enums; + +/** + * 流程引擎监听类型 + * + * <p> + * <a href="https://aizuda.com">官网</a>尊重知识产权,不允许非法使用,后果自负 + * </p> + * + * @author lizhongyuan + * @since 1.0 + */ +public enum TaskEventType { + /** + * 发起 + */ + start, + /** + * 创建 + */ + create, + /** + * 再创建,仅用于流程回退 + */ + recreate, + /** + * 抄送 + */ + cc, + /** + * 分配 + */ + assignment, + /** + * 委派任务解决 + */ + delegateResolve, + /** + * 任务加签 + */ + addTaskActor, + /** + * 任务减签 + */ + removeTaskActor, + /** + * 驳回至上一步处理 + */ + reject, + /** + * 角色认领 + */ + claimRole, + /** + * 部门认领 + */ + claimDepartment, + /** + * 拿回未执行任务 + */ + reclaim, + /** + * 撤回指定任务 + */ + withdraw, + /** + * 唤醒历史任务 + */ + resume, + /** + * 完成 + */ + complete, + /** + * 撤销 + */ + revoke, + /** + * 终止 + */ + terminate, + /** + * 更新 + */ + update, + /** + * 删除 + */ + delete, + /** + * 调用外部流程任务【办理子流程】 + */ + callProcess, + /** + * 超时 + */ + timeout, + /** + * 跳转 + */ + jump, + /** + * 自动跳转 + */ + autoJump, + /** + * 驳回跳转 + */ + rejectJump, + /** + * 路由跳转 + */ + routeJump, + /** + * 驳回重新审批跳转 + */ + reApproveJump, + /** + * 自动审批完成 + */ + autoComplete, + /** + * 自动审批拒绝 + */ + autoReject, + /** + * 触发器任务 + */ + trigger, + /** + * 结束 + */ + end; + + public boolean eq(TaskEventType eventType) { + return this == eventType; + } + + public boolean ne(TaskEventType eventType) { + return this != eventType; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/UpdateTypeEnum.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UpdateTypeEnum.java new file mode 100644 index 0000000..183b095 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UpdateTypeEnum.java @@ -0,0 +1,47 @@ +package com.ruoyi.common.enums; + +import lombok.Getter; + +/** + * @author xiaochen + * @ClassName Disable + * @Description + * @date 2022-06-08 16:55 + */ +public enum UpdateTypeEnum { + /*调整类型 1=经理更换,2=负责人更换,3=人员新增,4=调整计划时间*/ + PROJECT_MANAGER(1, "经理更换"), + WORK_HEADER(2, "负责人更换"), + ADD_USER(3, "人员新增"), + UPDATE_PLAN_TIME(4, "调整计划时间"), + REDUCE_USER(5, "人员减少"); + + @Getter + private String desc; + + + @Getter + private int code; + + + UpdateTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 通过code获取枚举 + * + * @param code + * @return + */ + public static UpdateTypeEnum fromCode(Integer code) { + UpdateTypeEnum[] resultTypes = UpdateTypeEnum.values(); + for (UpdateTypeEnum resultType : resultTypes) { + if (code.equals(resultType.getCode())) { + return resultType; + } + } + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java new file mode 100644 index 0000000..d7ff44a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java new file mode 100644 index 0000000..f6ad2ab --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java new file mode 100644 index 0000000..81a71b5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +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; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java new file mode 100644 index 0000000..fcc7ab6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java @@ -0,0 +1,74 @@ +package com.ruoyi.common.exception; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java new file mode 100644 index 0000000..980fa46 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java @@ -0,0 +1,26 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java new file mode 100644 index 0000000..b55d72e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java new file mode 100644 index 0000000..871f09b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +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); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..70e0ec9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +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 }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..ec6ab05 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +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 }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java new file mode 100644 index 0000000..f45e7ef --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java @@ -0,0 +1,61 @@ +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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..011f308 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,80 @@ +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); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java new file mode 100644 index 0000000..a567b40 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +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 + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/state/StateErrorCode.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/state/StateErrorCode.java new file mode 100644 index 0000000..9e38eef --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/state/StateErrorCode.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.exception.state; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum StateErrorCode { + + PROCESS_TEMPLATE_KEY_EXISTS("已经存在此名称的模版了!"), + PROCESS_TEMPLATE_NOT_EXISTS("流程模版不存在!"), + PROCESS_NOT_DEPLOY("模版尚未部署成功"), + PROCESS_VERSION_ERROR("流程引擎版本不一致"), + ; + + private final String value; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java new file mode 100644 index 0000000..2bf5038 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java new file mode 100644 index 0000000..389dbc7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..85f9486 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author ruoyi + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java new file mode 100644 index 0000000..c292d70 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java new file mode 100644 index 0000000..eff8181 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java @@ -0,0 +1,16 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..a7f3e5f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..c887cf1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/CustomRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/CustomRequestWrapper.java new file mode 100644 index 0000000..3989a61 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/CustomRequestWrapper.java @@ -0,0 +1,75 @@ +package com.ruoyi.common.filter; + + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +public class CustomRequestWrapper extends HttpServletRequestWrapper { + private final String body; + + /** + * 构造函数,用于创建自定义请求包装器。 + * + * @param request 原始 HTTP 请求对象 + * @param body 新的请求体内容 + * @throws IOException 如果读取或处理请求体时发生错误 + */ + public CustomRequestWrapper(HttpServletRequest request, String body) throws IOException { + super(request); + this.body = body; + } + + /** + * 重写 getInputStream 方法,返回一个新的 ServletInputStream, + * 其中包含修改后的请求体内容。 + * + * @return 包含修改后请求体内容的 ServletInputStream + * @throws IOException 如果读取输入流时发生错误 + */ + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); + return new ServletInputStream() { + @Override + public boolean isFinished() { + // 返回是否已经完成读取 + return false; + } + + @Override + public boolean isReady() { + // 返回是否准备好读取数据 + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + // 设置读监听器,这里不需要实现 + } + + @Override + public int read() throws IOException { + // 从字节数组输入流中读取下一个字节 + return byteArrayInputStream.read(); + } + }; + } + + /** + * 重写 getReader 方法,返回一个新的 BufferedReader, + * 其中包含修改后的请求体内容。 + * + * @return 包含修改后请求体内容的 BufferedReader + * @throws IOException 如果读取输入流时发生错误 + */ + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 0000000..e1e431b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author ruoyi + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..a1bcfe2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..407d1ba --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.ruoyi.common.utils.http.HttpHelper; +import com.ruoyi.common.constant.Constants; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestFilterConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestFilterConfig.java new file mode 100644 index 0000000..1fada65 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RequestFilterConfig.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.filter; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RequestFilterConfig { + + @Bean + public FilterRegistrationBean<SmCryptoFilter> loggingFilter(){ + FilterRegistrationBean<SmCryptoFilter> registrationBean = new FilterRegistrationBean<>(); + + registrationBean.setFilter(new SmCryptoFilter()); + registrationBean.addUrlPatterns("/api/*"); // 根据实际需求调整URL模式 +// registrationBean.addInitParameter("excludedUrls","/api/*"); + + return registrationBean; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/SmCryptoFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/SmCryptoFilter.java new file mode 100644 index 0000000..519f667 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/SmCryptoFilter.java @@ -0,0 +1,88 @@ +package com.ruoyi.common.filter; + +import com.ruoyi.common.utils.Sm4Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.web.util.ContentCachingResponseWrapper; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.rmi.ServerException; + +public class SmCryptoFilter implements Filter { + + private static final Logger logger = LoggerFactory.getLogger(SmCryptoFilter.class); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + try { + // 对请求进行解密 + String decryptedRequestBody = decryptRequest(httpRequest); + // 处理解密后的请求体 CustomRequestWrapper为自定义请求包装器,请看下面实现 + CustomRequestWrapper customRequestWrapper = new CustomRequestWrapper(httpRequest, decryptedRequestBody); + + // 继续处理请求 + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse); + chain.doFilter(customRequestWrapper, responseWrapper); + + // 获取并加密响应内容 + byte[] content = responseWrapper.getContentAsByteArray(); + String originalResponseBody = new String(content); + String encryptedResponseBody = encryptResponse(originalResponseBody); + + // 写回客户端 + responseWrapper.reset(); + responseWrapper.getWriter().write(encryptedResponseBody); + responseWrapper.copyBodyToResponse(); + + } catch (Exception e) { + logger.error("内部响应错误:{}", e.getMessage()); + e.printStackTrace(); + // 返回标准错误响应 + httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + httpResponse.getWriter().write("Internal Server Error"); + } + } + + /** + * 解密 HTTP 请求的主体内容。 + * + * @param request HTTP 请求对象 + * @return 解密后的请求体字符串 + * @throws IOException 如果读取或解密过程中发生错误 + */ + private String decryptRequest(HttpServletRequest request) throws IOException { + StringBuilder jb = new StringBuilder(); + String line; + BufferedReader reader = request.getReader(); + while ((line = reader.readLine()) != null) { + jb.append(line); + } + try { + return Sm4Utils.sm4DecryptUtil(jb.toString()); + } catch (Exception e) { + throw new ServerException("参数解密失败:{}", e); + } + } + + /** + * 加密 HTTP 响应的主体内容。 + * + * @param response 原始响应体字符串 + * @return 加密后的响应体字符串 + */ + private String encryptResponse(String response) throws ServerException { + try { + return Sm4Utils.sm4EncryptUtil(response); + } catch (Exception e) { + throw new ServerException("参数解密失败:{}", e); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java new file mode 100644 index 0000000..9052f0d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java @@ -0,0 +1,75 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.enums.HttpMethod; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List<String> excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + } + + @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 || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..05149f0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.ruoyi.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; +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[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java b/ruoyi-common/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..c9f3b66 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java @@ -0,0 +1,50 @@ +package com.ruoyi.common.redis.configure; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.nio.charset.Charset; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class<T> clazz; + + + public FastJson2JsonRedisSerializer(Class<T> clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/redis/service/RedisService.java b/ruoyi-common/src/main/java/com/ruoyi/common/redis/service/RedisService.java new file mode 100644 index 0000000..1ae72cd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/redis/service/RedisService.java @@ -0,0 +1,273 @@ +package com.ruoyi.common.redis.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisService +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public <T> void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public <T> T getCacheObject(final String key) + { + ValueOperations<String, T> operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) + { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public <T> long setCacheList(final String key, final List<T> dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public <T> List<T> getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) + { + BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); + Iterator<T> it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public <T> Set<T> getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public <T> void setCacheMap(final String key, final Map<String, T> dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + public <T> void setCacheMap(final String key, final Map<String, T> dataMap, long timeout) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + redisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public <T> Map<String, T> getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public <T> void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public <T> T getCacheMapValue(final String key, final String hKey) + { + HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) + { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection<String> keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/resp/AccessTokenRespBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/resp/AccessTokenRespBody.java new file mode 100644 index 0000000..293408d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/resp/AccessTokenRespBody.java @@ -0,0 +1,28 @@ +package com.ruoyi.common.resp; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * AccessToken 全局唯一 + * + * @author liheng + */ +@Data +public class AccessTokenRespBody implements Serializable { + + /** + * 获取到的凭证 + */ + @JsonProperty("access_token") + private String accessToken; + /** + * 凭证有效时间,单位:秒 + */ + @JsonProperty("expires_in") + private int expiresIn; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java new file mode 100644 index 0000000..b6326c2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java @@ -0,0 +1,114 @@ +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)); + BigDecimal one = BigDecimal.ONE; + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/CodeGenerateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CodeGenerateUtils.java new file mode 100644 index 0000000..76492bc --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/CodeGenerateUtils.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.utils; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Description + * @Author xiaochen + * @Date 2021/7/28 10:26 + */ +public class CodeGenerateUtils { + + /** + * @return + * @Description 获取商品编码 + * 商品编码规则:nanoTime(后5位)*5位随机数(10000~99999) + * @Author xiaochen + */ + public static String generateProductCode() { + long nanoPart = System.nanoTime() % 100000L; + if (nanoPart < 10000L) { + nanoPart += 10000L; + } + long randomPart = (long) (Math.random() * (90000) + 10000); + String code = "0" + new BigDecimal(nanoPart).multiply(new BigDecimal(randomPart)); + return code.substring(code.length() - 10); + } + + /** + * @return + * @Description 生成订单编号 10位 + * 订单编号规则:(10位):(年末尾*月,取后2位)+(用户ID%3.33*日取整后2位)+(timestamp*10000以内随机数,取后6位) + * @Author xiaochen + */ + public static String generateOrderSn() { + Calendar calendar = Calendar.getInstance(); + int year = calendar.get(Calendar.YEAR); + year = year % 10; + if (year == 0) year = 10; + int month = calendar.get(Calendar.MONTH) + 1; + int yearMonth = year * month; + String yearMonthPart = "0" + yearMonth; + yearMonthPart = yearMonthPart.substring(yearMonthPart.length() - 2); + + int day = calendar.get(Calendar.DAY_OF_MONTH); + double v = Math.random() * 10000; + int dayNum = (int) ((v % 3.33) * day); + String dayPart = "0" + dayNum; + dayPart = dayPart.substring(dayPart.length() - 2); + + String timestampPart = "" + (Math.random() * 10000) * (System.currentTimeMillis() / 10000); + timestampPart = timestampPart.replace(".", "").replace("E", ""); + timestampPart = timestampPart.substring(0, 6); + return yearMonthPart + dayPart + timestampPart; + } + + /** + * @return + * @Description 生成统一支付单号 规则:年(2)月(2)日(2)时(2)分(2)+timestamp*5位随机整数取后5位 + * @Author xiaochen + */ + public static String generateVolumeSn() { + Calendar calendar = Calendar.getInstance(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss"); + String dateTime = dateFormat.format(calendar.getTime()); + dateTime = dateTime.substring(2); + String timestampPart = "" + (Math.random() * 10000) * (System.currentTimeMillis() / 10000); + timestampPart = timestampPart.replace(".", "").replace("E", ""); + timestampPart = timestampPart.substring(0, 0); + return dateTime + timestampPart; + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java new file mode 100644 index 0000000..cd58196 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java @@ -0,0 +1,392 @@ +package com.ruoyi.common.utils; + +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 时间工具类 + * + * @author ruoyi + */ +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()); + } + + /** + * 指定日期所在周的周一和周日时间 + * + * @return 结果集 + */ + public static Map<String, Date> getWeekDate(Date date) { + Map<String, Date> map = new HashMap<>(2); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一 + cal.setFirstDayOfWeek(Calendar.MONDAY); + // 获得当前日期是一个星期的第几天 + int dayWeek = cal.get(Calendar.DAY_OF_WEEK); + if (dayWeek == 1) { + dayWeek = 8; + } + // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值 + cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - dayWeek); + Date mondayDate = cal.getTime(); + cal.add(Calendar.DATE, 4 + cal.getFirstDayOfWeek()); + Date sundayDate = cal.getTime(); + map.put("first", mondayDate); + map.put("last", sundayDate); + return map; + } + + /** + * 指定日期所在月的第一天/最后一天时间 + * + * @return 结果集 + */ + public static Map<String, Date> getMonthDate(Date date) { + Map<String, Date> map = new HashMap<>(2); + Calendar cal = Calendar.getInstance(); + //设置指定日期 + cal.setTime(date); + //获取当月第一天日期 + int first = cal.getActualMinimum(Calendar.DAY_OF_MONTH); + cal.set(Calendar.DAY_OF_MONTH, first); + Date firstDay = cal.getTime(); + map.put("first", firstDay); + //获取当月最后一天日期 + int last = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + cal.set(Calendar.DAY_OF_MONTH, last); + Date lastDay = cal.getTime(); + map.put("last", lastDay); + return map; + } + + /** + * 获取日期逻辑中所在的季度数 + * + * @param date 当前日期 + * @return 第几季度 + */ + public static int getQuarterOfYear(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar.get(Calendar.MONTH) / 3 + 1; + } + /** + * 指定日期所在季度的第一天/最后一天时间 + * + * @return 结果集 + */ + public static Map<String, Date> getQuarterDate(Date date) { + Map<String, Date> map = new HashMap<>(2); + + int quarter = getQuarterOfYear(date); + int year = dateToLocalDateTime(date).getYear(); + + int startMonth = (quarter - 1) * 3; + // 根据月获取开始时间 + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, year); + cal.set(Calendar.MONTH, startMonth); + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + Date first = cal.getTime(); + map.put("first", first); + + int lastMonth = quarter * 3 - 1; + // 根据月获取结束时间 + cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, year); + cal.set(Calendar.MONTH, lastMonth); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 59); + cal.set(Calendar.SECOND, 59); + Date last = cal.getTime(); + map.put("last", last); + + return map; + } + + /** + * 指定日期所在年的第一天/最后一天时间 + * + * @return 结果集 + */ + public static Map<String, Date> getYearDate(Date date) { + Map<String, Date> map = new HashMap<>(2); + Calendar cal = Calendar.getInstance(); + //设置指定日期 + cal.setTime(date); + //获取本年第一天日期 + int first = cal.getActualMinimum(Calendar.DAY_OF_YEAR); + cal.set(Calendar.DAY_OF_YEAR, first); + Date firstDay = cal.getTime(); + map.put("first", firstDay); + //获取本年最后一天日期 + int last = cal.getActualMaximum(Calendar.DAY_OF_YEAR); + cal.set(Calendar.DAY_OF_YEAR, last); + Date lastDay = cal.getTime(); + map.put("last", lastDay); + return map; + } + + /** + * 获取当天的00:00:00 + * + * @return + */ + public static LocalDateTime getDayStart(LocalDateTime time) { + return time.with(LocalTime.MIN); + } + + + /** + * 获取当天的23:59:59 + * + * @return + */ + public static LocalDateTime getDayEnd(LocalDateTime time) { + return time.with(LocalTime.MAX); + } + + /** + * localdatetime转为字符串 + * + * @param time localdatetime + * @return 字符串 + */ + public static String localDateTimeToString(LocalDateTime time) { + DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return df.format(time); + } + /** + * localdatetime转为字符串 + * + * @param time localdatetime + * @return 字符串 + */ + public static String localDateTimeToStringYear(LocalDateTime time) { + DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); + return df.format(time); + } + + /** + * Date转为LocalDateTime + * + * @param date 日期 + * @return LocalDateTime + */ + public static LocalDateTime dateToLocalDateTime(Date date) { + Instant instant = date.toInstant(); + ZoneId zoneId = ZoneId.systemDefault(); + return instant.atZone(zoneId).toLocalDateTime(); + } + + /** + * localdate转为字符串 + * + * @param time localdate + * @return 字符串 + */ + public static String localDateToString(LocalDate time) { + DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return df.format(time); + } + + /** + * 字符串转为localdate + * + * @param time localdate + * @return 字符串 + */ + public static LocalDate stringToLocalDate(String time) { + return LocalDate.parse(time, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java new file mode 100644 index 0000000..cc5eab2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java @@ -0,0 +1,186 @@ +package com.ruoyi.common.utils; + +import java.util.Collection; +import java.util.List; +import com.alibaba.fastjson2.JSONArray; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.spring.SpringUtils; + +/** + * 字典工具类 + * + * @author ruoyi + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List<SysDictData> dictDatas) + { + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List<SysDictData> getDictCache(String key) + { + JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) + { + return arrayCache.toList(SysDictData.class); + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + 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<SysDictData> datas = getDictCache(dictType); + + if (StringUtils.isNotNull(datas)) + { + if (StringUtils.containsAny(separator, dictValue)) + { + 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<SysDictData> datas = getDictCache(dictType); + + if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) + { + 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 key 字典键 + */ + public static void removeDictCache(String key) + { + SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String configKey) + { + return CacheConstants.SYS_DICT_KEY + configKey; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java new file mode 100644 index 0000000..214e4a0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/IDCardUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/IDCardUtils.java new file mode 100644 index 0000000..dde704d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/IDCardUtils.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.utils; + +public class IDCardUtils { + + public static void main(String[] args) { + String idCardNumber = "110101199001011234"; + String birthday = getBirthdayFromIdCard(idCardNumber); + System.out.println("出生日期为:" + birthday); + } + + public static String getBirthdayFromIdCard(String idCardNumber) { + String birthday = idCardNumber.substring(6, 14); + String year = birthday.substring(0, 4); + String month = birthday.substring(4, 6); + String day = birthday.substring(6, 8); + return year + "-" + month + "-" + day; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java new file mode 100644 index 0000000..0de30c6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.utils; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java new file mode 100644 index 0000000..7dac75a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +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()); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/MultipartFileUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MultipartFileUtil.java new file mode 100644 index 0000000..3759d6a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/MultipartFileUtil.java @@ -0,0 +1,222 @@ +//package com.ruoyi.common.utils; +// +//import cn.hutool.core.io.unit.DataSize; +//import cn.hutool.core.util.IdUtil; +//import cn.hutool.http.HttpRequest; +//import cn.hutool.http.HttpResponse; +//import com.google.common.collect.ImmutableList; +//import javafx.util.Duration; +//import org.apache.catalina.Manager; +//import org.apache.commons.io.FilenameUtils; +//import org.apache.commons.lang3.StringUtils; +//import org.apache.http.entity.ContentType; +//import org.springframework.mock.web.MockMultipartFile; +//import org.springframework.web.multipart.MultipartFile; +//import javafx.scene.media.Media; +//import javafx.scene.media.MediaPlayer; +//import ws.schild.jave.EncoderException; +//import ws.schild.jave.MultimediaInfo; +//import ws.schild.jave.MultimediaObject; +// +//import java.io.File; +// +//import java.io.*; +//import java.math.BigDecimal; +//import java.math.RoundingMode; +//import java.net.URL; +//import java.nio.channels.FileChannel; +//import java.util.ArrayList; +//import java.util.List; +// +///** +// * MultipartFile和File互转工具类 +// */ +//public class MultipartFileUtil { +// +// /** +// * 输入流转MultipartFile +// * +// * @param fileName +// * @param inputStream +// * @return +// */ +// public static MultipartFile getMultipartFile(String fileName, InputStream inputStream) { +// MultipartFile multipartFile = null; +// try { +// multipartFile = new MockMultipartFile(fileName, fileName, +// ContentType.APPLICATION_OCTET_STREAM.toString(), inputStream); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return multipartFile; +// } +// +// /** +// * 读取网络文件 +// * +// * @param url 文件地址 +// * @param fileName 文件名称(需带文件名后缀) +// * @return +// */ +// public static MultipartFile getMultipartFile(String url, String fileName) { +// HttpResponse response = HttpRequest.get(url).execute(); +// InputStream inputStream = response.bodyStream(); +// return MultipartFileUtil.getMultipartFile(fileName, inputStream); +// } +// +// /** +// * File 转MultipartFile +// * +// * @param file +// * @return +// */ +// public static MultipartFile getMultipartFile(File file) { +// FileInputStream fileInputStream = null; +// MultipartFile multipartFile = null; +// try { +// fileInputStream = new FileInputStream(file); +// multipartFile = new MockMultipartFile(file.getName(), file.getName(), +// ContentType.APPLICATION_OCTET_STREAM.toString(), fileInputStream); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return multipartFile; +// } +// +// /** +// * MultipartFileUtil 转File +// * +// * @param multipartFile +// * @return +// */ +// public static File getFile(MultipartFile multipartFile) { +// // 获取文件名 +// String fileName = multipartFile.getOriginalFilename(); +// // 获取文件后缀 +// String prefix = "." + getExtensionName(fileName); +// File file = null; +// try { +// // 用uuid作为文件名,防止生成的临时文件重复 +// file = File.createTempFile(IdUtil.simpleUUID(), prefix); +// // MultipartFile to File +// multipartFile.transferTo(file); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return file; +// } +// +// /** +// * 获取文件扩展名,不带 . +// */ +// public static String getExtensionName(String filename) { +// if ((filename != null) && (filename.length() > 0)) { +// int dot = filename.lastIndexOf('.'); +// if ((dot > -1) && (dot < (filename.length() - 1))) { +// return filename.substring(dot + 1); +// } +// } +// return filename; +// } +// +// /** +// 前端上传视频之后,根据上传的视频文件获取视频的大小和时长 +// 1、获取视频时长 +// */ +// public static int readVideoTime(File source) { +// int vedioSecond = Integer.parseInt(parseDuration(source.getAbsolutePath())); +// return vedioSecond; +// } +// +// +// /** +// * 视频时长 +// * +// * @param fileUrl +// * @return String[] 0=秒时长,1=展示时长(格式如 01:00:00) +// */ +// public static String parseDuration(String fileUrl) { +// long ls = 0L; +// String[] length = new String[2]; +// try { +// // +//// URL source = new URL(fileUrl); +// // 构造方法 接受URL对象 +//// MultimediaObject instance = new MultimediaObject(source); +// // 构造方法 接受File对象 +// MultimediaObject instance = new MultimediaObject(new File(fileUrl)); +// MultimediaInfo result = instance.getInfo(); +// ls = result.getDuration() / 1000; +// length[0] = String.valueOf(ls); +// Integer hour = (int) (ls / 3600); +// Integer minute = (int) (ls % 3600) / 60; +// Integer second = (int) (ls - hour * 3600 - minute * 60); +// String hr = hour.toString(); +// String mi = minute.toString(); +// String se = second.toString(); +// if (hr.length() < 2) { +// hr = "0" + hr; +// } +// if (mi.length() < 2) { +// mi = "0" + mi; +// } +// if (se.length() < 2) { +// se = "0" + se; +// } +// +// String noHour = "00"; +// if (noHour.equals(hr)) { +// length[1] = mi + ":" + se; +// } else { +// length[1] = hr + ":" + mi + ":" + se; +// } +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// System.out.println(length);//{"20","00:20"} +// return String.valueOf(ls); +// } +// +// +// +// +// +// +// /** +// * 2、获取文件大小 +// * @param source +// * @return +// * //***获取视频大小的时候,由于用到了流,使用完之后一定要及时的关闭流,避免无法删除视频文件*** +// * +// */ +// public static BigDecimal readFileSize(File source) { +// //cn.hutool.core.io.unit.DataSize.ofMegabytes() +// FileChannel fc= null; +// //String size = ""; +// BigDecimal size = null ; +// try { +// @SuppressWarnings("resource") +// FileInputStream fis = new FileInputStream(source); +// fc= fis.getChannel(); +// BigDecimal fileSize = new BigDecimal(fc.size()); +// //size = fileSize.divide(new BigDecimal(1048576), 2, RoundingMode.HALF_UP) + "MB"; +// size = fileSize.divide(new BigDecimal(1024*1024), 2, RoundingMode.HALF_UP) ; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } finally { +// if (null!=fc){ +// try{ +// fc.close(); +// }catch(IOException e){ +// e.printStackTrace(); +// } +// } +// } +// return size; +// } +// +// +//} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/OrderNos.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/OrderNos.java new file mode 100644 index 0000000..8a80490 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/OrderNos.java @@ -0,0 +1,164 @@ +package com.ruoyi.common.utils; + +import com.ruoyi.common.utils.ip.IpUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicLong; + +public class OrderNos { + + public static final char PADCHAR = '0'; + + + + /** + * 长度24位全球唯一标识 + */ + + /** + * 统一不重复内部订单号 + * + * @param systemCode + * 4字节系统编码 + * @return 24字节不重复编码 + */ + public static String oid(String systemCode) { + StringBuilder sb = new StringBuilder(); + sb.append(padding(systemCode, 4)); + sb.append(Did.getInstance().getId(20)); + return sb.toString(); + } + + public static String oid() { + return oid("O001"); + } + + /** + * 获取分布式Id + * + * @return 默认20字符长度的数字不重复编码 + */ + public static String getDid() { + return Did.getInstance().getId(); + } + + public static String getDid(int size) { + return Did.getInstance().getId(size); + } + + public static String getDid(String prefix) { + return prefix + Did.getInstance().getId(); + } + + private static String padding(String text, int size) { + String txt = StringUtils.trimToEmpty(text); + if (StringUtils.length(txt) > size) { + txt = StringUtils.substring(txt, StringUtils.length(txt) - size); + } + return StringUtils.leftPad(txt, size, PADCHAR); + } + + /** + * ID生成器 + * + * 20字符长度 yyMMddHHmmss+3位本机IP末三位+5位随机数字 + * + * @author zhangpu + * + */ + public static class Did { + private static final Logger logger = LoggerFactory.getLogger(OrderNos.class); + private static final int MIN_LENGTH = 19; + private static final int SEQU_MAX = 99999; + private static final int SEQU_19_MAX = 9999; + private volatile static long BASE = 0; + private AtomicLong sequence = new AtomicLong(0); + private String nodeFlag; + private Object nodeFlagLock = new Object(); + private static Did did = new Did(); + + + public static Did getInstance() { + return did; + } + + /** + * 生产新Id + * + * @return + */ + public String getId() { + return getId(20); + } + + public String getId(int size) { + if (size < MIN_LENGTH) { + throw new RuntimeException("did最小长度为" + MIN_LENGTH); + } + StringBuilder sb = new StringBuilder(); + sb.append(DateUtils.dateTimeNow("yyMMddHHmmss")); + sb.append(getNodeFlag()); + sb.append(getSequ(size-15)); + return sb.toString(); + } + + public synchronized String getSequ(int size) { + long timeCount = System.currentTimeMillis() / 1000; + + while (true) { + long now = sequence.get(); + if (timeCount > now) { + // 已经过了本秒,则设置新的值 + if (sequence.compareAndSet(now, timeCount)) { + break; + } + } else { + if (sequence.compareAndSet(now, now + 1)) { + timeCount = now + 1; + break; + } + } + } + int sn = (int) (timeCount % (size>=5?SEQU_MAX:SEQU_19_MAX)); + return StringUtils.leftPad(String.valueOf(sn), size, '0'); + } + + private String getNodeFlag(){ + if (this.nodeFlag == null) { + synchronized (Did.class){ + if (this.nodeFlag==null){ + this.nodeFlag = generateNodeFlag(); + } + } + } + return this.nodeFlag; + } + + /** + * 简单节点编码 + * + * 逻辑:Ip地址后三位,便于快速知道是哪个节点 + * + * @return + */ + private String generateNodeFlag() { + String ipPostfix = null; + try { + String ip = IpUtils.getFirstNoLoopbackIPV4Address(); + ipPostfix = StringUtils.substringAfterLast(ip, "."); + } catch (Exception e) { + logger.warn("生产DID要素本机IP获取失败:" + e.getMessage()); + } + return StringUtils.leftPad(ipPostfix, 3, "0"); + } + + private Did() { + super(); + } + } + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java new file mode 100644 index 0000000..8d1445b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java @@ -0,0 +1,45 @@ +//package com.ruoyi.common.utils; +// +//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 startPage(Integer pageNum,Integer pageSize) +// { +// PageDomain pageDomain = TableSupport.buildPageRequest(); +// String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); +// Boolean reasonable = pageDomain.getReasonable(); +// PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); +// } +// +// /** +// * 清理分页的线程变量 +// */ +// public static void clearPage() +// { +// PageHelper.clearPage(); +// } +//} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java new file mode 100644 index 0000000..6d0ea4a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java @@ -0,0 +1,163 @@ +package com.ruoyi.common.utils; + +import com.ruoyi.common.core.domain.model.LoginUserApplet; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @author ruoyi + */ +@Slf4j +public class SecurityUtils +{ + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + /** + * 获取营业部ID + **/ + public static String getBusinessDeptId() + { + try + { + return getLoginUser().getUser().getBusinessDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取营业部ID异常", HttpStatus.UNAUTHORIZED); + } + } + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + log.error("获取用户信息发生异常",e); +// throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + return ""; + } + /** + * 获取用户账户小程序 + **/ + public static String getUsernameApplet() + { + try + { + return getLoginUserApplet().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + public static LoginUserApplet getLoginUserApplet() + { + try + { + return (LoginUserApplet) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java new file mode 100644 index 0000000..febb603 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -0,0 +1,218 @@ +package com.ruoyi.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.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 +{ + /** + * 获取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); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map<String, String[]> getParams(ServletRequest request) + { + final Map<String, String[]> map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map<String, String> getParamMap(ServletRequest request) + { + Map<String, String> params = new HashMap<>(); + for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + + /** + * 获取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 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是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"); + } + + /** + * 内容编码 + * + * @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; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Util.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Util.java new file mode 100644 index 0000000..efe6ab4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Util.java @@ -0,0 +1,79 @@ +//package com.ruoyi.common.utils; +// +//import org.bouncycastle.crypto.engines.SM4Engine; +//import org.bouncycastle.crypto.modes.CBCBlockCipher; +//import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +//import org.bouncycastle.crypto.params.KeyParameter; +//import org.bouncycastle.crypto.params.ParametersWithIV; +// +//import java.util.Base64; +// +//public class Sm4Util { +// +// // 示例密钥,实际应用中应使用安全的方式生成和存储密钥 +// private static final String KEY = "0123456789abcdef"; +// // 示例向量,实际应用中应使用安全的方式生成和存储初始化向量 +// //需要确保KEY和IV保持16字节长度 +// private static final String IV = "fedcba9876543210"; +// +// /** +// * 使用 SM4 算法对明文进行加密。 +// * +// * @param plainText 要加密的明文字符串 +// * @return 加密后的 Base64 编码字符串 +// * @throws Exception 如果加密过程中发生错误 +// */ +// public static String encrypt(String plainText) throws Exception { +// // 创建一个带填充的缓冲块密码器,使用 CBC 模式和 SM4 引擎 +// PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new SM4Engine())); +// +// // 初始化密码器为加密模式,并设置密钥和初始化向量 +// cipher.init(true, new ParametersWithIV(new KeyParameter(KEY.getBytes()), IV.getBytes())); +// +// // 将明文转换为字节数组 +// byte[] input = plainText.getBytes(); +// // 计算输出缓冲区的大小 +// byte[] output = new byte[cipher.getOutputSize(input.length)]; +// +// // 进行分步加密 +// int length1 = cipher.processBytes(input, 0, input.length, output, 0); +// int length2 = cipher.doFinal(output, length1); +// +// // 返回加密后的内容,Base64 编码以便于传输和存储 +// return Base64.getEncoder().encodeToString(output); +// } +// +// public static void main(String[] args) throws Exception { +// String str = "hello world"; +// System.err.println(encrypt(str)); +// System.err.println(decrypt(str)); +// } +// +// /** +// * 使用 SM4 算法对密文进行解密。 +// * +// * @param encryptedText 加密后的 Base64 编码字符串 +// * @return 解密后的明文字符串 +// * @throws Exception 如果解密过程中发生错误 +// */ +// public static String decrypt(String encryptedText) throws Exception { +// // 创建一个带填充的缓冲块密码器,使用 CBC 模式和 SM4 引擎 +// PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new SM4Engine())); +// +// // 初始化密码器为解密模式,并设置密钥和初始化向量 +// cipher.init(false, new ParametersWithIV(new KeyParameter(KEY.getBytes()), IV.getBytes())); +// +// // 将 Base64 编码的密文解码为字节数组 +// byte[] input = Base64.getDecoder().decode(encryptedText); +// // 计算输出缓冲区的大小 +// byte[] output = new byte[cipher.getOutputSize(input.length)]; +// +// // 进行分步解密 +// int length1 = cipher.processBytes(input, 0, input.length, output, 0); +// int length2 = cipher.doFinal(output, length1); +// +// // 返回解密后的明文字符串 +// return new String(output, 0, length1 + length2); +// } +// +//} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Utils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Utils.java new file mode 100644 index 0000000..94de345 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Sm4Utils.java @@ -0,0 +1,58 @@ +package com.ruoyi.common.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.symmetric.SymmetricCrypto; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +/** + * @Author 国密4工具类 + * @Description 数据加密解密 + **/ +public class Sm4Utils { + + + // 示例密钥,实际应用中应使用安全的方式生成和存储密钥 + private static final String KEY = "2022lab02ora12to"; + /** + * sm4数据加密 + * @param params 参数信息 + * @return 加密后的值 + */ + public static String sm4EncryptUtil(String params){ + SymmetricCrypto sm4 = SmUtil.sm4(KEY.getBytes()); + return sm4.encryptHex(params); + } + + /** + * sm4数据解密 + * @param encryptContext 加密的内容 + * @return 解密后的值 + */ + public static String sm4DecryptUtil(String encryptContext){ + SymmetricCrypto sm4 = SmUtil.sm4(KEY.getBytes()); + return sm4.decryptStr(encryptContext, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 测试方法,测试完要记得删除掉 + */ + public static void main(String[] args) { + // 自定义秘钥 + String secretKey = "csdn1024CSDN1024"; + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", "csdn"); + jsonObject.put("desc","1024程序员节"); + + String strParams = JSON.toJSONString(jsonObject); + System.out.println(String.format("明文参数: %s", strParams)); + + String encryptContext = sm4EncryptUtil(strParams); + System.out.println(String.format("加密后的值: %s", encryptContext)); + String decryptInfo = sm4DecryptUtil("d2f1da26d73f31d8f66ea64b23beebe014345f504ca516579dce993e4a527b48bc242f17941c7cb6d3eeb60bfe6175a51dafb387fa67a655fb18084acf6f68d149b14b6b9bb0063a7714ae24df78e064250b7bcd7847a719a69328ffaaa2d3add6d002b44fcc4610f3661f7611031cd410325c8391a7227ded9f6c4ec8d8a9908ae19c93c4fb2a16501738055acbea0925500bf691703c2ad4e1b172679d38da"); + System.out.println(String.format("解密后的信息: %s", decryptInfo)); + } + +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtil.java new file mode 100644 index 0000000..c558969 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtil.java @@ -0,0 +1,79 @@ +package com.ruoyi.common.utils; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.config.SmsProperties; +import com.ruoyi.common.exception.ServiceException; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.sms.v20190711.SmsClient; +import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest; +import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +@Component +@Slf4j +public class SmsUtil { + + @Resource + SmsProperties smsProperties; + static SmsClient smsClient; + + + public SmsProperties getPro() { + return smsProperties; + } + + @PostConstruct + public void createSmsClient() { + // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId,SecretKey。 + // 为了保护密钥安全,建议将密钥设置在环境变量中或者配置文件中,请参考凭证管理 https://github.com/TencentCloud/tencentcloud-sdk-java?tab=readme-ov-file#%E5%87%AD%E8%AF%81%E7%AE%A1%E7%90%86。 + // 硬编码密钥到代码中有可能随代码泄露而暴露,有安全隐患,并不推荐。 + // SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi + // Credential cred = new Credential("SecretId", "SecretKey"); + + Credential cred = new Credential(smsProperties.getSecretid(), smsProperties.getSecretkey()); + // 实例化一个http选项,可选的,没有特殊需求可以跳过 + HttpProfile httpProfile = new HttpProfile(); + // 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com + httpProfile.setEndpoint("sms.tencentcloudapi.com"); + // 实例化一个客户端配置对象 + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + // 实例化要请求产品(sms)的client对象,第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 + smsClient = new SmsClient(cred, "ap-guangzhou", clientProfile); + } + + public boolean sendSms(String phone,String templateId,String[] param){ + phone = phone.startsWith("+86")?phone:("+86"+phone); + SendSmsRequest req = new SendSmsRequest(); + req.setSmsSdkAppid(smsProperties.getAppId()); + req.setPhoneNumberSet(new String[]{phone}); + req.setTemplateID(templateId); + req.setSign(smsProperties.getSign()); + req.setTemplateParamSet(param); + req.setSenderId(""); + req.setSessionContext(""); + req.setExtendCode(""); + try { + SendSmsResponse sendSmsResponse = smsClient.SendSms(req); + System.out.println(JSON.toJSONString(sendSmsResponse)); + return true; + } catch (TencentCloudSDKException e) { + log.error("发送短信失败,{},{}",phone,param,e); + throw new ServiceException("发送短信失败"); + } catch (Exception e){ + log.error("发送短信失败1,{},{}",phone,param,e); + throw new ServiceException("发送短信失败1"); + } + } + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java new file mode 100644 index 0000000..e683d54 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -0,0 +1,614 @@ +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.springframework.util.AntPathMatcher; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static <T> 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 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); + } + + /** + * 格式化文本, {} 表示占位符<br> + * 此方法只是简单将占位符 {} 按照顺序替换为参数<br> + * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br> + * 例:<br> + * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br> + * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br> + * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br> + * + * @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<String> str2Set(String str, String sep) + { + return new HashSet<String>(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List<String> list = new ArrayList<String>(); + 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; + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection<String> 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; + } + + /** + * 驼峰转下划线命名 + */ + 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; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如: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<String> 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> 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(); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentMailUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentMailUtil.java new file mode 100644 index 0000000..22aad9d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TencentMailUtil.java @@ -0,0 +1,207 @@ +package com.ruoyi.common.utils; + +import com.ruoyi.common.config.MailProperties; +import com.ruoyi.common.exception.ServiceException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.activation.DataHandler; +import javax.activation.FileDataSource; +import javax.annotation.Resource; +import javax.mail.*; +import javax.mail.internet.*; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +@Component +@Slf4j +public class TencentMailUtil { + + @Autowired + MailProperties properties; + + public MailProperties getPro() { + return properties; + } + + /** + * + * @param emailAddress 邮件接收者email地址 + * @param param 用户房屋地址参数 + */ + public void send(String emailAddress,String param){ + // 配置发送邮件的环境属性 + final Properties props = new Properties(); + // 表示SMTP发送邮件,需要进行身份验证 + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.host", properties.getSmtpHost()); + // 如果使用ssl,则去掉使用25端口的配置,进行如下配置, + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.port", properties.getSmtpPort()); + props.put("mail.smtp.port", properties.getSmtpPort()); + // 发件人的账号,填写控制台配置的发信地址,比如xxx@xxx.com + props.put("mail.user", properties.getUserAddr()); + // 访问SMTP服务时需要提供的密码(在控制台选择发信地址进行设置) + props.put("mail.password", properties.getPassword()); + props.setProperty("mail.smtp.socketFactory.fallback", "false"); + props.put("mail.smtp.ssl.enable", "true"); + props.put("mail.smtp.ssl.protocols", "TLSv1.2"); + // 构建授权信息,用于进行SMTP进行身份验证 + Authenticator authenticator = new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + // 用户名、密码 + String userName = props.getProperty("mail.user"); + String password = props.getProperty("mail.password"); + return new PasswordAuthentication(userName, password); + } + }; + // 使用环境属性和授权信息,创建邮件会话 + Session mailSession = Session.getInstance(props, authenticator); +// mailSession.setDebug(true); + //UUID uuid = UUID.randomUUID(); + //final String messageIDValue = "<" + uuid.toString() + ">"; + // 创建邮件消息 + MimeMessage message = new MimeMessage(mailSession) { + //@Override + //protected void updateMessageID() throws MessagingException { + //设置自定义Message-ID值 + //setHeader("Message-ID", messageIDValue); + //} + }; + try { + // 设置发件人邮件地址和名称。填写控制台配置的发信地址,比如xxx@xxx.com。和上面的mail.user保持一致。名称用户可以自定义填写。 + InternetAddress from = new InternetAddress(properties.getUserAddr(), properties.getUserName()); + message.setFrom(from); + //可选。设置回信地址 +// Address[] a = new Address[1]; +// a[0] = new InternetAddress("***"); +// message.setReplyTo(a); + // 设置收件人邮件地址,比如yyy@yyy.com + InternetAddress to = new InternetAddress(emailAddress); + message.setRecipient(MimeMessage.RecipientType.TO, to); + //如果同时发给多人,才将上面两行替换为如下(因为部分收信系统的一些限制,尽量每次投递给一个人;同时我们限制单次允许发送的人数是50人): + // 设置邮件标题 + message.setSubject("您的"+param+"账单提醒"); + message.setHeader("Content-Transfer-Encoding", "base64"); + // 设置邮件的内容体 type: text/plain(纯文本)text/html(HTML 文档) + message.setContent("邻居您好!您"+param+",提醒您有账单需要处理,如已处理请忽略此短信,感谢您的支持!如有疑问请详询:4008888888。", "text/html;charset=UTF-8"); + //发送邮件 + Transport.send(message); + } catch (MessagingException | UnsupportedEncodingException e) { + e.printStackTrace(); + log.error("发送邮件发生异常",e); + throw new ServiceException("发送邮件失败,请检查"); + }catch (Exception e){ + e.printStackTrace(); + log.error("发送邮件发生异常",e); + } + } + + public void sendInvoice(String emailAddress, List<Map<String, String>> list) { + // 异步发送邮件 + CompletableFuture.runAsync(() -> { + try { + sendEmail(emailAddress, list); + } catch (ServiceException e) { + log.error("邮件发送失败", e); + } + }); + } + + private void sendEmail(String emailAddress, List<Map<String, String>> list) throws ServiceException { + try { + // 创建邮件会话 + Session mailSession = Session.getInstance(getMailProperties(), getAuthenticator()); + // 创建邮件消息 + MimeMessage message = new MimeMessage(mailSession); + // 设置发件人 + InternetAddress from = new InternetAddress(properties.getUserAddr(), properties.getUserName()); + message.setFrom(from); + // 设置收件人 + InternetAddress to = new InternetAddress(emailAddress); + message.setRecipient(MimeMessage.RecipientType.TO, to); + // 设置邮件标题 + message.setSubject("发票"); + // 创建邮件内容 + Multipart multipart = new MimeMultipart(); + // 添加文本消息部分 + BodyPart messageBodyPart = new MimeBodyPart(); + messageBodyPart.setHeader("Content-Type", "text/plain;charset=utf-8"); + messageBodyPart.setContent("您在小程序提交的开票申请已开票成功,请查看附件内容","text/html;charset=UTF-8"); + multipart.addBodyPart(messageBodyPart); + List<Path> tempFilePath = new ArrayList<>(); + // 添加附件部分 + for (Map<String, String> map : list) { + messageBodyPart = new MimeBodyPart(); + String filePath = map.get("filePath"); + String fileName = map.get("fileName"); + tempFilePath.add(Paths.get(filePath,fileName)); + FileDataSource source = new FileDataSource(filePath+"\\"+fileName); + messageBodyPart.setDataHandler(new DataHandler(source)); + // String filenameEncode = MimeUtility.encodeText(fileName, "UTF-8", "base64"); + // String encodedFileName = Base64.getEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8)); + // String filenameEncode = MimeUtility.encodeText(encodedFileName); + messageBodyPart.setFileName(fileName); + messageBodyPart.setHeader("Content-Transfer-Encoding", "base64"); + messageBodyPart.setHeader("Content-Disposition", "attachment"); + messageBodyPart.setHeader("Content-Type", "application/octet-stream;name=\"" + fileName + "\""); + multipart.addBodyPart(messageBodyPart); + } + // 设置邮件内容 + message.setContent(multipart); + // 发送邮件 + Transport.send(message); + // 删除临时目录里面的文件 + for (Path path : tempFilePath) { + Files.deleteIfExists(path); + } + } catch (MessagingException | UnsupportedEncodingException | MalformedURLException e) { + log.error("发送邮件发生异常", e); + throw new ServiceException("发送邮件失败, 请检查"); + } catch (IOException e) { + throw new RuntimeException("文件下载发生异常"); + } + } + + private Properties getMailProperties() { + Properties props = new Properties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.host", properties.getSmtpHost()); + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.port", properties.getSmtpPort()); + props.put("mail.smtp.port", properties.getSmtpPort()); + props.put("mail.user", properties.getUserAddr()); + props.put("mail.password", properties.getPassword()); + props.setProperty("mail.smtp.socketFactory.fallback", "false"); + props.put("mail.smtp.ssl.enable", "true"); + props.put("mail.smtp.ssl.protocols", "TLSv1.2"); + return props; + } + + private Authenticator getAuthenticator() { + return new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + String userName = properties.getUserAddr(); + String password = properties.getPassword(); + return new PasswordAuthentication(userName, password); + } + }; + } + + // public static void main(String[] args) throws UnsupportedEncodingException { + // TencentMailUtil tencentMailUtil = new TencentMailUtil(); + // MailProperties properties = new MailProperties(); + // tencentMailUtil.properties = properties; + // tencentMailUtil.send("645025773@qq.com","大学城揽院24栋"); + // } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java new file mode 100644 index 0000000..71fe6d5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java @@ -0,0 +1,99 @@ +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); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/TimeConverter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TimeConverter.java new file mode 100644 index 0000000..6715f60 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/TimeConverter.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.utils; + +import java.util.concurrent.TimeUnit; + +public class TimeConverter { + + public static String convertSecondsToHMS(Long seconds) { + long hours = TimeUnit.SECONDS.toHours(seconds); + long minutes = TimeUnit.SECONDS.toMinutes(seconds) % 60; + long remainingSeconds = seconds % 60; + return String.format("%02d",hours) + ":" + String.format("%02d",minutes) + ":" + String.format("%02d",remainingSeconds); + } + + public static void main(String[] args) { + Long totalSeconds = 44871L; + String hms = convertSecondsToHMS(totalSeconds); + System.out.println(hms); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/VideoUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/VideoUtil.java new file mode 100644 index 0000000..4b6b739 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/VideoUtil.java @@ -0,0 +1,69 @@ +package com.ruoyi.common.utils; + + +import lombok.extern.slf4j.Slf4j; +import ws.schild.jave.MultimediaInfo; +import ws.schild.jave.MultimediaObject; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; + +@Slf4j +public class VideoUtil { + + + /** + * 视频时长 + * + * @param fileUrl + * @return String[] 0=秒时长,1=展示时长(格式如 01:00:00) + */ + public static String[] parseDuration(String fileUrl) { + String[] length = new String[2]; + try { + // +// URL source = new URL(fileUrl); + // 构造方法 接受URL对象 +// MultimediaObject instance = new MultimediaObject(source); + // 构造方法 接受File对象 + MultimediaObject instance = new MultimediaObject(new File(fileUrl)); + MultimediaInfo result = instance.getInfo(); + Long ls = result.getDuration() / 1000; + length[0] = String.valueOf(ls); + Integer hour = (int) (ls / 3600); + Integer minute = (int) (ls % 3600) / 60; + Integer second = (int) (ls - hour * 3600 - minute * 60); + String hr = hour.toString(); + String mi = minute.toString(); + String se = second.toString(); + if (hr.length() < 2) { + hr = "0" + hr; + } + if (mi.length() < 2) { + mi = "0" + mi; + } + if (se.length() < 2) { + se = "0" + se; + } + + String noHour = "00"; + if (noHour.equals(hr)) { + length[1] = mi + ":" + se; + } else { + length[1] = hr + ":" + mi + ":" + se; + } + + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return length; + } + + public static void main(String[] args) { + String url = "C:/Users/Admin/Desktop/qrcode/2023-10-25/video.mp4"; + String[] strings = parseDuration(url); + System.err.println(Arrays.toString(strings)); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/WebUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/WebUtils.java new file mode 100644 index 0000000..7397e87 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/WebUtils.java @@ -0,0 +1,170 @@ +package com.ruoyi.common.utils; + + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.Map; + + +/** + * web工具类 + * + * @author liheng + */ +public final class WebUtils extends org.springframework.web.util.WebUtils { + + /** + * 当前请求 + */ + public static HttpServletRequest request() { + return contextHolder() == null ? null : contextHolder().getRequest(); + } + + /** + * 当前响应 + */ + public static HttpServletResponse response() { + return contextHolder() == null ? null : contextHolder().getResponse(); + } + + /** + * 当前session + */ + public static HttpSession session() { + return request() == null ? null : request().getSession(); + } + + /** + * 当前ServletRequest + */ + public static ServletRequestAttributes contextHolder() { + return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + } + + /** + * 判断请求是否为 AJAX + * + * @param request 当前请求 + */ + public static boolean isAjax(HttpServletRequest request) { + return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); + } + + /** + * 获取操作系统,浏览器及浏览器版本信息 + * + * @param request + * @return + */ + public static Map<String, String> getOsAndBrowserInfo(HttpServletRequest request) { + Map<String, String> map = new HashMap<>(2); + String browserDetails = request.getHeader("User-Agent"); + String userAgent = browserDetails; + String user = userAgent.toLowerCase(); + + String os = ""; + String browser = ""; + //=================OS Info======================= + if (userAgent.toLowerCase().contains("windows")) { + os = "Windows"; + } else if (userAgent.toLowerCase().contains("mac")) { + os = "Mac"; + } else if (userAgent.toLowerCase().contains("x11")) { + os = "Unix"; + } else if (userAgent.toLowerCase().contains("android")) { + os = "Android"; + } else if (userAgent.toLowerCase().contains("iphone")) { + os = "IPhone"; + } else { + os = "UnKnown, More-Info: " + userAgent; + } + //===============Browser=========================== + if (user.contains("edge")) { + browser = (userAgent.substring(userAgent.indexOf("Edge")).split(" ")[0]).replace("/", "-"); + } else if (user.contains("msie")) { + String substring = userAgent.substring(userAgent.indexOf("MSIE")).split(";")[0]; + browser = substring.split(" ")[0].replace("MSIE", "IE") + "-" + substring.split(" ")[1]; + } else if (user.contains("safari") && user.contains("version")) { + browser = (userAgent.substring(userAgent.indexOf("Safari")).split(" ")[0]).split("/")[0] + + "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1]; + } else if (user.contains("opr") || user.contains("opera")) { + if (user.contains("opera")) { + browser = (userAgent.substring(userAgent.indexOf("Opera")).split(" ")[0]).split("/")[0] + + "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1]; + } else if (user.contains("opr")) { + browser = ((userAgent.substring(userAgent.indexOf("OPR")).split(" ")[0]).replace("/", "-")) + .replace("OPR", "Opera"); + } + } else if (user.contains("chrome")) { + browser = (userAgent.substring(userAgent.indexOf("Chrome")).split(" ")[0]).replace("/", "-"); + } else if ((user.contains("mozilla/7.0")) || (user.contains("netscape6")) || + (user.contains("mozilla/4.7")) || (user.contains("mozilla/4.78")) || + (user.contains("mozilla/4.08")) || (user.contains("mozilla/3"))) { + browser = "Netscape-?"; + } else if (user.contains("firefox")) { + browser = (userAgent.substring(userAgent.indexOf("Firefox")).split(" ")[0]).replace("/", "-"); + } else if (user.contains("rv")) { + String IEVersion = (userAgent.substring(userAgent.indexOf("rv")).split(" ")[0]).replace("rv:", "-"); + browser = "IE" + IEVersion.substring(0, IEVersion.length() - 1); + } else { + browser = "UnKnown, More-Info: " + userAgent; + } + map.put("os", os); + map.put("browser", browser); + return map; + } + + /** + * 读取cookie + * + * @param name cookie name + * @return cookie value + */ + public String getCookieVal(String name) { + return getCookieVal(request(), name); + } + + /** + * 读取cookie + * + * @param request HttpServletRequest + * @param name cookie name + * @return cookie value + */ + public String getCookieVal(HttpServletRequest request, String name) { + Cookie cookie = getCookie(request, name); + return cookie != null ? cookie.getValue() : null; + } + + /** + * 清除 某个指定的cookie + * + * @param response HttpServletResponse + * @param key cookie key + */ + public void removeCookie(HttpServletResponse response, String key) { + setCookie(response, key, null, 0); + } + + /** + * 设置cookie + * + * @param response HttpServletResponse + * @param name cookie name + * @param value cookie value + * @param maxAgeInSeconds maxage + */ + public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setMaxAge(maxAgeInSeconds); + //cookie.setHttpOnly(true); + response.addCookie(cookie); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/WxAppletTools.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/WxAppletTools.java new file mode 100644 index 0000000..7c6ee85 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/WxAppletTools.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.utils; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.config.WxConfig; +import com.ruoyi.common.core.redis.RedisCache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.text.MessageFormat; + +/** + * @author liheng + * @ClassName WxAppletTools + * @Description + * @date 2020-12-04 13:55 + */ +@Slf4j +@Component +public class WxAppletTools { + @Autowired + private RedisCache redisCache; + @Autowired + private RestTemplate restTemplate; + @Autowired + private WxConfig wxConfig; + private final static String ACCESSTOKEN_CACHE_KEY = "accessToken"; + /** + * 请求参数 + * 属性 类型 默认值 必填 说明 + * appid string 是 小程序 appId + * secret string 是 小程序 appSecret + * js_code string 是 登录时获取的 code + * grant_type string 是 授权类型,此处只需填写 authorization_cod + * <p> + * 返回值: + * <p> + * 属性 类型 说明 + * openid string 用户唯一标识 + * session_key string 会话密钥 + * unionid string 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。 + * errcode number 错误码 + * errmsg string 错误信息 + */ + private static final String JSCODE_2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code"; + + + /** + * 请求参数 + * 属性 类型 默认值 必填 说明 + * grant_type string 是 填写 client_credential + * appid string 是 小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态) + * secret string 是 小程序唯一凭证密钥,即 AppSecret,获取方式同 appid + * 返回值 + * Object + * 返回的 JSON 数据包 + * <p> + * 属性 类型 说明 + * access_token string 获取到的凭证 + * expires_in number 凭证有效时间,单位:秒。目前是7200秒之内的值。 + * errcode number 错误码 + * errmsg string 错误信息 + */ + private static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}"; + + /** + * @return + */ + public String getAccessToken() { + String requestUrl = MessageFormat.format(ACCESS_TOKEN_URL, wxConfig.getAppId(), wxConfig.getSecret()); + String respBody = restTemplate.getForEntity(requestUrl, String.class).getBody(); + JSONObject jsonObject = JSONObject.parseObject(respBody); + return jsonObject.getString("access_token"); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java new file mode 100644 index 0000000..4463662 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +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<Method> getSetterMethods(Object obj) + { + // setter方法列表 + List<Method> setterMethods = new ArrayList<Method>(); + + // 获取所有方法 + 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<Method> getGetterMethods(Object obj) + { + // getter方法列表 + List<Method> getterMethods = new ArrayList<Method>(); + // 获取所有方法 + 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方法名中的属性名是否相等。<br> + * 如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)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java new file mode 100644 index 0000000..80bfed7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class<?>... groups) + throws ConstraintViolationException + { + Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..68130b9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + * <p> + * 例如: ruoyi.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + * <p> + * 例如: 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; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..d9f2b13 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java @@ -0,0 +1,232 @@ +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.Seq; + +/** + * 文件上传工具类 + * + * @author ruoyi + */ +public class FileUploadUtils +{ + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 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 + { + 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 = 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)); + } + + 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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java new file mode 100644 index 0000000..ed4cbc9 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -0,0 +1,291 @@ +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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; +import org.apache.commons.io.FilenameUtils; + +/** + * 文件处理工具类 + * + * @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 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 真实文件名 + */ + 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.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java new file mode 100644 index 0000000..facaf74 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java @@ -0,0 +1,93 @@ +package com.ruoyi.common.utils.file; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Arrays; + +/** + * 图片处理工具类 + * + * @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); +// } + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..f968f1a --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +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 ""; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..f52e83e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +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 = "<script>alert(1);</script>"; + String escape = EscapeUtil.escape(html); + // String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>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)); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..ebff3fd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +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<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map<String, List<String>> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map<String, Integer> vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "<img />") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "<b></b>") + **/ + 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. "<b></b>" or "<b />") + **/ + 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. "<b text </b>" + * becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList<String> a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList<String> 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<String> 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<String, Object> 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<String, List<String>>) 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("<!--" + htmlSpecialChars(match) + "-->")); + } + 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("</").append(key).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[^>]*)?></" + tag + ">")); + } + 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 (!inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return "</" + name + ">"; + } + } + } + } + + // 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<String> paramNames = new ArrayList<>(); + final List<String> 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)); + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java new file mode 100644 index 0000000..589d123 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author ruoyi + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..f85c82c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -0,0 +1,274 @@ +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; + +/** + * 通用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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + 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) + { + 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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + 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) + { + 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/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + 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; + } + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java new file mode 100644 index 0000000..edfe419 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.ruoyi.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; +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 = "http://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 = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java new file mode 100644 index 0000000..8874697 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java @@ -0,0 +1,454 @@ +package com.ruoyi.common.utils.ip; + +import java.net.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import javax.servlet.http.HttpServletRequest; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +@Slf4j +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 + * + * @return IP地址 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + + /** + * 获取客户端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; + } + + + private static final String LOOP_BACK = "127.0.0.1"; + private static String firstNoLoopbackIPV4Address = null; + + private static Collection<InetAddress> allHostIPV4Address = null; + /** + * 获取ipv4地址,如果有多个网卡的情况,获取第一个非loopback ip地址 + * + * @return + */ + public static String getFirstNoLoopbackIPV4Address() { + if (firstNoLoopbackIPV4Address != null) { + return firstNoLoopbackIPV4Address; + } + Collection<String> allNoLoopbackAddresses = null; + try { + allNoLoopbackAddresses = getAllNoLoopbackIPV4Addresses(); + } catch (Exception e) { + log.error("获取ip失败", e); + return LOOP_BACK; + } + if (allNoLoopbackAddresses.isEmpty()) { + return LOOP_BACK; + } + + return firstNoLoopbackIPV4Address = allNoLoopbackAddresses.iterator().next(); + } + + + public static Collection<String> getAllNoLoopbackIPV4Addresses() { + Collection<String> noLoopbackAddresses = new ArrayList<String>(); + Collection<InetAddress> allInetAddresses = getAllHostIPV4Address(); + + for (InetAddress address : allInetAddresses) { + if (!address.isLoopbackAddress()) { + noLoopbackAddresses.add(address.getHostAddress()); + } + } + + return noLoopbackAddresses; + } + + public static Collection<InetAddress> getAllHostIPV4Address() { + if (allHostIPV4Address == null) { + try { + Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); + Collection<InetAddress> addresses = new ArrayList<InetAddress>(); + + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress instanceof Inet4Address) { + addresses.add(inetAddress); + } + } + } + allHostIPV4Address = addresses; + } catch (SocketException e) { + log.error("获取ip地址失败", e); + throw new RuntimeException(e.getMessage(), e); + } + + } + return allHostIPV4Address; + } +} \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..4c189fd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.poi; + + +import javafx.scene.control.Cell; + +/** + * 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); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..0c89771 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1679 @@ +package com.ruoyi.common.utils.poi; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * Excel相关处理 + * + * @author ruoyi + */ +public class ExcelUtil<T> +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 用于dictType属性数据存储,避免重复查缓存 + */ + public Map<String, String> sysDictMap = new HashMap<String, String>(); + + /** + * 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<String, CellStyle> styles; +// +// /** +// * 导入导出数据列表 +// */ +// private List<T> list; +// +// /** +// * 注解列表 +// */ +// private List<Object[]> fields; +// +// /** +// * 当前行号 +// */ +// private int rownum; +// +// /** +// * 标题 +// */ +// private String title; +// +// /** +// * 最大高度 +// */ +// private short maxHeight; +// +// /** +// * 合并后最后行数 +// */ +// private int subMergedLastRowNum = 0; +// +// /** +// * 合并后开始行数 +// */ +// private int subMergedFirstRowNum = 1; +// +// /** +// * 对象的子列表方法 +// */ +// private Method subMethod; +// +// /** +// * 对象的子列表属性 +// */ +// private List<Field> subFields; +// +// /** +// * 统计列表 +// */ +// private Map<Integer, Double> statistics = new HashMap<Integer, Double>(); +// +// /** +// * 数字格式 +// */ +// private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); +// +// /** +// * 实体对象 +// */ +// public Class<T> clazz; +// +// /** +// * 需要排除列属性 +// */ +// public String[] excludeFields; +// +// public ExcelUtil(Class<T> clazz) +// { +// this.clazz = clazz; +// } +// +// /** +// * 隐藏Excel中列属性 +// * +// * @param fields 列属性名 示例[单个"name"/多个"id","name"] +// * @throws Exception +// */ +// public void hideColumn(String... fields) +// { +// this.excludeFields = fields; +// } +// +// public void init(List<T> list, String sheetName, String title, Type type) +// { +// if (list == null) +// { +// list = new ArrayList<T>(); +// } +// this.list = list; +// this.sheetName = sheetName; +// this.type = type; +// this.title = title; +// createExcelField(); +// createWorkbook(); +// createTitle(); +// createSubHead(); +// } +// +// /** +// * 创建excel第一行标题 +// */ +// public void createTitle() +// { +// if (StringUtils.isNotEmpty(title)) +// { +// subMergedFirstRowNum++; +// subMergedLastRowNum++; +// int titleLastCol = this.fields.size() - 1; +// if (isSubList()) +// { +// titleLastCol = titleLastCol + subFields.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(), titleRow.getRowNum(), titleLastCol)); +// } +// } +// +// /** +// * 创建对象的子列表名称 +// */ +// public void createSubHead() +// { +// if (isSubList()) +// { +// subMergedFirstRowNum++; +// subMergedLastRowNum++; +// Row subRow = sheet.createRow(rownum); +// int excelNum = 0; +// for (Object[] objects : fields) +// { +// Excel attr = (Excel) objects[1]; +// Cell headCell1 = subRow.createCell(excelNum); +// headCell1.setCellValue(attr.name()); +// headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); +// excelNum++; +// } +// int headFirstRow = excelNum - 1; +// int headLastRow = headFirstRow + subFields.size() - 1; +// if (headLastRow > headFirstRow) +// { +// sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow)); +// } +// rownum++; +// } +// } +// +// /** +// * 对excel表单默认第一个索引名转换成list +// * +// * @param is 输入流 +// * @return 转换后集合 +// */ +// public List<T> importExcel(InputStream is) +// { +// List<T> list = null; +// try +// { +// list = importExcel(is, 0); +// } +// catch (Exception e) +// { +// log.error("导入Excel异常{}", e.getMessage()); +// throw new UtilException(e.getMessage()); +// } +// finally +// { +// IOUtils.closeQuietly(is); +// } +// return list; +// } +// +// /** +// * 对excel表单默认第一个索引名转换成list +// * +// * @param is 输入流 +// * @param titleNum 标题占用行数 +// * @return 转换后集合 +// */ +// public List<T> importExcel(InputStream is, int titleNum) throws Exception +// { +// return importExcel(StringUtils.EMPTY, is, titleNum); +// } +// +// /** +// * 对excel表单指定表格索引名转换成list +// * +// * @param sheetName 表格索引名 +// * @param titleNum 标题占用行数 +// * @param is 输入流 +// * @return 转换后集合 +// */ +// public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception +// { +// this.type = Type.IMPORT; +// this.wb = WorkbookFactory.create(is); +// List<T> list = new ArrayList<T>(); +// // 如果指定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<String, PictureData> pictures; +// 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<String, Integer> cellMap = new HashMap<String, Integer>(); +// // 获取表头 +// Row heard = sheet.getRow(titleNum); +// for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) +// { +// Cell cell = heard.getCell(i); +// if (StringUtils.isNotNull(cell)) +// { +// String value = this.getCellValue(heard, i).toString(); +// cellMap.put(value, i); +// } +// else +// { +// cellMap.put(null, i); +// } +// } +// // 有数据时才处理 得到类的所有field. +// List<Object[]> fields = this.getFields(); +// Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>(); +// 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<Integer, Object[]> entry : fieldsMap.entrySet()) +// { +// Object val = this.getCellValue(row, entry.getKey()); +// +// // 如果不存在实例则新建. +// entity = (entity == null ? clazz.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 (StringUtils.endsWith(s, ".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())) +// { +// val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); +// } +// else if (!attr.handler().equals(ExcelHandlerAdapter.class)) +// { +// val = dataFormatHandlerAdapter(val, attr, null); +// } +// else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) +// { +// PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); +// if (image == null) +// { +// val = ""; +// } +// else +// { +// byte[] data = image.getData(); +// val = FileUtils.writeImportBytes(data); +// } +// } +// ReflectUtils.invokeSetter(entity, propertyName, val); +// } +// } +// list.add(entity); +// } +// } +// return list; +// } +// +// /** +// * 对list数据源将其里面的数据导入到excel表单 +// * +// * @param list 导出数据集合 +// * @param sheetName 工作表的名称 +// * @return 结果 +// */ +// public AjaxResult exportExcel(List<T> list, String sheetName) +// { +// return exportExcel(list, sheetName, StringUtils.EMPTY); +// } +// +// /** +// * 对list数据源将其里面的数据导入到excel表单 +// * +// * @param list 导出数据集合 +// * @param sheetName 工作表的名称 +// * @param title 标题 +// * @return 结果 +// */ +// public AjaxResult exportExcel(List<T> 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<T> 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<T> 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())) +// { +// for (Field subField : subFields) +// { +// Excel subExcel = subField.getAnnotation(Excel.class); +// this.createHeadCell(subExcel, row, column++); +// } +// } +// else +// { +// this.createHeadCell(excel, row, column++); +// } +// } +// if (Type.EXPORT.equals(type)) +// { +// fillExcelData(index, row); +// addStatisticsRow(); +// } +// } +// } +// +// /** +// * 填充excel数据 +// * +// * @param index 序号 +// * @param row 单元格行 +// */ +// @SuppressWarnings("unchecked") +// public void fillExcelData(int index, Row row) +// { +// int startNo = index * sheetSize; +// int endNo = Math.min(startNo + sheetSize, list.size()); +// int rowNo = (1 + rownum) - startNo; +// for (int i = startNo; i < endNo; i++) +// { +// rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo; +// row = sheet.createRow(rowNo); +// // 得到导出对象. +// T vo = (T) list.get(i); +// Collection<?> subList = null; +// if (isSubList()) +// { +// if (isSubListValue(vo)) +// { +// subList = getListCellValue(vo); +// subMergedLastRowNum = subMergedLastRowNum + subList.size(); +// } +// else +// { +// subMergedFirstRowNum++; +// subMergedLastRowNum++; +// } +// } +// int column = 0; +// for (Object[] os : fields) +// { +// Field field = (Field) os[0]; +// Excel excel = (Excel) os[1]; +// if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) +// { +// boolean subFirst = false; +// for (Object obj : subList) +// { +// if (subFirst) +// { +// rowNo++; +// row = sheet.createRow(rowNo); +// } +// List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class); +// int subIndex = 0; +// for (Field subField : subFields) +// { +// if (subField.isAnnotationPresent(Excel.class)) +// { +// subField.setAccessible(true); +// Excel attr = subField.getAnnotation(Excel.class); +// this.addCell(attr, row, (T) obj, subField, column + subIndex); +// } +// subIndex++; +// } +// subFirst = true; +// } +// this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); +// } +// else +// { +// this.addCell(excel, row, vo, field, column++); +// } +// } +// } +// } +// +// /** +// * 创建表格样式 +// * +// * @param wb 工作薄对象 +// * @return 样式列表 +// */ +// private Map<String, CellStyle> createStyles(Workbook wb) +// { +// // 写入各条记录,每条记录对应excel表中的一行 +// Map<String, CellStyle> styles = new HashMap<String, CellStyle>(); +// 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); +// 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); +// 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<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles) +// { +// Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>(); +// 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); +// headerStyles.put(key, style); +// } +// } +// return headerStyles; +// } +// +// /** +// * 根据Excel注解创建表格列样式 +// * +// * @param wb 工作薄对象 +// * @return 自定义样式列表 +// */ +// private Map<String, CellStyle> annotationDataStyles(Workbook wb) +// { +// Map<String, CellStyle> styles = new HashMap<String, CellStyle>(); +// for (Object[] os : fields) +// { +// Excel excel = (Excel) os[1]; +// String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor()); +// 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()); +// Font dataFont = wb.createFont(); +// dataFont.setFontName("Arial"); +// dataFont.setFontHeightInPoints((short) 10); +// dataFont.setColor(excel.color().index); +// style.setFont(dataFont); +// styles.put(key, style); +// } +// } +// return styles; +// } +// +// /** +// * 创建单元格 +// */ +// 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()))); +// 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()) +// { +// 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 imagePath = Convert.toStr(value); +// if (StringUtils.isNotEmpty(imagePath)) +// { +// 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) +// { +// if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255) +// { +// // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 +// setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); +// } +// else +// { +// // 提示信息或只能选择不能输入的列内容. +// setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); +// } +// } +// } +// +// /** +// * 添加单元格 +// */ +// 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).size() > 1 && attr.needMerge()) +// { +// CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column); +// sheet.addMergedRegion(cellAddress); +// } +// cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); +// +// // 用于读取对象中的属性 +// 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.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; +// } +// +// /** +// * 设置 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 = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 +// for (int i = 0; i < textlist.length; i++) +// { +// hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); +// } +// // 创建名称,可被其他单元格引用 +// Name name = wb.createName(); +// name.setNameName(hideSheetName + "_data"); +// name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); +// DataValidationHelper helper = sheet.getDataValidationHelper(); +// // 加载下拉列表内容 +// DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); +// // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 +// 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(","); +// 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(","); +// 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().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<Integer> 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(DOUBLE_FORMAT.format(statistics.get(key))); +// } +// statistics.clear(); +// } +// } +// +// /** +// * 编码文件名 +// */ +// public String encodingFilename(String filename) +// { +// filename = UUID.randomUUID() + "_" + filename + ".xlsx"; +// return filename; +// } +// +// /** +// * 获取下载路径 +// * +// * @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 +// { +// 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<Object[]> getFields() +// { +// List<Object[]> fields = new ArrayList<Object[]>(); +// List<Field> tempFields = new ArrayList<>(); +// tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); +// tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); +// for (Field field : tempFields) +// { +// if (!ArrayUtils.contains(this.excludeFields, field.getName())) +// { +// // 单注解 +// if (field.isAnnotationPresent(Excel.class)) +// { +// Excel attr = field.getAnnotation(Excel.class); +// if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) +// { +// field.setAccessible(true); +// fields.add(new Object[] { field, attr }); +// } +// if (Collection.class.isAssignableFrom(field.getType())) +// { +// subMethod = getSubMethod(field.getName(), clazz); +// ParameterizedType pt = (ParameterizedType) field.getGenericType(); +// Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0]; +// this.subFields = 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 (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) +// && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) +// { +// field.setAccessible(true); +// fields.add(new Object[] { field, attr }); +// } +// } +// } +// } +// } +// return fields; +// } +// +// /** +// * 根据注解获取最大行高 +// */ +// 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(); +// wb.setSheetName(index, 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<String, PictureData> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) +// { +// Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>(); +// List<HSSFPictureData> pictures = workbook.getAllPictures(); +// if (!pictures.isEmpty()) +// { +// for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) +// { +// HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor(); +// if (shape instanceof HSSFPicture) +// { +// HSSFPicture pic = (HSSFPicture) shape; +// int pictureIndex = pic.getPictureIndex() - 1; +// HSSFPictureData picData = pictures.get(pictureIndex); +// String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); +// sheetIndexPicMap.put(picIndex, picData); +// } +// } +// return sheetIndexPicMap; +// } +// else +// { +// return sheetIndexPicMap; +// } +// } +// +// /** +// * 获取Excel2007图片 +// * +// * @param sheet 当前sheet对象 +// * @param workbook 工作簿对象 +// * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData +// */ +// public static Map<String, PictureData> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) +// { +// Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>(); +// for (POIXMLDocumentPart dr : sheet.getRelations()) +// { +// if (dr instanceof XSSFDrawing) +// { +// XSSFDrawing drawing = (XSSFDrawing) dr; +// List<XSSFShape> shapes = drawing.getShapes(); +// for (XSSFShape shape : shapes) +// { +// if (shape instanceof XSSFPicture) +// { +// XSSFPicture pic = (XSSFPicture) shape; +// XSSFClientAnchor anchor = pic.getPreferredSize(); +// CTMarker ctMarker = anchor.getFrom(); +// String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); +// sheetIndexPicMap.put(picIndex, 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.isNotNull(subFields) && subFields.size() > 0; +// } +// +// /** +// * 是否有对象的子列表,集合不为空 +// */ +// public boolean isSubListValue(T vo) +// { +// return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; +// } +// +// /** +// * 获取集合的值 +// */ +// public Collection<?> getListCellValue(Object obj) +// { +// Object value; +// try +// { +// value = subMethod.invoke(obj, new Object[] {}); +// } +// catch (Exception e) +// { +// return new ArrayList<Object>(); +// } +// return (Collection<?>) value; +// } +// +// /** +// * 获取对象的子列表方法 +// * +// * @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; +// } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..64d47ca --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,409 @@ +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.util.Date; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用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> 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 <E> 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> 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 <E> 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> 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> 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]); + } + } + } + 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抱怨。 + */ + 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抱怨。 + */ + 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 <T> Class<T> 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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java new file mode 100644 index 0000000..ca1cd92 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.ruoyi.common.utils.sign; + +/** + * Base64工具类 + * + * @author ruoyi + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java new file mode 100644 index 0000000..c1c58db --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java @@ -0,0 +1,67 @@ +package com.ruoyi.common.utils.sign; + +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; + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java new file mode 100644 index 0000000..f290ec3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java @@ -0,0 +1,158 @@ +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> T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static <T> T getBean(Class<T> 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> 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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java new file mode 100644 index 0000000..93b0347 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java @@ -0,0 +1,70 @@ +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 = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|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注入风险"); + } + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..2c84427 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java new file mode 100644 index 0000000..bf99611 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +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); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java new file mode 100644 index 0000000..a5585d6 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +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<UUID> +{ + 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} 是如何生成的。 + * <p> + * 版本号具有以下含意: + * <ul> + * <li>1 基于时间的 UUID + * <li>2 DCE 安全 UUID + * <li>3 基于名称的 UUID + * <li>4 随机生成的 UUID + * </ul> + * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + * <p> + * 变体号具有以下含意: + * <ul> + * <li>0 为 NCS 向后兼容保留 + * <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF RFC 4122</a>(Leach-Salz), 用于此类 + * <li>6 保留,微软向后兼容 + * <li>7 保留供以后定义使用 + * </ul> + * + * @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 相关联的时间戳值。 + * + * <p> + * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br> + * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + * <p> + * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br> + * 如果此 {@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 相关联的时钟序列值。 + * + * <p> + * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + * <p> + * {@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 相关的节点值。 + * + * <p> + * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + * <p> + * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br> + * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + * <p> + * UUID 的字符串表示形式由此 BNF 描述: + * + * <pre> + * {@code + * UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node> + * time_low = 4*<hexOctet> + * time_mid = 2*<hexOctet> + * time_high_and_version = 2*<hexOctet> + * variant_and_sequence = 2*<hexOctet> + * node = 6*<hexOctet> + * hexOctet = <hexDigit><hexDigit> + * hexDigit = [0-9a-fA-F] + * } + * </pre> + * + * </blockquote> + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + * <p> + * UUID 的字符串表示形式由此 BNF 描述: + * + * <pre> + * {@code + * UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node> + * time_low = 4*<hexOctet> + * time_mid = 2*<hexOctet> + * time_high_and_version = 2*<hexOctet> + * variant_and_sequence = 2*<hexOctet> + * node = 6*<hexOctet> + * hexOctet = <hexDigit><hexDigit> + * hexDigit = [0-9a-fA-F] + * } + * </pre> + * + * </blockquote> + * + * @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; + } + + /** + * 将此对象与指定对象比较。 + * <p> + * 当且仅当参数不为 {@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 比较。 + * + * <p> + * 如果两个 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); + } + } + + /** + * 获取随机数生成器对象<br> + * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java new file mode 100644 index 0000000..7bfdf04 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.xss; + +import javax.validation.Constraint; +import javax.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<? extends Payload>[] payload() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java new file mode 100644 index 0000000..ed9ec1f --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.xss; + +import com.ruoyi.common.utils.StringUtils; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator<Xss, String> +{ + 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) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml new file mode 100644 index 0000000..3b70537 --- /dev/null +++ b/ruoyi-framework/pom.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>ruoyi-framework</artifactId> + + <description> + framework框架核心 + </description> + + <dependencies> + + <!-- SpringBoot Web容器 --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <!-- SpringBoot 拦截器 --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-aop</artifactId> + </dependency> + + <!-- 阿里数据库连接池 --> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>druid-spring-boot-starter</artifactId> + </dependency> + + <!-- 验证码 --> + <dependency> + <groupId>pro.fessional</groupId> + <artifactId>kaptcha</artifactId> + <exclusions> + <exclusion> + <artifactId>servlet-api</artifactId> + <groupId>javax.servlet</groupId> + </exclusion> + </exclusions> + </dependency> + + <!-- 获取系统信息 --> + <dependency> + <groupId>com.github.oshi</groupId> + <artifactId>oshi-core</artifactId> + </dependency> + + <!-- 系统模块--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-system</artifactId> + </dependency> + + </dependencies> + +</project> \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..1bc2f69 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,174 @@ +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.core.domain.BaseEntity; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class DataScopeAspect +{ + /** + * 全部数据权限 + */ + 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"; + + /** + * 数据权限过滤关键字 + */ + 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) + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) + { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !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<String> conditions = new ArrayList<String>(); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) + { + continue; + } + if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + else if (DATA_SCOPE_DEPT.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } + else if (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 (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, ""); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..8c2c9f4 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +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.utils.StringUtils; +import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @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); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..5f5d764 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -0,0 +1,267 @@ +package com.ruoyi.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.system.service.ISysRoleService; +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.beans.factory.annotation.Autowired; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.BusinessStatus; +import com.ruoyi.common.enums.HttpMethod; +import com.ruoyi.common.filter.PropertyPreExcludeFilter; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +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 +{ + @Autowired + private ISysRoleService roleService; + + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间 */ + private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time"); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void boBefore(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 + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (loginUser != null) + { + operLog.setOperName(loginUser.getUsername()); + operLog.setUserId(loginUser.getUserId()); + if(Objects.nonNull(loginUser.getUser())){ + operLog.setNickName(loginUser.getUser().getNickName()); + operLog.setPhonenumber(loginUser.getUser().getPhonenumber()); + // 设置角色名称 + SysRole sysRole = roleService.selectRoleByUserId(loginUser.getUserId()); + if(Objects.nonNull(sysRole)){ + operLog.setRoleName(sysRole.getRoleName()); + } + } + } + + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 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(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception + { + Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (StringUtils.isEmpty(paramsMap) + && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) + { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + else + { + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) + { + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 判断是否需要过滤的对象。 + * + * @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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..b720bc1 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,89 @@ +package com.ruoyi.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.ruoyi.common.annotation.RateLimiter; +import com.ruoyi.common.enums.LimitType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate<Object, Object> redisTemplate; + + private RedisScript<Long> limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript<Long> limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List<Object> keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class<?> targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..1d4dc1f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java @@ -0,0 +1,30 @@ +package com.ruoyi.framework.config; + +import java.util.TimeZone; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +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 +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..43e78ae --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java new file mode 100644 index 0000000..f6abac1 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java @@ -0,0 +1,126 @@ +package com.ruoyi.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +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.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.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; + +/** + * 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<Object, Object> 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<Object, Object> 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(javax.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("<a.*?banner\"></a><br/>", ""); + text = text.replaceAll("powered.*?shrek.wang</a>", ""); + response.getWriter().write(text); + } + @Override + public void destroy() + { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..4adbb7f --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,52 @@ +package com.ruoyi.framework.config; + +import java.nio.charset.Charset; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import com.ruoyi.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + private Class<T> clazz; + + public FastJson2JsonRedisSerializer(Class<T> clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java new file mode 100644 index 0000000..f3a3cff --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -0,0 +1,59 @@ +package com.ruoyi.framework.config; + +import java.util.HashMap; +import java.util.Map; +import javax.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.filter.RepeatableFilter; +import com.ruoyi.common.filter.XssFilter; +import com.ruoyi.common.utils.StringUtils; + +/** + * Filter配置 + * + * @author ruoyi + */ +@Configuration +public class FilterConfig +{ + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + 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<String, String> initParameters = new HashMap<String, String>(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..7f8e1d5 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java @@ -0,0 +1,68 @@ +package com.ruoyi.framework.config; + +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 Random(); + 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 (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]); + } + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..bc6618a --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java @@ -0,0 +1,132 @@ +//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<String> allResult = new ArrayList<String>(); +// try +// { +// for (String aliasesPackage : typeAliasesPackage.split(",")) +// { +// List<String> result = new ArrayList<String>(); +// 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<String> hashResult = new HashSet<String>(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<Resource> resources = new ArrayList<Resource>(); +// 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(); +// } +//} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java new file mode 100644 index 0000000..3f4f485 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java @@ -0,0 +1,69 @@ +package com.ruoyi.framework.config; + +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author ruoyi + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate<Object, Object> template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript<Long> limitScript() + { + DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..74fb93e --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -0,0 +1,73 @@ +package com.ruoyi.framework.config; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +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 +{ + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @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/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());; + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() + { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java new file mode 100644 index 0000000..b4118dc --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -0,0 +1,157 @@ +package com.ruoyi.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.ruoyi.framework.config.properties.PermitAllUrlProperties; +import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; +import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; +import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author ruoyi + */ +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter +{ + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return + * @throws Exception + */ + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception + { + return super.authenticationManagerBean(); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception + { + // 注解标记允许匿名访问的url + ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); + permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); + + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 禁用HTTP响应标头 + .headers().cacheControl().disable().and() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/getPrivacyAgreement/{agreementType}", + "/applet/queryProtocolConfigByType","/applet/login", + "/login","/applet/queryProtocolConfigByType", + "/register","/applet/getCode","/applet/loginCode", + "/applet/changepwd", "/captchaImage","/getCode","/loginCode", + "/operations/getBySingleNum/**", + "/user/getUserInfoByNumber/**", + "/wxLogin/**", + "/open/**","/cos/get/**" + ).permitAll() + // 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + .antMatchers("/swagger-ui.html","/doc.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + // 添加Logout filter + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } + + /** + * 身份认证接口 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception + { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java new file mode 100644 index 0000000..b5b7de3 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.ruoyi.framework.config; + +import javax.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(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java new file mode 100644 index 0000000..7840141 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java @@ -0,0 +1,63 @@ +package com.ruoyi.framework.config; + +import com.ruoyi.common.utils.Threads; +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 java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + * + * @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, + new 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); + } + }; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..c8a5c8a --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java @@ -0,0 +1,89 @@ +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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 0000000..29118fa --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,73 @@ +package com.ruoyi.framework.config.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import com.ruoyi.common.annotation.Anonymous; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author ruoyi + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware +{ + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List<String> urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + @Override + public void afterPropertiesSet() + { + RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); + Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods(); + + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); + + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException + { + this.applicationContext = context; + } + + public List<String> getUrls() + { + return urls; + } + + public void setUrls(List<String> urls) + { + this.urls = urls; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..e70b8cf --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java @@ -0,0 +1,26 @@ +package com.ruoyi.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author ruoyi + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..9770af6 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.ruoyi.framework.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<String> 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(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..c49eaf4 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,56 @@ +package com.ruoyi.framework.interceptor; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.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.toJSONString(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request 请求信息 + * @param annotation 防重复注解参数 + * @return 结果 + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..9dc9511 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,110 @@ +package com.ruoyi.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.filter.RepeatedlyRequestWrapper; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpHelper; +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"; + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @Autowired + private RedisCache redisCache; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + Map<String, Object> nowDataMap = new HashMap<String, Object>(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; + + Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); + if (sessionObj != null) + { + Map<String, Object> sessionMap = (Map<String, Object>) sessionObj; + if (sessionMap.containsKey(url)) + { + Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map<String, Object> cacheMap = new HashMap<String, Object>(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> 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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java new file mode 100644 index 0000000..7387a02 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +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 ruoyi + */ +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); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..e36ca3c --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java @@ -0,0 +1,39 @@ +package com.ruoyi.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import javax.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author ruoyi + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() + { + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..267e305 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.ruoyi.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.LogUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.service.ISysLogininforService; +import com.ruoyi.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author ruoyi + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @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 UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = IpUtils.getIpAddr(); + 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 = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(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(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @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); + } + }; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 0000000..6c776ce --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.ruoyi.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author ruoyi + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java new file mode 100644 index 0000000..5472f3d --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.ruoyi.framework.security.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)); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..9015708 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,56 @@ +package com.ruoyi.framework.security.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.common.core.domain.model.LoginUserApplet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author ruoyi + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + LoginUserApplet applet = tokenService.getLoginUserApplet(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())|| + StringUtils.isNotNull(applet)) + { + if (StringUtils.isNotNull(loginUser)){ + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + if (StringUtils.isNotNull(applet)){ + tokenService.verifyTokenApplet(applet); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(applet, null, applet.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + } + chain.doFilter(request, response); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..93b7032 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.ruoyi.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author ruoyi + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..c01f691 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,52 @@ +package com.ruoyi.framework.security.handle; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.TokenService; + +/** + * 自定义退出处理类 返回成功 + * + * @author ruoyi + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler +{ + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功"))); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java new file mode 100644 index 0000000..63b03da --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +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.ip.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<SysFile> sysFiles = new LinkedList<SysFile>(); + + 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<SysFile> getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List<SysFile> 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<OSFileStore> 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); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..a13a66c --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..1fdc6ac --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +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(); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..13eec52 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +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); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..45d64d9 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..1320cde --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +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; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..a3ec182 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,138 @@ +package com.ruoyi.framework.web.exception; + +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +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 com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.exception.DemoModeException; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @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(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(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(); + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + + /** + * 拦截未知的运行时异常 + */ + @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(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java new file mode 100644 index 0000000..249c6a0 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java @@ -0,0 +1,168 @@ +package com.ruoyi.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.PermissionContextHolder; + +/** + * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author ruoyi + */ +@Service("ss") +public class PermissionService +{ + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) + { + if (StringUtils.isEmpty(permission)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) + { + if (StringUtils.isEmpty(permissions)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permissions); + Set<String> authorities = loginUser.getPermissions(); + for (String permission : permissions.split(PERMISSION_DELIMETER)) + { + if (permission != null && hasPermissions(authorities, permission)) + { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + if (StringUtils.isEmpty(role)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) + { + String roleKey = sysRole.getRoleKey(); + if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) + { + return true; + } + } + return false; + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) + { + return hasRole(role) != true; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) + { + if (StringUtils.isEmpty(roles)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (String role : roles.split(ROLE_DELIMETER)) + { + if (hasRole(role)) + { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set<String> permissions, String permission) + { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java new file mode 100644 index 0000000..f703c22 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -0,0 +1,247 @@ +package com.ruoyi.framework.web.service; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.entity.TTenantResp; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.domain.model.LoginUserApplet; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.exception.user.BlackListException; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +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.MessageUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Slf4j +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + @Autowired + private SysPermissionService permissionService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public LoginUser login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + // 用户验证 + Authentication authentication = null; + // 用户验证 + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)){ + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return loginUser; + } + + /** + * 登录验证 + * + * @param username 用户名 + * @param code 验证码 + * @return 结果 + */ + public LoginUser loginCode(String username,String code) + { + // 登录前置校验 + if (StringUtils.isEmpty(username)){ + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 用户验证 + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)){ + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + if(user.isAdmin()){ + log.info("登录用户:{} 不可用短信验证码登录.", username); + throw new ServiceException("不可用短信验证码登录"); + } + // 校验验证码 + Object cacheObject = redisCache.getCacheObject(user.getPhonenumber()); + if(!code.equals(String.valueOf(cacheObject))){ + log.info("登录用户:{} 短信验证码错误{}", username,code); + throw new ServiceException("短信验证码错误"); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return loginUser; + } + + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + 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, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr()); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java new file mode 100644 index 0000000..6ad91b0 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPasswordService.java @@ -0,0 +1,94 @@ +package com.ruoyi.framework.web.service; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.redis.RedisCache; +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.SecurityUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.security.context.AuthenticationContextHolder; + +/** + * 登录密码方法 + * + * @author ruoyi + */ +@Component +public class SysPasswordService +{ + @Autowired + private RedisCache redisCache; + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * @return 缓存键key + */ + private String getCacheKey(String username) + { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + if (retryCount == null) + { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + if (!matches(user, password)) + { + retryCount = retryCount + 1; + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.count", retryCount))); + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + if (redisCache.hasKey(getCacheKey(loginName))) + { + redisCache.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java new file mode 100644 index 0000000..d1fb4ed --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java @@ -0,0 +1,83 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author ruoyi + */ +@Component +public class SysPermissionService +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set<String> getRolePermission(SysUser user) + { + Set<String> roles = new HashSet<String>(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + roles.add("admin"); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set<String> getMenuPermission(SysUser user) + { + Set<String> perms = new HashSet<String>(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + perms.add("*:*:*"); + } + else + { + List<SysRole> roles = user.getRoles(); + if (!CollectionUtils.isEmpty(roles)) + { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) + { + Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java new file mode 100644 index 0000000..f2afe31 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java @@ -0,0 +1,115 @@ +package com.ruoyi.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +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.ISysUserService; + +/** + * 注册校验方法 + * + * @author ruoyi + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private RedisCache redisCache; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java new file mode 100644 index 0000000..206c1a4 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java @@ -0,0 +1,360 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; + +import com.ruoyi.common.core.domain.model.LoginUserApplet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +public class TokenService +{ + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + @Autowired + private RedisCache redisCache; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser() + { + return getLoginUser(ServletUtils.getRequest()); + } + /** + * 小程序获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUserApplet getLoginUserApplet() + { + return getLoginUserApplet(ServletUtils.getRequest()); + } + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + } + return null; + } + /** + * 小程序获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUserApplet getLoginUserApplet(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_APPLET_KEY); + String userKey = getTokenKey(uuid); + LoginUserApplet user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + + Map<String, Object> claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + return createToken(claims); + } + /** + * 创建用户小程序令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createTokenApplet(LoginUserApplet loginUser) + { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgentApplet(loginUser); + refreshTokenApplet(loginUser); + + Map<String, Object> claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_APPLET_KEY, token); + return createTokenApplet(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + + public boolean verifyToken(String token) + { + Claims claims = parseToken(token); + + return true; + } + /** + * 小程序验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyTokenApplet(LoginUserApplet loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshTokenApplet(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshTokenApplet(LoginUserApplet loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) + { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgentApplet(LoginUserApplet loginUser) + { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map<String, Object> claims) + { + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + /** + * 小程序从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createTokenApplet(Map<String, Object> claims) + { + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) + { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) + { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) + { + String token = request.getHeader(header); + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) + { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } + + private String getTokenKey(String uuid) + { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..5dcdf90 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,66 @@ +package com.ruoyi.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author ruoyi + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException + { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml new file mode 100644 index 0000000..1180284 --- /dev/null +++ b/ruoyi-generator/pom.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>ruoyi-generator</artifactId> + + <description> + generator代码生成 + </description> + + <dependencies> + + <!--velocity代码生成使用模板 --> + <dependency> + <groupId>org.apache.velocity</groupId> + <artifactId>velocity-engine-core</artifactId> + </dependency> + + <!-- collections工具类 --> + <dependency> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + </dependency> + + <!-- 通用工具--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-common</artifactId> + </dependency> + + </dependencies> + +</project> \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java new file mode 100644 index 0000000..cc4cd14 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +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; + + /** 自动去除表前缀,默认是false */ + public static boolean autoRemovePre; + + /** 表前缀(类名不会包含表前缀) */ + public static String tablePrefix; + + 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; + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java new file mode 100644 index 0000000..51ab569 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java @@ -0,0 +1,214 @@ +package com.ruoyi.generator.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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.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.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.generator.service.IGenTableColumnService; +import com.ruoyi.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + + /** + * 查询代码生成列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) + { +// startPage(); + List<GenTable> list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 修改代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable Long tableId) + { + GenTable table = genTableService.selectGenTableById(tableId); + List<GenTable> tables = genTableService.selectGenTableAll(); + List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + Map<String, Object> map = new HashMap<String, Object>(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return success(map); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { +// startPage(); + List<GenTable> list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(Long tableId) + { + TableDataInfo dataInfo = new TableDataInfo(); + List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRecords(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(String tables) + { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList); + return success(); + } + + /** + * 修改保存代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTable genTable) + { + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + return success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map<String, String> dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @PreAuthorize("@ss.hasPermi('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); + } + + /** + * 生成代码(自定义路径) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) + { + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) + { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + 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.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + 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()); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java new file mode 100644 index 0000000..269779c --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java @@ -0,0 +1,372 @@ +package com.ruoyi.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.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; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenTableColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List<GenTableColumn> 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 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<GenTableColumn> getColumns() + { + return columns; + } + + public void setColumns(List<GenTableColumn> 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); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java new file mode 100644 index 0000000..d1733b6 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java @@ -0,0 +1,373 @@ +package com.ruoyi.generator.domain; + +import javax.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日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ + 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; + } + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..951e166 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +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<GenTableColumn> selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List<GenTableColumn> genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..9b330df --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java @@ -0,0 +1,83 @@ +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<GenTable> selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List<GenTable> selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List<GenTable> selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List<GenTable> 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); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 0000000..0679689 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.ruoyi.generator.service; + +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; + +/** + * 业务字段 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @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)); + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java new file mode 100644 index 0000000..4889f81 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java @@ -0,0 +1,521 @@ +package com.ruoyi.generator.service; + +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.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.core.text.CharsetKit; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +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.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<GenTable> selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List<GenTable> selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List<GenTable> selectDbTableListByNames(String[] tableNames) + { + return genTableMapper.selectDbTableListByNames(tableNames); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List<GenTable> 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 cenTableColumn : genTable.getColumns()) + { + genTableColumnMapper.updateGenTableColumn(cenTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param tableIds 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) + { + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List<GenTable> tableList) + { + String operName = SecurityUtils.getUsername(); + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 保存列信息 + List<GenTableColumn> 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<String, String> previewCode(Long tableId) + { + Map<String, String> dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List<String> 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<String> templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.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<GenTableColumn> tableColumns = table.getColumns(); + Map<String, GenTableColumn> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List<String> 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<GenTableColumn> 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<String> 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 = JSON.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 = JSON.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); + } +} \ No newline at end of file diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java new file mode 100644 index 0000000..3037f70 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import com.ruoyi.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author ruoyi + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @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); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java new file mode 100644 index 0000000..9d53f95 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java @@ -0,0 +1,121 @@ +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<GenTable> selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List<GenTable> selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List<GenTable> selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List<GenTable> selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + public void importGenTable(List<GenTable> tableList); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map<String, String> previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + 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); +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java new file mode 100644 index 0000000..e7ebc20 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java @@ -0,0 +1,257 @@ +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, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * + * @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; + } + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..9f69403 --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +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); + } + } +} diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java new file mode 100644 index 0000000..7ede02d --- /dev/null +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -0,0 +1,402 @@ +package com.ruoyi.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; + +/** + * 模板处理工具类 + * + * @author ruoyi + */ +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + 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", StringUtils.capitalize(genTable.getBusinessName())); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + 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); + velocityContext.put("dicts", getDicts(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 = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.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<String> getTemplateList(String tplCategory) + { + List<String> templates = new ArrayList<String>(); + 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"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add("vm/vue/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add("vm/vue/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add("vm/vue/index.vue.vm"); + templates.add("vm/java/sub-domain.java.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 vuePath = "vue"; + + 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("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取包前缀 + * + * @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<String> getImportList(GenTable genTable) + { + List<GenTableColumn> columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet<String> importList = new HashSet<String>(); + 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 genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) + { + List<GenTableColumn> columns = genTable.getColumns(); + Set<String> dicts = new HashSet<String>(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List<GenTableColumn> subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) + { + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * + * @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 = JSON.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; + } +} diff --git a/ruoyi-generator/src/main/resources/generator.yml b/ruoyi-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..7eae68e --- /dev/null +++ b/ruoyi-generator/src/main/resources/generator.yml @@ -0,0 +1,10 @@ +# 代码生成 +gen: + # 作者 + author: ruoyi + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.ruoyi.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..66109de --- /dev/null +++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.generator.mapper.GenTableColumnMapper"> + + <resultMap type="GenTableColumn" id="GenTableColumnResult"> + <id property="columnId" column="column_id" /> + <result property="tableId" column="table_id" /> + <result property="columnName" column="column_name" /> + <result property="columnComment" column="column_comment" /> + <result property="columnType" column="column_type" /> + <result property="javaType" column="java_type" /> + <result property="javaField" column="java_field" /> + <result property="isPk" column="is_pk" /> + <result property="isIncrement" column="is_increment" /> + <result property="isRequired" column="is_required" /> + <result property="isInsert" column="is_insert" /> + <result property="isEdit" column="is_edit" /> + <result property="isList" column="is_list" /> + <result property="isQuery" column="is_query" /> + <result property="queryType" column="query_type" /> + <result property="htmlType" column="html_type" /> + <result property="dictType" column="dict_type" /> + <result property="sort" column="sort" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + </resultMap> + + <sql id="selectGenTableColumnVo"> + 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 + </sql> + + <select id="selectGenTableColumnListByTableId" parameterType="Long" resultMap="GenTableColumnResult"> + <include refid="selectGenTableColumnVo"/> + where table_id = #{tableId} + order by sort + </select> + + <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult"> + select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type + from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName}) + order by ordinal_position + </select> + + <insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId"> + insert into gen_table_column ( + <if test="tableId != null and tableId != ''">table_id,</if> + <if test="columnName != null and columnName != ''">column_name,</if> + <if test="columnComment != null and columnComment != ''">column_comment,</if> + <if test="columnType != null and columnType != ''">column_type,</if> + <if test="javaType != null and javaType != ''">java_type,</if> + <if test="javaField != null and javaField != ''">java_field,</if> + <if test="isPk != null and isPk != ''">is_pk,</if> + <if test="isIncrement != null and isIncrement != ''">is_increment,</if> + <if test="isRequired != null and isRequired != ''">is_required,</if> + <if test="isInsert != null and isInsert != ''">is_insert,</if> + <if test="isEdit != null and isEdit != ''">is_edit,</if> + <if test="isList != null and isList != ''">is_list,</if> + <if test="isQuery != null and isQuery != ''">is_query,</if> + <if test="queryType != null and queryType != ''">query_type,</if> + <if test="htmlType != null and htmlType != ''">html_type,</if> + <if test="dictType != null and dictType != ''">dict_type,</if> + <if test="sort != null">sort,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="tableId != null and tableId != ''">#{tableId},</if> + <if test="columnName != null and columnName != ''">#{columnName},</if> + <if test="columnComment != null and columnComment != ''">#{columnComment},</if> + <if test="columnType != null and columnType != ''">#{columnType},</if> + <if test="javaType != null and javaType != ''">#{javaType},</if> + <if test="javaField != null and javaField != ''">#{javaField},</if> + <if test="isPk != null and isPk != ''">#{isPk},</if> + <if test="isIncrement != null and isIncrement != ''">#{isIncrement},</if> + <if test="isRequired != null and isRequired != ''">#{isRequired},</if> + <if test="isInsert != null and isInsert != ''">#{isInsert},</if> + <if test="isEdit != null and isEdit != ''">#{isEdit},</if> + <if test="isList != null and isList != ''">#{isList},</if> + <if test="isQuery != null and isQuery != ''">#{isQuery},</if> + <if test="queryType != null and queryType != ''">#{queryType},</if> + <if test="htmlType != null and htmlType != ''">#{htmlType},</if> + <if test="dictType != null and dictType != ''">#{dictType},</if> + <if test="sort != null">#{sort},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + + <update id="updateGenTableColumn" parameterType="GenTableColumn"> + update gen_table_column + <set> + <if test="columnComment != null">column_comment = #{columnComment},</if> + <if test="javaType != null">java_type = #{javaType},</if> + <if test="javaField != null">java_field = #{javaField},</if> + <if test="isInsert != null">is_insert = #{isInsert},</if> + <if test="isEdit != null">is_edit = #{isEdit},</if> + <if test="isList != null">is_list = #{isList},</if> + <if test="isQuery != null">is_query = #{isQuery},</if> + <if test="isRequired != null">is_required = #{isRequired},</if> + <if test="queryType != null">query_type = #{queryType},</if> + <if test="htmlType != null">html_type = #{htmlType},</if> + <if test="dictType != null">dict_type = #{dictType},</if> + <if test="sort != null">sort = #{sort},</if> + <if test="updateBy != null">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where column_id = #{columnId} + </update> + + <delete id="deleteGenTableColumnByIds" parameterType="Long"> + delete from gen_table_column where table_id in + <foreach collection="array" item="tableId" open="(" separator="," close=")"> + #{tableId} + </foreach> + </delete> + + <delete id="deleteGenTableColumns"> + delete from gen_table_column where column_id in + <foreach collection="list" item="item" open="(" separator="," close=")"> + #{item.columnId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..b605e90 --- /dev/null +++ b/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.generator.mapper.GenTableMapper"> + + <resultMap type="GenTable" id="GenTableResult"> + <id property="tableId" column="table_id" /> + <result property="tableName" column="table_name" /> + <result property="tableComment" column="table_comment" /> + <result property="subTableName" column="sub_table_name" /> + <result property="subTableFkName" column="sub_table_fk_name" /> + <result property="className" column="class_name" /> + <result property="tplCategory" column="tpl_category" /> + <result property="packageName" column="package_name" /> + <result property="moduleName" column="module_name" /> + <result property="businessName" column="business_name" /> + <result property="functionName" column="function_name" /> + <result property="functionAuthor" column="function_author" /> + <result property="genType" column="gen_type" /> + <result property="genPath" column="gen_path" /> + <result property="options" column="options" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + <result property="remark" column="remark" /> + <collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult" /> + </resultMap> + + <resultMap type="GenTableColumn" id="GenTableColumnResult"> + <id property="columnId" column="column_id" /> + <result property="tableId" column="table_id" /> + <result property="columnName" column="column_name" /> + <result property="columnComment" column="column_comment" /> + <result property="columnType" column="column_type" /> + <result property="javaType" column="java_type" /> + <result property="javaField" column="java_field" /> + <result property="isPk" column="is_pk" /> + <result property="isIncrement" column="is_increment" /> + <result property="isRequired" column="is_required" /> + <result property="isInsert" column="is_insert" /> + <result property="isEdit" column="is_edit" /> + <result property="isList" column="is_list" /> + <result property="isQuery" column="is_query" /> + <result property="queryType" column="query_type" /> + <result property="htmlType" column="html_type" /> + <result property="dictType" column="dict_type" /> + <result property="sort" column="sort" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + </resultMap> + + <sql id="selectGenTableVo"> + 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, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + </sql> + + <select id="selectGenTableList" parameterType="GenTable" resultMap="GenTableResult"> + <include refid="selectGenTableVo"/> + <where> + <if test="tableName != null and tableName != ''"> + AND lower(table_name) like lower(concat('%', #{tableName}, '%')) + </if> + <if test="tableComment != null and tableComment != ''"> + AND lower(table_comment) like lower(concat('%', #{tableComment}, '%')) + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + AND date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + AND date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + </if> + </where> + </select> + + <select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult"> + select table_name, table_comment, create_time, update_time from information_schema.tables + where table_schema = (select database()) + AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%' + AND table_name NOT IN (select table_name from gen_table) + <if test="tableName != null and tableName != ''"> + AND lower(table_name) like lower(concat('%', #{tableName}, '%')) + </if> + <if test="tableComment != null and tableComment != ''"> + AND lower(table_comment) like lower(concat('%', #{tableComment}, '%')) + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + AND date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + AND date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + </if> + order by create_time desc + </select> + + <select id="selectDbTableListByNames" resultMap="GenTableResult"> + select table_name, table_comment, create_time, update_time from information_schema.tables + where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database()) + and table_name in + <foreach collection="array" item="name" open="(" separator="," close=")"> + #{name} + </foreach> + </select> + + <select id="selectTableByName" parameterType="String" resultMap="GenTableResult"> + select table_name, table_comment, create_time, update_time from information_schema.tables + where table_comment <![CDATA[ <> ]]> '' and table_schema = (select database()) + and table_name = #{tableName} + </select> + + <select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult"> + SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, + c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort + FROM gen_table t + LEFT JOIN gen_table_column c ON t.table_id = c.table_id + where t.table_id = #{tableId} order by c.sort + </select> + + <select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult"> + SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, + c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort + FROM gen_table t + LEFT JOIN gen_table_column c ON t.table_id = c.table_id + where t.table_name = #{tableName} order by c.sort + </select> + + <select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult"> + SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark, + c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort + FROM gen_table t + LEFT JOIN gen_table_column c ON t.table_id = c.table_id + order by c.sort + </select> + + <insert id="insertGenTable" parameterType="GenTable" useGeneratedKeys="true" keyProperty="tableId"> + insert into gen_table ( + <if test="tableName != null">table_name,</if> + <if test="tableComment != null and tableComment != ''">table_comment,</if> + <if test="className != null and className != ''">class_name,</if> + <if test="tplCategory != null and tplCategory != ''">tpl_category,</if> + <if test="packageName != null and packageName != ''">package_name,</if> + <if test="moduleName != null and moduleName != ''">module_name,</if> + <if test="businessName != null and businessName != ''">business_name,</if> + <if test="functionName != null and functionName != ''">function_name,</if> + <if test="functionAuthor != null and functionAuthor != ''">function_author,</if> + <if test="genType != null and genType != ''">gen_type,</if> + <if test="genPath != null and genPath != ''">gen_path,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="tableName != null">#{tableName},</if> + <if test="tableComment != null and tableComment != ''">#{tableComment},</if> + <if test="className != null and className != ''">#{className},</if> + <if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if> + <if test="packageName != null and packageName != ''">#{packageName},</if> + <if test="moduleName != null and moduleName != ''">#{moduleName},</if> + <if test="businessName != null and businessName != ''">#{businessName},</if> + <if test="functionName != null and functionName != ''">#{functionName},</if> + <if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if> + <if test="genType != null and genType != ''">#{genType},</if> + <if test="genPath != null and genPath != ''">#{genPath},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + + <update id="updateGenTable" parameterType="GenTable"> + update gen_table + <set> + <if test="tableName != null">table_name = #{tableName},</if> + <if test="tableComment != null and tableComment != ''">table_comment = #{tableComment},</if> + <if test="subTableName != null">sub_table_name = #{subTableName},</if> + <if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if> + <if test="className != null and className != ''">class_name = #{className},</if> + <if test="functionAuthor != null and functionAuthor != ''">function_author = #{functionAuthor},</if> + <if test="genType != null and genType != ''">gen_type = #{genType},</if> + <if test="genPath != null and genPath != ''">gen_path = #{genPath},</if> + <if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if> + <if test="packageName != null and packageName != ''">package_name = #{packageName},</if> + <if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if> + <if test="businessName != null and businessName != ''">business_name = #{businessName},</if> + <if test="functionName != null and functionName != ''">function_name = #{functionName},</if> + <if test="options != null and options != ''">options = #{options},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + <if test="remark != null">remark = #{remark},</if> + update_time = sysdate() + </set> + where table_id = #{tableId} + </update> + + <delete id="deleteGenTableByIds" parameterType="Long"> + delete from gen_table where table_id in + <foreach collection="array" item="tableId" open="(" separator="," close=")"> + #{tableId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..bf88988 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,115 @@ +package ${packageName}.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.ruoyi.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.ruoyi.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/ruoyi-generator/src/main/resources/vm/java/domain.java.vm b/ruoyi-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..bd51c17 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,105 @@ +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") + @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(); + } +} diff --git a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..7e7d7c2 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +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(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${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 +} diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..264882b --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${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(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..14746e1 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,169 @@ +package ${packageName}.service.impl; + +import java.util.List; +#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; + +/** + * ${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(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${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.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 +} diff --git a/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm b/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..a3f53eb --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,76 @@ +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") + @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(); + } +} diff --git a/ruoyi-generator/src/main/resources/vm/js/api.js.vm b/ruoyi-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/ruoyi-generator/src/main/resources/vm/sql/sql.vm b/ruoyi-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..4819c2a --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,505 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> +#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") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-input + v-model="queryParams.${column.javaField}" + placeholder="请输入${comment}" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option + v-for="dict in dict.type.${dictType}" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-date-picker clearable + v-model="queryParams.${column.javaField}" + type="date" + value-format="yyyy-MM-dd" + placeholder="选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + <el-form-item label="${comment}"> + <el-date-picker + v-model="daterange${AttrName}" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> +#end +#end +#end + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['${moduleName}:${businessName}:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-sort" + size="mini" + @click="toggleExpandAll" + >展开/折叠</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table + v-if="refreshTable" + v-loading="loading" + :data="${businessName}List" + row-key="${treeCode}" + :default-expand-all="isExpandAll" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + > +#foreach($column in $columns) +#set($javaField=$column.javaField) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.pk) +#elseif($column.list && $column.htmlType == "datetime") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> +#elseif($column.list && $column.htmlType == "imageUpload") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="100"> + <template slot-scope="scope"> + <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/> + </template> + </el-table-column> +#elseif($column.list && "" != $column.dictType) + <el-table-column label="${comment}" align="center" prop="${javaField}"> + <template slot-scope="scope"> +#if($column.htmlType == "checkbox") + <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/> +#else + <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/> +#end + </template> + </el-table-column> +#elseif($column.list && "" != $javaField) +#if(${foreach.index} == 1) + <el-table-column label="${comment}" prop="${javaField}" /> +#else + <el-table-column label="${comment}" align="center" prop="${javaField}" /> +#end +#end +#end + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['${moduleName}:${businessName}:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-plus" + @click="handleAdd(scope.row)" + v-hasPermi="['${moduleName}:${businessName}:add']" + >新增</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['${moduleName}:${businessName}:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <!-- 添加或修改${functionName}对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> +#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) + <el-form-item label="${comment}" prop="${treeParentCode}"> + <treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" /> + </el-form-item> +#elseif($column.htmlType == "input") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" placeholder="请输入${comment}" /> + </el-form-item> +#elseif($column.htmlType == "imageUpload") + <el-form-item label="${comment}" prop="${field}"> + <image-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "fileUpload") + <el-form-item label="${comment}" prop="${field}"> + <file-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "editor") + <el-form-item label="${comment}"> + <editor v-model="form.${field}" :min-height="192"/> + </el-form-item> +#elseif($column.htmlType == "select" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option + v-for="dict in dict.type.${dictType}" + :key="dict.value" + :label="dict.label" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :value="parseInt(dict.value)" +#else + :value="dict.value" +#end + ></el-option> + </el-select> + </el-form-item> +#elseif($column.htmlType == "select" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "checkbox" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox + v-for="dict in dict.type.${dictType}" + :key="dict.value" + :label="dict.value"> + {{dict.label}} + </el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "checkbox" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox>请选择字典生成</el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "radio" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio + v-for="dict in dict.type.${dictType}" + :key="dict.value" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :label="parseInt(dict.value)" +#else + :label="dict.value" +#end + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "radio" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio label="1">请选择字典生成</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "datetime") + <el-form-item label="${comment}" prop="${field}"> + <el-date-picker clearable + v-model="form.${field}" + type="date" + value-format="yyyy-MM-dd" + placeholder="选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "textarea") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> + </el-form-item> +#end +#end +#end +#end + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"; +import Treeselect from "@riophae/vue-treeselect"; +import "@riophae/vue-treeselect/dist/vue-treeselect.css"; + +export default { + name: "${BusinessName}", +#if(${dicts} != '') + dicts: [${dicts}], +#end + components: { + Treeselect + }, + data() { + return { + // 遮罩层 + loading: true, + // 显示搜索条件 + showSearch: true, + // ${functionName}表格数据 + ${businessName}List: [], + // ${functionName}树选项 + ${businessName}Options: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 是否展开,默认全部展开 + isExpandAll: true, + // 重新渲染表格状态 + refreshTable: true, +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + // $comment时间范围 + daterange${AttrName}: [], +#end +#end + // 查询参数 + queryParams: { +#foreach ($column in $columns) +#if($column.query) + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { +#foreach ($column in $columns) +#if($column.required) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end + $column.javaField: [ + { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end } + ]#if($foreach.count != $columns.size()),#end +#end +#end + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询${functionName}列表 */ + getList() { + this.loading = true; +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + this.queryParams.params = {}; +#break +#end +#end +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) { + this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0]; + this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1]; + } +#end +#end + list${BusinessName}(this.queryParams).then(response => { + this.${businessName}List = this.handleTree(response.data, "${treeCode}", "${treeParentCode}"); + this.loading = false; + }); + }, + /** 转换${functionName}数据结构 */ + normalizer(node) { + if (node.children && !node.children.length) { + delete node.children; + } + return { + id: node.${treeCode}, + label: node.${treeName}, + children: node.children + }; + }, + /** 查询${functionName}下拉树结构 */ + getTreeselect() { + list${BusinessName}().then(response => { + this.${businessName}Options = []; + const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] }; + data.children = this.handleTree(response.data, "${treeCode}", "${treeParentCode}"); + this.${businessName}Options.push(data); + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + $column.javaField: []#if($foreach.count != $columns.size()),#end +#else + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + this.daterange${AttrName} = []; +#end +#end + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 新增按钮操作 */ + handleAdd(row) { + this.reset(); + this.getTreeselect(); + if (row != null && row.${treeCode}) { + this.form.${treeParentCode} = row.${treeCode}; + } else { + this.form.${treeParentCode} = 0; + } + this.open = true; + this.title = "添加${functionName}"; + }, + /** 展开/折叠操作 */ + toggleExpandAll() { + this.refreshTable = false; + this.isExpandAll = !this.isExpandAll; + this.$nextTick(() => { + this.refreshTable = true; + }); + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + this.getTreeselect(); + if (row != null) { + this.form.${treeParentCode} = row.${treeParentCode}; + } + get${BusinessName}(row.${pkColumn.javaField}).then(response => { + this.form = response.data; +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + this.form.$column.javaField = this.form.${column.javaField}.split(","); +#end +#end + this.open = true; + this.title = "修改${functionName}"; + }); + }, + /** 提交按钮 */ + submitForm() { + this.#[[$]]#refs["form"].validate(valid => { + if (valid) { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + this.form.$column.javaField = this.form.${column.javaField}.join(","); +#end +#end + if (this.form.${pkColumn.javaField} != null) { + update${BusinessName}(this.form).then(response => { + this.#[[$modal]]#.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + add${BusinessName}(this.form).then(response => { + this.#[[$modal]]#.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() { + return del${BusinessName}(row.${pkColumn.javaField}); + }).then(() => { + this.getList(); + this.#[[$modal]]#.msgSuccess("删除成功"); + }).catch(() => {}); + } + } +}; +</script> diff --git a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 0000000..6296014 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,602 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> +#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") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-input + v-model="queryParams.${column.javaField}" + placeholder="请输入${comment}" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option + v-for="dict in dict.type.${dictType}" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-date-picker clearable + v-model="queryParams.${column.javaField}" + type="date" + value-format="yyyy-MM-dd" + placeholder="请选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + <el-form-item label="${comment}"> + <el-date-picker + v-model="daterange${AttrName}" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> +#end +#end +#end + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['${moduleName}:${businessName}:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['${moduleName}:${businessName}:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['${moduleName}:${businessName}:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['${moduleName}:${businessName}:export']" + >导出</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> +#foreach($column in $columns) +#set($javaField=$column.javaField) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.pk) + <el-table-column label="${comment}" align="center" prop="${javaField}" /> +#elseif($column.list && $column.htmlType == "datetime") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> +#elseif($column.list && $column.htmlType == "imageUpload") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="100"> + <template slot-scope="scope"> + <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/> + </template> + </el-table-column> +#elseif($column.list && "" != $column.dictType) + <el-table-column label="${comment}" align="center" prop="${javaField}"> + <template slot-scope="scope"> +#if($column.htmlType == "checkbox") + <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/> +#else + <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/> +#end + </template> + </el-table-column> +#elseif($column.list && "" != $javaField) + <el-table-column label="${comment}" align="center" prop="${javaField}" /> +#end +#end + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['${moduleName}:${businessName}:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['${moduleName}:${businessName}:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改${functionName}对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> +#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($column.htmlType == "input") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" placeholder="请输入${comment}" /> + </el-form-item> +#elseif($column.htmlType == "imageUpload") + <el-form-item label="${comment}" prop="${field}"> + <image-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "fileUpload") + <el-form-item label="${comment}" prop="${field}"> + <file-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "editor") + <el-form-item label="${comment}"> + <editor v-model="form.${field}" :min-height="192"/> + </el-form-item> +#elseif($column.htmlType == "select" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option + v-for="dict in dict.type.${dictType}" + :key="dict.value" + :label="dict.label" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :value="parseInt(dict.value)" +#else + :value="dict.value" +#end + ></el-option> + </el-select> + </el-form-item> +#elseif($column.htmlType == "select" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "checkbox" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox + v-for="dict in dict.type.${dictType}" + :key="dict.value" + :label="dict.value"> + {{dict.label}} + </el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "checkbox" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox>请选择字典生成</el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "radio" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio + v-for="dict in dict.type.${dictType}" + :key="dict.value" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :label="parseInt(dict.value)" +#else + :label="dict.value" +#end + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "radio" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio label="1">请选择字典生成</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "datetime") + <el-form-item label="${comment}" prop="${field}"> + <el-date-picker clearable + v-model="form.${field}" + type="date" + value-format="yyyy-MM-dd" + placeholder="请选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "textarea") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> + </el-form-item> +#end +#end +#end +#end +#if($table.sub) + <el-divider content-position="center">${subTable.functionName}信息</el-divider> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd${subClassName}">添加</el-button> + </el-col> + <el-col :span="1.5"> + <el-button type="danger" icon="el-icon-delete" size="mini" @click="handleDelete${subClassName}">删除</el-button> + </el-col> + </el-row> + <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}"> + <el-table-column type="selection" width="50" align="center" /> + <el-table-column label="序号" align="center" prop="index" width="50"/> +#foreach($column in $subTable.columns) +#set($javaField=$column.javaField) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.pk || $javaField == ${subTableFkclassName}) +#elseif($column.list && $column.htmlType == "input") + <el-table-column label="$comment" prop="${javaField}" width="150"> + <template slot-scope="scope"> + <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" /> + </template> + </el-table-column> +#elseif($column.list && $column.htmlType == "datetime") + <el-table-column label="$comment" prop="${javaField}" width="240"> + <template slot-scope="scope"> + <el-date-picker clearable v-model="scope.row.$javaField" type="date" value-format="yyyy-MM-dd" placeholder="请选择$comment" /> + </template> + </el-table-column> +#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType) + <el-table-column label="$comment" prop="${javaField}" width="150"> + <template slot-scope="scope"> + <el-select v-model="scope.row.$javaField" placeholder="请选择$comment"> + <el-option + v-for="dict in dict.type.$column.dictType" + :key="dict.value" + :label="dict.label" + :value="dict.value" + ></el-option> + </el-select> + </template> + </el-table-column> +#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType) + <el-table-column label="$comment" prop="${javaField}" width="150"> + <template slot-scope="scope"> + <el-select v-model="scope.row.$javaField" placeholder="请选择$comment"> + <el-option label="请选择字典生成" value="" /> + </el-select> + </template> + </el-table-column> +#end +#end + </el-table> +#end + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"; + +export default { + name: "${BusinessName}", +#if(${dicts} != '') + dicts: [${dicts}], +#end + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], +#if($table.sub) + // 子表选中数据 + checked${subClassName}: [], +#end + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // ${functionName}表格数据 + ${businessName}List: [], +#if($table.sub) + // ${subTable.functionName}表格数据 + ${subclassName}List: [], +#end + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + // $comment时间范围 + daterange${AttrName}: [], +#end +#end + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, +#foreach ($column in $columns) +#if($column.query) + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { +#foreach ($column in $columns) +#if($column.required) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end + $column.javaField: [ + { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end } + ]#if($foreach.count != $columns.size()),#end +#end +#end + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询${functionName}列表 */ + getList() { + this.loading = true; +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + this.queryParams.params = {}; +#break +#end +#end +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) { + this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0]; + this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1]; + } +#end +#end + list${BusinessName}(this.queryParams).then(response => { + this.${businessName}List = response.rows; + this.total = response.total; + this.loading = false; + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + $column.javaField: []#if($foreach.count != $columns.size()),#end +#else + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }; +#if($table.sub) + this.${subclassName}List = []; +#end + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + this.daterange${AttrName} = []; +#end +#end + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.${pkColumn.javaField}) + this.single = selection.length!==1 + this.multiple = !selection.length + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加${functionName}"; + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const ${pkColumn.javaField} = row.${pkColumn.javaField} || this.ids + get${BusinessName}(${pkColumn.javaField}).then(response => { + this.form = response.data; +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + this.form.$column.javaField = this.form.${column.javaField}.split(","); +#end +#end +#if($table.sub) + this.${subclassName}List = response.data.${subclassName}List; +#end + this.open = true; + this.title = "修改${functionName}"; + }); + }, + /** 提交按钮 */ + submitForm() { + this.#[[$]]#refs["form"].validate(valid => { + if (valid) { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + this.form.$column.javaField = this.form.${column.javaField}.join(","); +#end +#end +#if($table.sub) + this.form.${subclassName}List = this.${subclassName}List; +#end + if (this.form.${pkColumn.javaField} != null) { + update${BusinessName}(this.form).then(response => { + this.#[[$modal]]#.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + add${BusinessName}(this.form).then(response => { + this.#[[$modal]]#.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids; + this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() { + return del${BusinessName}(${pkColumn.javaField}s); + }).then(() => { + this.getList(); + this.#[[$modal]]#.msgSuccess("删除成功"); + }).catch(() => {}); + }, +#if($table.sub) + /** ${subTable.functionName}序号 */ + row${subClassName}Index({ row, rowIndex }) { + row.index = rowIndex + 1; + }, + /** ${subTable.functionName}添加按钮操作 */ + handleAdd${subClassName}() { + let obj = {}; +#foreach($column in $subTable.columns) +#if($column.pk || $column.javaField == ${subTableFkclassName}) +#elseif($column.list && "" != $javaField) + obj.$column.javaField = ""; +#end +#end + this.${subclassName}List.push(obj); + }, + /** ${subTable.functionName}删除按钮操作 */ + handleDelete${subClassName}() { + if (this.checked${subClassName}.length == 0) { + this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据"); + } else { + const ${subclassName}List = this.${subclassName}List; + const checked${subClassName} = this.checked${subClassName}; + this.${subclassName}List = ${subclassName}List.filter(function(item) { + return checked${subClassName}.indexOf(item.index) == -1 + }); + } + }, + /** 复选框选中数据 */ + handle${subClassName}SelectionChange(selection) { + this.checked${subClassName} = selection.map(item => item.index) + }, +#end + /** 导出按钮操作 */ + handleExport() { + this.download('${moduleName}/${businessName}/export', { + ...this.queryParams + }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`) + } + } +}; +</script> diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..c54d62b --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px"> +#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") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-input + v-model="queryParams.${column.javaField}" + placeholder="请输入${comment}" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option + v-for="dict in ${dictType}" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-date-picker clearable + v-model="queryParams.${column.javaField}" + type="date" + value-format="YYYY-MM-DD" + placeholder="选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + <el-form-item label="${comment}" style="width: 308px"> + <el-date-picker + v-model="daterange${AttrName}" + value-format="YYYY-MM-DD" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> +#end +#end +#end + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> + <el-button icon="Refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="Plus" + @click="handleAdd" + v-hasPermi="['${moduleName}:${businessName}:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="Sort" + @click="toggleExpandAll" + >展开/折叠</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table + v-if="refreshTable" + v-loading="loading" + :data="${businessName}List" + row-key="${treeCode}" + :default-expand-all="isExpandAll" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + > +#foreach($column in $columns) +#set($javaField=$column.javaField) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.pk) +#elseif($column.list && $column.htmlType == "datetime") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> +#elseif($column.list && $column.htmlType == "imageUpload") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="100"> + <template #default="scope"> + <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/> + </template> + </el-table-column> +#elseif($column.list && "" != $column.dictType) + <el-table-column label="${comment}" align="center" prop="${javaField}"> + <template #default="scope"> +#if($column.htmlType == "checkbox") + <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/> +#else + <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/> +#end + </template> + </el-table-column> +#elseif($column.list && "" != $javaField) +#if(${foreach.index} == 1) + <el-table-column label="${comment}" prop="${javaField}" /> +#else + <el-table-column label="${comment}" align="center" prop="${javaField}" /> +#end +#end +#end + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button> + <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button> + <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button> + </template> + </el-table-column> + </el-table> + + <!-- 添加或修改${functionName}对话框 --> + <el-dialog :title="title" v-model="open" width="500px" append-to-body> + <el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px"> +#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) + <el-form-item label="${comment}" prop="${treeParentCode}"> + <el-tree-select + v-model="form.${treeParentCode}" + :data="${businessName}Options" + :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }" + value-key="${treeCode}" + placeholder="请选择${comment}" + check-strictly + /> + </el-form-item> +#elseif($column.htmlType == "input") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" placeholder="请输入${comment}" /> + </el-form-item> +#elseif($column.htmlType == "imageUpload") + <el-form-item label="${comment}" prop="${field}"> + <image-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "fileUpload") + <el-form-item label="${comment}" prop="${field}"> + <file-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "editor") + <el-form-item label="${comment}"> + <editor v-model="form.${field}" :min-height="192"/> + </el-form-item> +#elseif($column.htmlType == "select" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option + v-for="dict in ${dictType}" + :key="dict.value" + :label="dict.label" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :value="parseInt(dict.value)" +#else + :value="dict.value" +#end + ></el-option> + </el-select> + </el-form-item> +#elseif($column.htmlType == "select" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "checkbox" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox + v-for="dict in ${dictType}" + :key="dict.value" + :label="dict.value"> + {{dict.label}} + </el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "checkbox" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox>请选择字典生成</el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "radio" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio + v-for="dict in ${dictType}" + :key="dict.value" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :label="parseInt(dict.value)" +#else + :label="dict.value" +#end + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "radio" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio label="1">请选择字典生成</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "datetime") + <el-form-item label="${comment}" prop="${field}"> + <el-date-picker clearable + v-model="form.${field}" + type="date" + value-format="YYYY-MM-DD" + placeholder="选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "textarea") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> + </el-form-item> +#end +#end +#end +#end + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup name="${BusinessName}"> +import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"; + +const { proxy } = getCurrentInstance(); +#if(${dicts} != '') +#set($dictsNoSymbol=$dicts.replace("'", "")) +const { ${dictsNoSymbol} } = proxy.useDict(${dicts}); +#end + +const ${businessName}List = ref([]); +const ${businessName}Options = ref([]); +const open = ref(false); +const loading = ref(true); +const showSearch = ref(true); +const title = ref(""); +const isExpandAll = ref(true); +const refreshTable = ref(true); +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +const daterange${AttrName} = ref([]); +#end +#end + +const data = reactive({ + form: {}, + queryParams: { + #foreach ($column in $columns) +#if($column.query) + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }, + rules: { + #foreach ($column in $columns) +#if($column.required) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end + $column.javaField: [ + { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end } + ]#if($foreach.count != $columns.size()),#end +#end +#end + } +}); + +const { queryParams, form, rules } = toRefs(data); + +/** 查询${functionName}列表 */ +function getList() { + loading.value = true; +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + queryParams.value.params = {}; +#break +#end +#end +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + if (null != daterange${AttrName} && '' != daterange${AttrName}) { + queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0]; + queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1]; + } +#end +#end + list${BusinessName}(queryParams.value).then(response => { + ${businessName}List.value = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}"); + loading.value = false; + }); +} + +/** 查询${functionName}下拉树结构 */ +function getTreeselect() { + list${BusinessName}().then(response => { + ${businessName}Options.value = []; + const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] }; + data.children = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}"); + ${businessName}Options.value.push(data); + }); +} + +// 取消按钮 +function cancel() { + open.value = false; + reset(); +} + +// 表单重置 +function reset() { + form.value = { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + $column.javaField: []#if($foreach.count != $columns.size()),#end +#else + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }; + proxy.resetForm("${businessName}Ref"); +} + +/** 搜索按钮操作 */ +function handleQuery() { + getList(); +} + +/** 重置按钮操作 */ +function resetQuery() { +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + daterange${AttrName}.value = []; +#end +#end + proxy.resetForm("queryRef"); + handleQuery(); +} + +/** 新增按钮操作 */ +function handleAdd(row) { + reset(); + getTreeselect(); + if (row != null && row.${treeCode}) { + form.value.${treeParentCode} = row.${treeCode}; + } else { + form.value.${treeParentCode} = 0; + } + open.value = true; + title.value = "添加${functionName}"; +} + +/** 展开/折叠操作 */ +function toggleExpandAll() { + refreshTable.value = false; + isExpandAll.value = !isExpandAll.value; + nextTick(() => { + refreshTable.value = true; + }); +} + +/** 修改按钮操作 */ +async function handleUpdate(row) { + reset(); + await getTreeselect(); + if (row != null) { + form.value.${treeParentCode} = row.${treeParentCode}; + } + get${BusinessName}(row.${pkColumn.javaField}).then(response => { + form.value = response.data; +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + form.value.$column.javaField = form.value.${column.javaField}.split(","); +#end +#end + open.value = true; + title.value = "修改${functionName}"; + }); +} + +/** 提交按钮 */ +function submitForm() { + proxy.#[[$]]#refs["${businessName}Ref"].validate(valid => { + if (valid) { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + form.value.$column.javaField = form.value.${column.javaField}.join(","); +#end +#end + if (form.value.${pkColumn.javaField} != null) { + update${BusinessName}(form.value).then(response => { + proxy.#[[$modal]]#.msgSuccess("修改成功"); + open.value = false; + getList(); + }); + } else { + add${BusinessName}(form.value).then(response => { + proxy.#[[$modal]]#.msgSuccess("新增成功"); + open.value = false; + getList(); + }); + } + } + }); +} + +/** 删除按钮操作 */ +function handleDelete(row) { + proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() { + return del${BusinessName}(row.${pkColumn.javaField}); + }).then(() => { + getList(); + proxy.#[[$modal]]#.msgSuccess("删除成功"); + }).catch(() => {}); +} + +getList(); +</script> diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..8b25665 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px"> +#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") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-input + v-model="queryParams.${column.javaField}" + placeholder="请输入${comment}" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option + v-for="dict in ${dictType}" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType) + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN") + <el-form-item label="${comment}" prop="${column.javaField}"> + <el-date-picker clearable + v-model="queryParams.${column.javaField}" + type="date" + value-format="YYYY-MM-DD" + placeholder="请选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + <el-form-item label="${comment}" style="width: 308px"> + <el-date-picker + v-model="daterange${AttrName}" + value-format="YYYY-MM-DD" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> +#end +#end +#end + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> + <el-button icon="Refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="Plus" + @click="handleAdd" + v-hasPermi="['${moduleName}:${businessName}:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="Edit" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['${moduleName}:${businessName}:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="Delete" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['${moduleName}:${businessName}:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="Download" + @click="handleExport" + v-hasPermi="['${moduleName}:${businessName}:export']" + >导出</el-button> + </el-col> + <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> +#foreach($column in $columns) +#set($javaField=$column.javaField) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.pk) + <el-table-column label="${comment}" align="center" prop="${javaField}" /> +#elseif($column.list && $column.htmlType == "datetime") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> +#elseif($column.list && $column.htmlType == "imageUpload") + <el-table-column label="${comment}" align="center" prop="${javaField}" width="100"> + <template #default="scope"> + <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/> + </template> + </el-table-column> +#elseif($column.list && "" != $column.dictType) + <el-table-column label="${comment}" align="center" prop="${javaField}"> + <template #default="scope"> +#if($column.htmlType == "checkbox") + <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/> +#else + <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/> +#end + </template> + </el-table-column> +#elseif($column.list && "" != $javaField) + <el-table-column label="${comment}" align="center" prop="${javaField}" /> +#end +#end + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button> + <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改${functionName}对话框 --> + <el-dialog :title="title" v-model="open" width="500px" append-to-body> + <el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px"> +#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($column.htmlType == "input") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" placeholder="请输入${comment}" /> + </el-form-item> +#elseif($column.htmlType == "imageUpload") + <el-form-item label="${comment}" prop="${field}"> + <image-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "fileUpload") + <el-form-item label="${comment}" prop="${field}"> + <file-upload v-model="form.${field}"/> + </el-form-item> +#elseif($column.htmlType == "editor") + <el-form-item label="${comment}"> + <editor v-model="form.${field}" :min-height="192"/> + </el-form-item> +#elseif($column.htmlType == "select" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option + v-for="dict in ${dictType}" + :key="dict.value" + :label="dict.label" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :value="parseInt(dict.value)" +#else + :value="dict.value" +#end + ></el-option> + </el-select> + </el-form-item> +#elseif($column.htmlType == "select" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-select v-model="form.${field}" placeholder="请选择${comment}"> + <el-option label="请选择字典生成" value="" /> + </el-select> + </el-form-item> +#elseif($column.htmlType == "checkbox" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox + v-for="dict in ${dictType}" + :key="dict.value" + :label="dict.value"> + {{dict.label}} + </el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "checkbox" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-checkbox-group v-model="form.${field}"> + <el-checkbox>请选择字典生成</el-checkbox> + </el-checkbox-group> + </el-form-item> +#elseif($column.htmlType == "radio" && "" != $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio + v-for="dict in ${dictType}" + :key="dict.value" +#if($column.javaType == "Integer" || $column.javaType == "Long") + :label="parseInt(dict.value)" +#else + :label="dict.value" +#end + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "radio" && $dictType) + <el-form-item label="${comment}" prop="${field}"> + <el-radio-group v-model="form.${field}"> + <el-radio label="1">请选择字典生成</el-radio> + </el-radio-group> + </el-form-item> +#elseif($column.htmlType == "datetime") + <el-form-item label="${comment}" prop="${field}"> + <el-date-picker clearable + v-model="form.${field}" + type="date" + value-format="YYYY-MM-DD" + placeholder="请选择${comment}"> + </el-date-picker> + </el-form-item> +#elseif($column.htmlType == "textarea") + <el-form-item label="${comment}" prop="${field}"> + <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" /> + </el-form-item> +#end +#end +#end +#end +#if($table.sub) + <el-divider content-position="center">${subTable.functionName}信息</el-divider> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="primary" icon="Plus" @click="handleAdd${subClassName}">添加</el-button> + </el-col> + <el-col :span="1.5"> + <el-button type="danger" icon="Delete" @click="handleDelete${subClassName}">删除</el-button> + </el-col> + </el-row> + <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}"> + <el-table-column type="selection" width="50" align="center" /> + <el-table-column label="序号" align="center" prop="index" width="50"/> +#foreach($column in $subTable.columns) +#set($javaField=$column.javaField) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($column.pk || $javaField == ${subTableFkclassName}) +#elseif($column.list && $column.htmlType == "input") + <el-table-column label="$comment" prop="${javaField}" width="150"> + <template #default="scope"> + <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" /> + </template> + </el-table-column> +#elseif($column.list && $column.htmlType == "datetime") + <el-table-column label="$comment" prop="${javaField}" width="240"> + <template #default="scope"> + <el-date-picker clearable + v-model="scope.row.$javaField" + type="date" + value-format="YYYY-MM-DD" + placeholder="请选择$comment"> + </el-date-picker> + </template> + </el-table-column> +#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType) + <el-table-column label="$comment" prop="${javaField}" width="150"> + <template #default="scope"> + <el-select v-model="scope.row.$javaField" placeholder="请选择$comment"> + <el-option + v-for="dict in $column.dictType" + :key="dict.value" + :label="dict.label" + :value="dict.value" + ></el-option> + </el-select> + </template> + </el-table-column> +#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType) + <el-table-column label="$comment" prop="${javaField}" width="150"> + <template #default="scope"> + <el-select v-model="scope.row.$javaField" placeholder="请选择$comment"> + <el-option label="请选择字典生成" value="" /> + </el-select> + </template> + </el-table-column> +#end +#end + </el-table> +#end + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup name="${BusinessName}"> +import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}"; + +const { proxy } = getCurrentInstance(); +#if(${dicts} != '') +#set($dictsNoSymbol=$dicts.replace("'", "")) +const { ${dictsNoSymbol} } = proxy.useDict(${dicts}); +#end + +const ${businessName}List = ref([]); +#if($table.sub) +const ${subclassName}List = ref([]); +#end +const open = ref(false); +const loading = ref(true); +const showSearch = ref(true); +const ids = ref([]); +#if($table.sub) +const checked${subClassName} = ref([]); +#end +const single = ref(true); +const multiple = ref(true); +const total = ref(0); +const title = ref(""); +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +const daterange${AttrName} = ref([]); +#end +#end + +const data = reactive({ + form: {}, + queryParams: { + pageNum: 1, + pageSize: 10, + #foreach ($column in $columns) +#if($column.query) + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }, + rules: { + #foreach ($column in $columns) +#if($column.required) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end + $column.javaField: [ + { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end } + ]#if($foreach.count != $columns.size()),#end +#end +#end + } +}); + +const { queryParams, form, rules } = toRefs(data); + +/** 查询${functionName}列表 */ +function getList() { + loading.value = true; +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") + queryParams.value.params = {}; +#break +#end +#end +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + if (null != daterange${AttrName} && '' != daterange${AttrName}) { + queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0]; + queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1]; + } +#end +#end + list${BusinessName}(queryParams.value).then(response => { + ${businessName}List.value = response.rows; + total.value = response.total; + loading.value = false; + }); +} + +// 取消按钮 +function cancel() { + open.value = false; + reset(); +} + +// 表单重置 +function reset() { + form.value = { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + $column.javaField: []#if($foreach.count != $columns.size()),#end +#else + $column.javaField: null#if($foreach.count != $columns.size()),#end +#end +#end + }; +#if($table.sub) + ${subclassName}List.value = []; +#end + proxy.resetForm("${businessName}Ref"); +} + +/** 搜索按钮操作 */ +function handleQuery() { + queryParams.value.pageNum = 1; + getList(); +} + +/** 重置按钮操作 */ +function resetQuery() { +#foreach ($column in $columns) +#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) + daterange${AttrName}.value = []; +#end +#end + proxy.resetForm("queryRef"); + handleQuery(); +} + +// 多选框选中数据 +function handleSelectionChange(selection) { + ids.value = selection.map(item => item.${pkColumn.javaField}); + single.value = selection.length != 1; + multiple.value = !selection.length; +} + +/** 新增按钮操作 */ +function handleAdd() { + reset(); + open.value = true; + title.value = "添加${functionName}"; +} + +/** 修改按钮操作 */ +function handleUpdate(row) { + reset(); + const _${pkColumn.javaField} = row.${pkColumn.javaField} || ids.value + get${BusinessName}(_${pkColumn.javaField}).then(response => { + form.value = response.data; +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + form.value.$column.javaField = form.value.${column.javaField}.split(","); +#end +#end +#if($table.sub) + ${subclassName}List.value = response.data.${subclassName}List; +#end + open.value = true; + title.value = "修改${functionName}"; + }); +} + +/** 提交按钮 */ +function submitForm() { + proxy.#[[$]]#refs["${businessName}Ref"].validate(valid => { + if (valid) { +#foreach ($column in $columns) +#if($column.htmlType == "checkbox") + form.value.$column.javaField = form.value.${column.javaField}.join(","); +#end +#end +#if($table.sub) + form.value.${subclassName}List = ${subclassName}List.value; +#end + if (form.value.${pkColumn.javaField} != null) { + update${BusinessName}(form.value).then(response => { + proxy.#[[$modal]]#.msgSuccess("修改成功"); + open.value = false; + getList(); + }); + } else { + add${BusinessName}(form.value).then(response => { + proxy.#[[$modal]]#.msgSuccess("新增成功"); + open.value = false; + getList(); + }); + } + } + }); +} + +/** 删除按钮操作 */ +function handleDelete(row) { + const _${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value; + proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').then(function() { + return del${BusinessName}(_${pkColumn.javaField}s); + }).then(() => { + getList(); + proxy.#[[$modal]]#.msgSuccess("删除成功"); + }).catch(() => {}); +} + +#if($table.sub) +/** ${subTable.functionName}序号 */ +function row${subClassName}Index({ row, rowIndex }) { + row.index = rowIndex + 1; +} + +/** ${subTable.functionName}添加按钮操作 */ +function handleAdd${subClassName}() { + let obj = {}; +#foreach($column in $subTable.columns) +#if($column.pk || $column.javaField == ${subTableFkclassName}) +#elseif($column.list && "" != $javaField) + obj.$column.javaField = ""; +#end +#end + ${subclassName}List.value.push(obj); +} + +/** ${subTable.functionName}删除按钮操作 */ +function handleDelete${subClassName}() { + if (checked${subClassName}.value.length == 0) { + proxy.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据"); + } else { + const ${subclassName}s = ${subclassName}List.value; + const checked${subClassName}s = checked${subClassName}.value; + ${subclassName}List.value = ${subclassName}s.filter(function(item) { + return checked${subClassName}s.indexOf(item.index) == -1 + }); + } +} + +/** 复选框选中数据 */ +function handle${subClassName}SelectionChange(selection) { + checked${subClassName}.value = selection.map(item => item.index) +} + +#end +/** 导出按钮操作 */ +function handleExport() { + proxy.download('${moduleName}/${businessName}/export', { + ...queryParams.value + }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`) +} + +getList(); +</script> diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt b/ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt new file mode 100644 index 0000000..99239bb --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt @@ -0,0 +1 @@ +���ʹ�õ���RuoYi-Vue3ǰ�ˣ���ô��Ҫ����һ�´�Ŀ¼��ģ��index.vue.vm��index-tree.vue.vm�ļ����ϼ�vueĿ¼�� \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..0ceb3d8 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="${packageName}.mapper.${ClassName}Mapper"> + + <resultMap type="${ClassName}" id="${ClassName}Result"> +#foreach ($column in $columns) + <result property="${column.javaField}" column="${column.columnName}" /> +#end + </resultMap> +#if($table.sub) + + <resultMap id="${ClassName}${subClassName}Result" type="${ClassName}" extends="${ClassName}Result"> + <collection property="${subclassName}List" notNullColumn="sub_${subTable.pkColumn.columnName}" javaType="java.util.List" resultMap="${subClassName}Result" /> + </resultMap> + + <resultMap type="${subClassName}" id="${subClassName}Result"> +#foreach ($column in $subTable.columns) + <result property="${column.javaField}" column="sub_${column.columnName}" /> +#end + </resultMap> +#end + + <sql id="select${ClassName}Vo"> + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + </sql> + + <select id="select${ClassName}List" parameterType="${ClassName}" resultMap="${ClassName}Result"> + <include refid="select${ClassName}Vo"/> + <where> +#foreach($column in $columns) +#set($queryType=$column.queryType) +#set($javaField=$column.javaField) +#set($javaType=$column.javaType) +#set($columnName=$column.columnName) +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#if($column.query) +#if($column.queryType == "EQ") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName = #{$javaField}</if> +#elseif($queryType == "NE") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName != #{$javaField}</if> +#elseif($queryType == "GT") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName > #{$javaField}</if> +#elseif($queryType == "GTE") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName >= #{$javaField}</if> +#elseif($queryType == "LT") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName < #{$javaField}</if> +#elseif($queryType == "LTE") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName <= #{$javaField}</if> +#elseif($queryType == "LIKE") + <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName like concat('%', #{$javaField}, '%')</if> +#elseif($queryType == "BETWEEN") + <if test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''"> and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}</if> +#end +#end +#end + </where> + </select> + + <select id="select${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end"> +#if($table.crud || $table.tree) + <include refid="select${ClassName}Vo"/> + where ${pkColumn.columnName} = #{${pkColumn.javaField}} +#elseif($table.sub) + select#foreach($column in $columns) a.$column.columnName#if($foreach.count != $columns.size()),#end#end, + #foreach($column in $subTable.columns) b.$column.columnName as sub_$column.columnName#if($foreach.count != $subTable.columns.size()),#end#end + + from ${tableName} a + left join ${subTableName} b on b.${subTableFkName} = a.${pkColumn.columnName} + where a.${pkColumn.columnName} = #{${pkColumn.javaField}} +#end + </select> + + <insert id="insert${ClassName}" parameterType="${ClassName}"#if($pkColumn.increment) useGeneratedKeys="true" keyProperty="$pkColumn.javaField"#end> + insert into ${tableName} + <trim prefix="(" suffix=")" suffixOverrides=","> +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + <if test="$column.javaField != null#if($column.javaType == 'String' && $column.required) and $column.javaField != ''#end">$column.columnName,</if> +#end +#end + </trim> + <trim prefix="values (" suffix=")" suffixOverrides=","> +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + <if test="$column.javaField != null#if($column.javaType == 'String' && $column.required) and $column.javaField != ''#end">#{$column.javaField},</if> +#end +#end + </trim> + </insert> + + <update id="update${ClassName}" parameterType="${ClassName}"> + update ${tableName} + <trim prefix="SET" suffixOverrides=","> +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + <if test="$column.javaField != null#if($column.javaType == 'String' && $column.required) and $column.javaField != ''#end">$column.columnName = #{$column.javaField},</if> +#end +#end + </trim> + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + </update> + + <delete id="delete${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}"> + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + </delete> + + <delete id="delete${ClassName}By${pkColumn.capJavaField}s" parameterType="String"> + delete from ${tableName} where ${pkColumn.columnName} in + <foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")"> + #{${pkColumn.javaField}} + </foreach> + </delete> +#if($table.sub) + + <delete id="delete${subClassName}By${subTableFkClassName}s" parameterType="String"> + delete from ${subTableName} where ${subTableFkName} in + <foreach item="${subTableFkclassName}" collection="array" open="(" separator="," close=")"> + #{${subTableFkclassName}} + </foreach> + </delete> + + <delete id="delete${subClassName}By${subTableFkClassName}" parameterType="${pkColumn.javaType}"> + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + </delete> + + <insert id="batch${subClassName}"> + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + <foreach item="item" index="index" collection="list" separator=","> + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + </foreach> + </insert> +#end +</mapper> \ No newline at end of file diff --git a/ruoyi-quartz/pom.xml b/ruoyi-quartz/pom.xml new file mode 100644 index 0000000..43dcb2f --- /dev/null +++ b/ruoyi-quartz/pom.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>ruoyi-quartz</artifactId> + + <description> + quartz定时任务 + </description> + + <dependencies> + + <!-- 定时任务 --> + <dependency> + <groupId>org.quartz-scheduler</groupId> + <artifactId>quartz</artifactId> + <exclusions> + <exclusion> + <groupId>com.mchange</groupId> + <artifactId>c3p0</artifactId> + </exclusion> + </exclusions> + </dependency> + + <!-- 通用工具--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-common</artifactId> + </dependency> + + </dependencies> + +</project> \ No newline at end of file diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..a558170 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//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", "1"); +// 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; +// } +//} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java new file mode 100644 index 0000000..38380d5 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java @@ -0,0 +1,185 @@ +package com.ruoyi.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.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 + */ +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { +// startPage(); + List<SysJob> list = jobService.selectJobList(sysJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ +// @PreAuthorize("@ss.hasPermi('monitor:job:export')") +// @Log(title = "定时任务", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysJob sysJob) +// { +// List<SysJob> list = jobService.selectJobList(sysJob); +// ExcelUtil<SysJob> util = new ExcelUtil<SysJob>(SysJob.class); +// util.exportExcel(response, list, "定时任务"); +// } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody 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(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody 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.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException + { + jobService.deleteJobByIds(jobIds); + return success(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..d9d1f71 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java @@ -0,0 +1,92 @@ +package com.ruoyi.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +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.quartz.domain.SysJobLog; +import com.ruoyi.quartz.service.ISysJobLogService; + +/** + * 调度日志操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { +// startPage(); + List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog); + return getDataTable(list); + } + + /** + * 导出定时任务调度日志列表 + */ +// @PreAuthorize("@ss.hasPermi('monitor:job:export')") +// @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) +// @PostMapping("/export") +// public void export(HttpServletResponse response, SysJobLog sysJobLog) +// { +// List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog); +// ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class); +// util.exportExcel(response, list, "调度日志"); +// } + + /** + * 根据调度编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); + } + + + /** + * 删除定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); + } + + /** + * 清空定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java new file mode 100644 index 0000000..277aa60 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java @@ -0,0 +1,169 @@ +package com.ruoyi.quartz.domain; + +import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonFormat; +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 +{ + 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 = 500, 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; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + 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(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..c5418b9 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java @@ -0,0 +1,154 @@ +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.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 stopTime; + + 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 getStopTime() + { + return stopTime; + } + + public void setStopTime(Date stopTime) + { + this.stopTime = stopTime; + } + + @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("stopTime", getStopTime()) + .toString(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..727d916 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.ruoyi.quartz.mapper; + +import java.util.List; +import com.ruoyi.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author ruoyi + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List<SysJobLog> selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List<SysJobLog> selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..20f45db --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.ruoyi.quartz.mapper; + +import java.util.List; +import com.ruoyi.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author ruoyi + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List<SysJob> selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List<SysJob> 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); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..8546792 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +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<SysJobLog> selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java new file mode 100644 index 0000000..437ade8 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +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<SysJob> 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 jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) 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); +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..812eed7 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +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.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<SysJobLog> 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 logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..77fdbb5 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,261 @@ +package com.ruoyi.quartz.service.impl; + +import java.util.List; +import javax.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.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<SysJob> jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List<SysJob> 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 jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + 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(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + 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); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java new file mode 100644 index 0000000..b03ebb3 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java @@ -0,0 +1,34 @@ +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("执行无参方法"); + } + + + + + + +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..731a5eb --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,107 @@ +package com.ruoyi.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +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<Date> threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + 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.setStopTime(new Date()); + long runMs = sysJobLog.getStopTime().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; +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java new file mode 100644 index 0000000..dd53839 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.ruoyi.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * 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()); + } + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..e3dc62c --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +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<Object[]> 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<Object[]> 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<Object[]> getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List<Object[]> 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<Object[]> 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<Object[]> methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..5e13558 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +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); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..e975326 --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +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); + } +} diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..21fedae --- /dev/null +++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java @@ -0,0 +1,141 @@ +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<? extends Job> 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<? extends Job> 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.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); + } +} diff --git a/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..e608e42 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.quartz.mapper.SysJobLogMapper"> + + <resultMap type="SysJobLog" id="SysJobLogResult"> + <id property="jobLogId" column="job_log_id" /> + <result property="jobName" column="job_name" /> + <result property="jobGroup" column="job_group" /> + <result property="invokeTarget" column="invoke_target" /> + <result property="jobMessage" column="job_message" /> + <result property="status" column="status" /> + <result property="exceptionInfo" column="exception_info" /> + <result property="createTime" column="create_time" /> + </resultMap> + + <sql id="selectJobLogVo"> + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + </sql> + + <select id="selectJobLogList" parameterType="SysJobLog" resultMap="SysJobLogResult"> + <include refid="selectJobLogVo"/> + <where> + <if test="jobName != null and jobName != ''"> + AND job_name like concat('%', #{jobName}, '%') + </if> + <if test="jobGroup != null and jobGroup != ''"> + AND job_group = #{jobGroup} + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + <if test="invokeTarget != null and invokeTarget != ''"> + AND invoke_target like concat('%', #{invokeTarget}, '%') + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + and date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + and date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + </if> + </where> + </select> + + <select id="selectJobLogAll" resultMap="SysJobLogResult"> + <include refid="selectJobLogVo"/> + </select> + + <select id="selectJobLogById" parameterType="Long" resultMap="SysJobLogResult"> + <include refid="selectJobLogVo"/> + where job_log_id = #{jobLogId} + </select> + + <delete id="deleteJobLogById" parameterType="Long"> + delete from sys_job_log where job_log_id = #{jobLogId} + </delete> + + <delete id="deleteJobLogByIds" parameterType="Long"> + delete from sys_job_log where job_log_id in + <foreach collection="array" item="jobLogId" open="(" separator="," close=")"> + #{jobLogId} + </foreach> + </delete> + + <update id="cleanJobLog"> + truncate table sys_job_log + </update> + + <insert id="insertJobLog" parameterType="SysJobLog"> + insert into sys_job_log( + <if test="jobLogId != null and jobLogId != 0">job_log_id,</if> + <if test="jobName != null and jobName != ''">job_name,</if> + <if test="jobGroup != null and jobGroup != ''">job_group,</if> + <if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if> + <if test="jobMessage != null and jobMessage != ''">job_message,</if> + <if test="status != null and status != ''">status,</if> + <if test="exceptionInfo != null and exceptionInfo != ''">exception_info,</if> + create_time + )values( + <if test="jobLogId != null and jobLogId != 0">#{jobLogId},</if> + <if test="jobName != null and jobName != ''">#{jobName},</if> + <if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if> + <if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if> + <if test="jobMessage != null and jobMessage != ''">#{jobMessage},</if> + <if test="status != null and status != ''">#{status},</if> + <if test="exceptionInfo != null and exceptionInfo != ''">#{exceptionInfo},</if> + sysdate() + ) + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..5605c44 --- /dev/null +++ b/ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.quartz.mapper.SysJobMapper"> + + <resultMap type="SysJob" id="SysJobResult"> + <id property="jobId" column="job_id" /> + <result property="jobName" column="job_name" /> + <result property="jobGroup" column="job_group" /> + <result property="invokeTarget" column="invoke_target" /> + <result property="cronExpression" column="cron_expression" /> + <result property="misfirePolicy" column="misfire_policy" /> + <result property="concurrent" column="concurrent" /> + <result property="status" column="status" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + <result property="remark" column="remark" /> + </resultMap> + + <sql id="selectJobVo"> + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + </sql> + + <select id="selectJobList" parameterType="SysJob" resultMap="SysJobResult"> + <include refid="selectJobVo"/> + <where> + <if test="jobName != null and jobName != ''"> + AND job_name like concat('%', #{jobName}, '%') + </if> + <if test="jobGroup != null and jobGroup != ''"> + AND job_group = #{jobGroup} + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + <if test="invokeTarget != null and invokeTarget != ''"> + AND invoke_target like concat('%', #{invokeTarget}, '%') + </if> + </where> + </select> + + <select id="selectJobAll" resultMap="SysJobResult"> + <include refid="selectJobVo"/> + </select> + + <select id="selectJobById" parameterType="Long" resultMap="SysJobResult"> + <include refid="selectJobVo"/> + where job_id = #{jobId} + </select> + + <delete id="deleteJobById" parameterType="Long"> + delete from sys_job where job_id = #{jobId} + </delete> + + <delete id="deleteJobByIds" parameterType="Long"> + delete from sys_job where job_id in + <foreach collection="array" item="jobId" open="(" separator="," close=")"> + #{jobId} + </foreach> + </delete> + + <update id="updateJob" parameterType="SysJob"> + update sys_job + <set> + <if test="jobName != null and jobName != ''">job_name = #{jobName},</if> + <if test="jobGroup != null and jobGroup != ''">job_group = #{jobGroup},</if> + <if test="invokeTarget != null and invokeTarget != ''">invoke_target = #{invokeTarget},</if> + <if test="cronExpression != null and cronExpression != ''">cron_expression = #{cronExpression},</if> + <if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy = #{misfirePolicy},</if> + <if test="concurrent != null and concurrent != ''">concurrent = #{concurrent},</if> + <if test="status !=null">status = #{status},</if> + <if test="remark != null and remark != ''">remark = #{remark},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where job_id = #{jobId} + </update> + + <insert id="insertJob" parameterType="SysJob" useGeneratedKeys="true" keyProperty="jobId"> + insert into sys_job( + <if test="jobId != null and jobId != 0">job_id,</if> + <if test="jobName != null and jobName != ''">job_name,</if> + <if test="jobGroup != null and jobGroup != ''">job_group,</if> + <if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if> + <if test="cronExpression != null and cronExpression != ''">cron_expression,</if> + <if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy,</if> + <if test="concurrent != null and concurrent != ''">concurrent,</if> + <if test="status != null and status != ''">status,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="jobId != null and jobId != 0">#{jobId},</if> + <if test="jobName != null and jobName != ''">#{jobName},</if> + <if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if> + <if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if> + <if test="cronExpression != null and cronExpression != ''">#{cronExpression},</if> + <if test="misfirePolicy != null and misfirePolicy != ''">#{misfirePolicy},</if> + <if test="concurrent != null and concurrent != ''">#{concurrent},</if> + <if test="status != null and status != ''">#{status},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml new file mode 100644 index 0000000..5adce03 --- /dev/null +++ b/ruoyi-system/pom.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>ruoyi</artifactId> + <groupId>com.ruoyi</groupId> + <version>3.8.6</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>ruoyi-system</artifactId> + + <description> + system系统模块 + </description> + + <dependencies> + <dependency> + <groupId>com.qcloud</groupId> + <artifactId>cos_api</artifactId> + <version>5.6.227</version> + </dependency> + <!-- 通用工具--> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-common</artifactId> +<!-- <exclusions>--> +<!-- <exclusion>--> +<!-- <groupId>com.github.pagehelper</groupId>--> +<!-- <artifactId>pagehelper-spring-boot-starter</artifactId>--> +<!-- </exclusion>--> +<!-- </exclusions>--> + </dependency> + <dependency> + <groupId>com.ruoyi</groupId> + <artifactId>ruoyi-quartz</artifactId> + </dependency> + + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-spring-boot-starter</artifactId> + <version>4.0.0</version> + <exclusions> + <exclusion> + <artifactId>guava</artifactId> + <groupId>com.google.guava</groupId> + </exclusion> + </exclusions> + </dependency> + + <!--mybatis-plus--> + <dependency> + <groupId>com.baomidou</groupId> + <artifactId>mybatis-plus-boot-starter</artifactId> + <version>3.5.2</version> + </dependency> + <!--集成EasyExcel --> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>easyexcel</artifactId> + <version>2.1.6</version> + </dependency> + + +<!-- <dependency>--> +<!-- <groupId>org.apache.httpcomponents</groupId>--> +<!-- <artifactId>httpcore</artifactId>--> +<!-- <version>4.3.2</version>--> +<!-- </dependency>--> + +<!-- <dependency>--> +<!-- <groupId>org.springframework</groupId>--> +<!-- <artifactId>spring-test</artifactId>--> +<!-- <version>5.1.3.RELEASE</version>--> +<!-- </dependency>--> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jsr310</artifactId> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/DeployBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/DeployBO.java new file mode 100644 index 0000000..7adf52f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/DeployBO.java @@ -0,0 +1,15 @@ +package com.ruoyi.system.bo; + +import com.aizuda.bpm.engine.model.NodeModel; +import lombok.Data; + +@Data +public class DeployBO { + private String key; + private String name; + private String instanceUrl; + /** + * 流程定义 + */ + private NodeModel nodeConfig; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessAgreeBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessAgreeBO.java new file mode 100644 index 0000000..b52551e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessAgreeBO.java @@ -0,0 +1,36 @@ +package com.ruoyi.system.bo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 流程审批请求参数 同意 + * + * <p> + * 尊重知识产权,不允许非法使用,后果自负 + * </p> + * + */ +@Data +public class ProcessAgreeBO { + + /** + * 任务id + */ + private String taskId; + + /** + * 理由 + */ + private String remark; + /** + * 图片 + */ + private String pictures; + /** + * 审批用户id + */ + @ApiModelProperty(value = "前端忽略") + private Long userId; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessCreateBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessCreateBO.java new file mode 100644 index 0000000..ed40541 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessCreateBO.java @@ -0,0 +1,21 @@ +package com.ruoyi.system.bo; + +import lombok.Data; + +@Data +public class ProcessCreateBO { + + + /** + * 模版名称 + */ + private String templateName; + + /** + * json流程模版 + */ + private String process; + + private String remark; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessModuleUpdateBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessModuleUpdateBO.java new file mode 100644 index 0000000..6510129 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessModuleUpdateBO.java @@ -0,0 +1,19 @@ +package com.ruoyi.system.bo; + +import lombok.Data; + +@Data +public class ProcessModuleUpdateBO { + + /** + * 模块id + */ + private String id; + + /** + * 流程id + */ + private String templateId; + private String remark; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessRefuseBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessRefuseBO.java new file mode 100644 index 0000000..6b0c34e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessRefuseBO.java @@ -0,0 +1,30 @@ +package com.ruoyi.system.bo; + +import lombok.Data; + +/** + * 流程审批请求参数 拒绝 + * + * <p> + * 尊重知识产权,不允许非法使用,后果自负 + * </p> + * + */ +@Data +public class ProcessRefuseBO { + + /** + * 任务id + */ + private String taskId; + + /** + * 理由 + */ + private String remark; + /** + * 图片 + */ + private String pictures; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessStartBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessStartBO.java new file mode 100644 index 0000000..6bd43bc --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessStartBO.java @@ -0,0 +1,57 @@ +package com.ruoyi.system.bo; + +import lombok.Data; + +import java.util.Map; + +@Data +public class ProcessStartBO { + + /** + * 阶段类型 + * 1入户调查 + * 2价格评估 + * 3协议签订 + * 4资金管理-预算资金 + * 5住宅临时安置补助费 + * 6停产停业经济损失补助费 + * 7安置情况 + */ + private String category; + + /** + * 任务中心-项目名称 + */ + private String name; + + /** + * 模块名称 + */ + private String moduleName; + + /** + * 系统摘要 + * 入户调查摘要:【镇/街】【征收实施单位】【调查户数】 + * 价格评估摘要:【镇/街】【征收实施单位】【价格评估合计】 + * 协议签订摘要:【镇/街】【征收实施单位】【权利人】【协议类型】 + * 预算资金摘要:【镇/街】【征收实施单位】【预算金额】 + * 住宅临时安置补助费摘要:【镇/街】【征收实施单位】【开始时间-截止时间】【申请金额】 + * 停产停业经济损失补助费摘要:【镇/街】【征收实施单位】【开始时间-截止时间】【申请金额】 + * 安置情况摘要:【镇/街】【征收实施单位】【批次名称】【安置类型】 + */ + private String remark; + + + /** + * 类型 1集体 2国有 + */ + private Integer type = 2; + + /** + * 变量:流程完成后需要修改状态的表id信息 + * 例如: + * variable.put("objectId",12); + */ + private Map<String, Object> variable; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTaskListBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTaskListBO.java new file mode 100644 index 0000000..750a429 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTaskListBO.java @@ -0,0 +1,52 @@ +package com.ruoyi.system.bo; + +import com.ruoyi.common.core.domain.BasePage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Setter +@Getter +public class ProcessTaskListBO extends BasePage { + /** + * 任务名称 + */ + @ApiModelProperty(value = "任务名称") + private String name; + + /** + * 任务名称 + */ + @ApiModelProperty(value = "任务名称") + private String moduleName; +// /** +// * 流程实例状态( 0,审批中 1,审批通过 2,审批拒绝 3) +// */ +// private Integer instanceState; + /** + * 创建人 + */ + @ApiModelProperty(value = "创建人") + private String createBy; + + @ApiModelProperty(value = "乙方名称") + private String partyTwoName; + @ApiModelProperty(value = "合同编号") + private String contractNumber; + @ApiModelProperty(value = "合同名称") + private String contractName; + @ApiModelProperty(value = "合同状态") + private String status; + @ApiModelProperty(value = "前端忽略") + private List<String> instanceIds; + + @ApiModelProperty(value = "时间筛选 1=1天 2=7天 3=30天") + private Integer timeType; + + @ApiModelProperty(value = "排序 1=最早到达 2=最新到达") + private Integer sortBy=2; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTemplatePageBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTemplatePageBO.java new file mode 100644 index 0000000..9502e83 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessTemplatePageBO.java @@ -0,0 +1,12 @@ +package com.ruoyi.system.bo; + +import lombok.Data; + +@Data +public class ProcessTemplatePageBO { + + private Integer currentPage; + private Integer pageSize; + private String name; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessUpdateBO.java b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessUpdateBO.java new file mode 100644 index 0000000..09ac837 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/bo/ProcessUpdateBO.java @@ -0,0 +1,22 @@ +package com.ruoyi.system.bo; + +import lombok.Data; + +@Data +public class ProcessUpdateBO { + + private String id; + + /** + * 模版名称 + */ + private String templateName; + + /** + * json流程模版 + */ + private String process; + + private String remark; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/code/SubmitTemplateReg.java b/ruoyi-system/src/main/java/com/ruoyi/system/code/SubmitTemplateReg.java new file mode 100644 index 0000000..8987982 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/code/SubmitTemplateReg.java @@ -0,0 +1,40 @@ +package com.ruoyi.system.code; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@ApiModel(value = "短信验证码实体") +public class SubmitTemplateReg implements Serializable { + + @ApiModelProperty(value = "企业名称") + private String ecName; + + @ApiModelProperty(value = "账号用户名") + private String apId; + + @ApiModelProperty(value = "模板ID") + private String templateId; + + @ApiModelProperty(value = "收信手机号码") + private String mobiles; + + @ApiModelProperty(value = "模板变量。格式:[“param1”,“param2”],无变量模板填['']") + private String params; + + @ApiModelProperty(value = "签名编码") + private String sign; + + @ApiModelProperty(value = "扩展码") + private String addSerial; + + @ApiModelProperty(value = "key值") + private String secretKey; + + @ApiModelProperty(value = "参数校验序列") + private String mac; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java new file mode 100644 index 0000000..83f0703 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysCache.java @@ -0,0 +1,81 @@ +package com.ruoyi.system.domain; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 缓存信息 + * + * @author ruoyi + */ +public class SysCache +{ + /** 缓存名称 */ + private String cacheName = ""; + + /** 缓存键名 */ + private String cacheKey = ""; + + /** 缓存内容 */ + private String cacheValue = ""; + + /** 备注 */ + private String remark = ""; + + public SysCache() + { + + } + + public SysCache(String cacheName, String remark) + { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) + { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + + public String getCacheName() + { + return cacheName; + } + + public void setCacheName(String cacheName) + { + this.cacheName = cacheName; + } + + public String getCacheKey() + { + return cacheKey; + } + + public void setCacheKey(String cacheKey) + { + this.cacheKey = cacheKey; + } + + public String getCacheValue() + { + return cacheValue; + } + + public void setCacheValue(String cacheValue) + { + this.cacheValue = cacheValue; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java new file mode 100644 index 0000000..146bcd6 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java @@ -0,0 +1,111 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 参数配置表 sys_config + * + * @author ruoyi + */ +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Excel(name = "参数主键") + private Long configId; + + /** 参数名称 */ + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Excel(name = "系统内置") + 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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java new file mode 100644 index 0000000..89adb21 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java @@ -0,0 +1,144 @@ +package com.ruoyi.system.domain; + +import java.util.Date; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.fasterxml.jackson.annotation.JsonFormat; +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 = "序号") + private Long infoId; + + /** 用户账号 */ + @Excel(name = "用户账号") + private String userName; + + /** 登录状态 0成功 1失败 */ + @Excel(name = "登录状态") + 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; + + /** 访问时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "访问时间", width = 30, exportFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + public Long getInfoId() + { + return infoId; + } + + public void setInfoId(Long infoId) + { + this.infoId = infoId; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + 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; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java new file mode 100644 index 0000000..8c07a54 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java @@ -0,0 +1,102 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +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.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; + + 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; + } + + @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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java new file mode 100644 index 0000000..19b3ca0 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java @@ -0,0 +1,322 @@ +package com.ruoyi.system.domain; + +import java.io.Serializable; +import java.util.Date; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 操作日志记录表 oper_log + * + * @author ruoyi + */ +public class SysOperLog implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 日志主键 */ + @Excel(name = "操作序号") + private Long operId; + + /** 操作模块 */ + @Excel(name = "操作模块") + private String title; + + /** 业务类型(0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据) */ + @Excel(name = "业务类型") + private Integer businessType; + + /** 业务类型数组 */ + private Integer[] businessTypes; + + /** 请求方法 */ + @Excel(name = "请求方法") + private String method; + + /** 请求方式 */ + @Excel(name = "请求方式") + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + @Excel(name = "操作类别") + 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 = "状态") + private Integer status; + + /** 错误消息 */ + @Excel(name = "错误消息") + private String errorMsg; + + /** 操作时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "操作时间", width = 30, exportFormat = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + + /** 消耗时间 */ + @Excel(name = "消耗时间", suffix = "毫秒") + private Long costTime; + + /** 公司名称 */ + @Excel(name = "公司名称") + private String companyName; + @Excel(name = "角色名称") + private String roleName; + @Excel(name = "手机号") + private String phonenumber; + @Excel(name = "用户id") + private Long userId; + @Excel(name = "操作人员名称") + private String nickName; + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public String getCompanyName() { + return companyName; + } + + public void setCompanyName(String companyName) { + this.companyName = companyName; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + 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; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java new file mode 100644 index 0000000..4111991 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java @@ -0,0 +1,124 @@ +package com.ruoyi.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 岗位表 sys_post + * + * @author ruoyi + */ +public class SysPost extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 岗位序号 */ + @Excel(name = "岗位序号") + private Long postId; + + /** 岗位编码 */ + @Excel(name = "岗位编码") + private String postCode; + + /** 岗位名称 */ + @Excel(name = "岗位名称") + private String postName; + + /** 岗位排序 */ + @Excel(name = "岗位排序") + private Integer postSort; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态") + 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; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getPostSort() + { + return postSort; + } + + public void setPostSort(Integer 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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java new file mode 100644 index 0000000..47b21bf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java @@ -0,0 +1,46 @@ +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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java new file mode 100644 index 0000000..de10a74 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java @@ -0,0 +1,46 @@ +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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java new file mode 100644 index 0000000..2bbd318 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java @@ -0,0 +1,113 @@ +package com.ruoyi.system.domain; + +/** + * 当前在线会话 + * + * @author ruoyi + */ +public class SysUserOnline +{ + /** 会话编号 */ + private String tokenId; + + /** 部门名称 */ + private String deptName; + + /** 用户名称 */ + private String userName; + + /** 登录IP地址 */ + private String ipaddr; + + /** 登录地址 */ + private String loginLocation; + + /** 浏览器类型 */ + private String browser; + + /** 操作系统 */ + private String os; + + /** 登录时间 */ + private Long loginTime; + + public String getTokenId() + { + return tokenId; + } + + public void setTokenId(String tokenId) + { + this.tokenId = tokenId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + 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 Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java new file mode 100644 index 0000000..6e8c416 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java @@ -0,0 +1,46 @@ +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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java new file mode 100644 index 0000000..4d15810 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java @@ -0,0 +1,46 @@ +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(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java new file mode 100644 index 0000000..a5d5fdc --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java @@ -0,0 +1,106 @@ +package com.ruoyi.system.domain.vo; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author ruoyi + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 <keep-alive>缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo() + { + } + + public MetaVo(String title, String icon) + { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) + { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) + { + this.link = link; + } + } + + public boolean isNoCache() + { + return noCache; + } + + public void setNoCache(boolean noCache) + { + this.noCache = noCache; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public String getLink() + { + return link; + } + + public void setLink(String link) + { + this.link = link; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java new file mode 100644 index 0000000..afff8c9 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java @@ -0,0 +1,148 @@ +package com.ruoyi.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author ruoyi + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo +{ + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List<RouterVo> children; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + public boolean getHidden() + { + return hidden; + } + + public void setHidden(boolean hidden) + { + this.hidden = hidden; + } + + public String getRedirect() + { + return redirect; + } + + public void setRedirect(String redirect) + { + this.redirect = redirect; + } + + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public Boolean getAlwaysShow() + { + return alwaysShow; + } + + public void setAlwaysShow(Boolean alwaysShow) + { + this.alwaysShow = alwaysShow; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List<RouterVo> getChildren() + { + return children; + } + + public void setChildren(List<RouterVo> children) + { + this.children = children; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysRoleDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysRoleDTO.java new file mode 100644 index 0000000..ed59ec0 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysRoleDTO.java @@ -0,0 +1,28 @@ +package com.ruoyi.system.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +@ApiModel(value = "角色新增DTO") +public class SysRoleDTO implements Serializable { + + @ApiModelProperty(value = "角色id") + private Long roleId; + + @ApiModelProperty(value = "角色名称") + private String roleName; + + @ApiModelProperty(value = "类型") + private Integer postType; + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "权限id集合") + private List<Long> menuIds; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysUserUpdateStatusDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysUserUpdateStatusDTO.java new file mode 100644 index 0000000..57f6eec --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/SysUserUpdateStatusDTO.java @@ -0,0 +1,22 @@ +package com.ruoyi.system.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@ApiModel(value = "用户修改状态DTO") +public class SysUserUpdateStatusDTO implements Serializable { + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "状态 帐号状态(0正常 1停用)") + private Integer status; + + @ApiModelProperty(value = "备注") + private String remark; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/TDeptUpAndDownDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/TDeptUpAndDownDTO.java new file mode 100644 index 0000000..53ca618 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/TDeptUpAndDownDTO.java @@ -0,0 +1,22 @@ +package com.ruoyi.system.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@ApiModel(value = "部门启用禁用dto") +public class TDeptUpAndDownDTO implements Serializable { + + @ApiModelProperty(value = "部门id") + private String id; + + @ApiModelProperty(value = "部门状态 1=启用 0=禁用") + private Integer status; + + @ApiModelProperty(value = "备注") + private String disableRemark; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/TExamineDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/TExamineDTO.java new file mode 100644 index 0000000..2a2006d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/TExamineDTO.java @@ -0,0 +1,14 @@ +package com.ruoyi.system.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "审批DTO") +public class TExamineDTO{ + @ApiModelProperty(value = "审批id") + private String id; + @ApiModelProperty(value = "审批状态") + private Integer status; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/dto/TerminateContractDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/dto/TerminateContractDTO.java new file mode 100644 index 0000000..ca20d5a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/dto/TerminateContractDTO.java @@ -0,0 +1,27 @@ +package com.ruoyi.system.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@ApiModel(value = "终止合同DTO") +public class TerminateContractDTO implements Serializable { + + @ApiModelProperty(value = "合同id") + private String id; + + @ApiModelProperty(value = "备注说明") + private String terminateRemark; + + @ApiModelProperty(value = "终止日期") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime terminateTime; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/export/ContractExport.java b/ruoyi-system/src/main/java/com/ruoyi/system/export/ContractExport.java new file mode 100644 index 0000000..ac9c72f --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/export/ContractExport.java @@ -0,0 +1,51 @@ +package com.ruoyi.system.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Date; + +@Data +@ApiModel(value = "合同导出excel") +public class ContractExport implements Serializable { + + @Excel(name = "合同编号",width = 30) + private String contractNumber; + + @Excel(name = "合同名称",width = 30) + private String contractName; + + @Excel(name = "甲方名称",width = 30) + private String partyOneName; + + @Excel(name = "乙方名称",width = 30) + private String partyTwoName; + + @Excel(name = "创建时间",width = 30) + private String createTime; + + + @Excel(name = "生效日期",width = 30) + private String startTime; + + @Excel(name = "终止日期",width = 30) + private String endTime; + + @Excel(name = "租金支付方式",width = 30) + private String payType; + + @Excel(name = "押金",width = 30) + private String deposit; + + @Excel(name = "状态",width = 30) + private String status; + +// @Excel(name = "计划周期",width = 30,replace = {"周计划_1","月计划_2"}) +// private Integer planCycle; + + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/export/OpticalInspectionExport.java b/ruoyi-system/src/main/java/com/ruoyi/system/export/OpticalInspectionExport.java new file mode 100644 index 0000000..b197df9 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/export/OpticalInspectionExport.java @@ -0,0 +1,41 @@ +package com.ruoyi.system.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +@ApiModel(value = "光缆巡检导出excel") +public class OpticalInspectionExport implements Serializable { + + @Excel(name = "工单号",width = 20) + private Integer id; + + @Excel(name = "工单名称",width = 30) + private String inspectionName; + + @Excel(name = "开始时间",width = 30,exportFormat = "yyyy-MM-dd HH:mm") + private Date startTime; + + @Excel(name = "截止时间",width = 30,exportFormat = "yyyy-MM-dd HH:mm") + private Date endTime; + + @Excel(name = "计划周期",width = 30,replace = {"周计划_1","月计划_2"}) + private Integer planCycle; + + @Excel(name = "路线",width = 30) + private String address; + + @Excel(name = "巡检点位",width = 30) + private Integer inspectionCount; + + @Excel(name = "提交人",width = 30) + private String createBy; + + @Excel(name = "巡检状态",width = 30,replace = {"待巡检_1","未完成_2","已完成_3"}) + private Integer state; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..792e9fc --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java @@ -0,0 +1,79 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysConfig; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 参数配置 数据层 + * + * @author ruoyi + */ +@Mapper +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<SysConfig> 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 参数ID + * @return 结果 + */ + public int deleteConfigById(Long configId); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + * @return 结果 + */ + public int deleteConfigByIds(Long[] configIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..7c19414 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java @@ -0,0 +1,119 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.core.domain.entity.SysDept; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 部门管理 数据层 + * + * @author ruoyi + */ +public interface SysDeptMapper +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List<SysDept> selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List<Long> selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List<SysDept> selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public int hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 + */ + public int checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param deptName 部门名称 + * @param parentId 父部门ID + * @return 结果 + */ + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); + + /** + * 新增部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + + /** + * 修改子元素关系 + * + * @param depts 子元素 + * @return 结果 + */ + public int updateDeptChildren(@Param("depts") List<SysDept> depts); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..de239b6 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java @@ -0,0 +1,100 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.core.domain.entity.SysDictData; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysDictDataMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List<SysDictData> selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List<SysDictData> 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 dictCodes 需要删除的字典数据ID + * @return 结果 + */ + public int deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增字典数据信息 + * + * @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); + + public String selectDictDataByTypeAndValue(@Param("dictType")String dictType, @Param("dictValue")Integer dictValue); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..c225f56 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,84 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.core.domain.entity.SysDictType; + +import java.util.List; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictTypeMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List<SysDictType> selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List<SysDictType> 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 dictIds 需要删除的字典ID + * @return 结果 + */ + public int deleteDictTypeByIds(Long[] dictIds); + + /** + * 新增字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public SysDictType checkDictTypeUnique(String dictType); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..157c60e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java @@ -0,0 +1,43 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysLogininfor; + +import java.util.List; + +/** + * 系统访问日志情况信息 数据层 + * + * @author ruoyi + */ +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List<SysLogininfor> selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..39b086e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java @@ -0,0 +1,135 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.core.domain.entity.SysMenu; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 菜单表 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysMenuMapper +{ + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List<SysMenu> selectMenuList(SysMenu menu); + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + public List<String> selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List<SysMenu> selectMenuListByUserId(SysMenu menu); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public List<String> selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List<String> selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List<SysMenu> selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List<SysMenu> selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + public List<Long> selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int hasChildByMenuId(Long menuId); + + /** + * 新增菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menuName 菜单名称 + * @param parentId 父菜单ID + * @return 结果 + */ + public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); + + List<SysMenu> selectListByRoleId(@Param("roleId")Long roleId); + + List<SysMenu> getAllInIds(@Param("menusId") List<Long> menusId); + + List<SysMenu> selectList(); + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..6e664e3 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java @@ -0,0 +1,61 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysNotice; + +import java.util.List; + +/** + * 通知公告表 数据层 + * + * @author ruoyi + */ +public interface SysNoticeMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List<SysNotice> selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 批量删除公告 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..3b8255c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java @@ -0,0 +1,71 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.query.SysOperLogQuery; +import com.ruoyi.system.vo.SysOperLogVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 操作日志 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List<SysOperLog> selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(@Param("operIds") List<Long> operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); + + /** + * 分页查询操作日志 + * @param query + * @param pageInfo + * @return + */ + List<SysOperLogVO> selectOperLogPageList(@Param("query") SysOperLogQuery query, @Param("pageInfo")PageInfo<SysOperLogVO> pageInfo); + + + /** + * 操作日志分页列表 + * @param query + * @return + */ +// List<SysOperLogVO> selectOperLogPageList(@Param("query") SysOperLogQuery query); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java new file mode 100644 index 0000000..1df68ce --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java @@ -0,0 +1,102 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysPost; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 岗位信息 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysPostMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List<SysPost> selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List<SysPost> selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List<Long> selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public List<SysPost> selectPostsByUserName(String userName); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 修改岗位信息 + * + * @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); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..0e674cd --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,47 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysRoleDept; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 角色与部门关联表 数据层 + * + * @author ruoyi + */ +@Mapper +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<SysRoleDept> roleDeptList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..0c07430 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java @@ -0,0 +1,132 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.system.query.SysRoleQuery; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 角色表 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysRoleMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List<SysRole> selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List<SysRole> selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List<SysRole> selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List<Long> selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List<SysRole> selectRolesByUserName(String userName); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); + + /** + * 修改角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 新增角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(@Param("roleIds") List<Long> roleIds); + + int selectCountByRoleName(@Param("roleName") String roleName); + +// List<SysRole> selectList(@Param("query")SysRoleQuery query); + + int selectCount(@Param("status")Integer status); + + void updateStatus(SysRole role); + + List<SysRole> selectListByDelFlag(@Param("delFlag")Integer delFlag); + + SysRole selectRoleByUserId(@Param("userId")Long userId); + + String selectByUserId(@Param("userId") Long user_id); + + List<SysRole> selectPageList(@Param("query")SysRoleQuery query,@Param("pageInfo") PageInfo<SysRole> pageInfo); + + List<SysRole> selectRoleByUserIds(@Param("roleIds")List<String> roleIds); + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..a8e6fa2 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysRoleMenu; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 角色与菜单关联表 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysRoleMenuMapper +{ + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(@Param("ids") List<Long> ids); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List<SysRoleMenu> roleMenuList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..58d9109 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -0,0 +1,179 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.query.SysUserQuery; +import com.ruoyi.system.vo.SysUserVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户表 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysUserMapper +{ + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List<SysUser> selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List<SysUser> selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List<SysUser> selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(@Param("ids") List<Long> userIds); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public SysUser checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); + + /** + * 查询用户集合通过用户id + * @param userIds + * @return + */ + List<SysUser> selectUserByIds(@Param("userIds") List<Long> userIds); + + List<SysUser> selectList(); + + Integer selectCount(@Param("status") Integer status); + + /** + * 获取用户列表 + * @param query + * @return + */ +// List<SysUserVO> selectUserPageList(@Param("query")SysUserQuery query); + + /** + * 获取用户黑名单列表 + * @param + * @return + */ +// List<SysUserVO> selectBlackPageList(@Param("query")SysUserQuery query); + + List<SysUser> selectListByNamePhone(@Param("name")String name); + + List<SysUser> selectUserByUserNameList(@Param("names")List<String> names); +// UserInfoVo userInfo(@Param("id") Long userId); + + SysUser selectByPhone(@Param("phonenumber") String phonenumber); + +// UserInfoVo getUserInfoBy(@Param("singleNum")String singleNum); + + Long getUserRole(@Param("userId") Long userId); + + int updateUserIfBlack(@Param("ids")List<Long> ids); + + List<SysUser> selectAllList(); + + List<SysUserVO> pageList(@Param("query")SysUserQuery query, @Param("pageInfo")PageInfo<SysUserVO> pageInfo); + + void updatePassword(@Param("id") Long id,@Param("s") String s); + + long selectIdByPhone(@Param("phonenumber") String phonenumber); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java new file mode 100644 index 0000000..d1d0fec --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java @@ -0,0 +1,47 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysUserPost; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 用户与岗位关联表 数据层 + * + * @author ruoyi + */ +@Mapper +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<SysUserPost> userPostList); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..b905725 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,74 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysUserRole; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户与角色关联表 数据层 + * + * @author ruoyi + */ +@Mapper +public interface SysUserRoleMapper +{ + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(@Param("ids") List<Long> ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List<SysUserRole> userRoleList); + + /** + * 批量新增用户角色信息 + * + * @param userRole 用户角色列表 + * @return 结果 + */ + public int insertUserRole(@Param("userRole") SysUserRole userRole); + + /** + * 删除用户和角色关联信息 + * + * @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); + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/query/SysOperLogQuery.java b/ruoyi-system/src/main/java/com/ruoyi/system/query/SysOperLogQuery.java new file mode 100644 index 0000000..f316f75 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/query/SysOperLogQuery.java @@ -0,0 +1,13 @@ +package com.ruoyi.system.query; + +import com.ruoyi.common.core.domain.BasePage; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +@Data +@ApiModel(value = "操作日志查询query") +public class SysOperLogQuery extends BasePage { + + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/query/SysRoleQuery.java b/ruoyi-system/src/main/java/com/ruoyi/system/query/SysRoleQuery.java new file mode 100644 index 0000000..ecf60c9 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/query/SysRoleQuery.java @@ -0,0 +1,17 @@ +package com.ruoyi.system.query; + +import com.ruoyi.common.core.domain.BasePage; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "角色查询Query") +public class SysRoleQuery extends BasePage { + + @ApiModelProperty(value = "角色名称") + private String roleName; + + @ApiModelProperty(value = "角色状态 0=正常,1=停用") + private Integer status; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/query/SysUserQuery.java b/ruoyi-system/src/main/java/com/ruoyi/system/query/SysUserQuery.java new file mode 100644 index 0000000..5b12765 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/query/SysUserQuery.java @@ -0,0 +1,29 @@ +package com.ruoyi.system.query; + +import com.ruoyi.common.core.domain.BasePage; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "账户列表query") +public class SysUserQuery extends BasePage { + + @ApiModelProperty(value = "姓名") + private String nickNameOrPhone; + + @ApiModelProperty(value = "角色id") + private List<Integer> roleIds; + + @ApiModelProperty(value = "部门id集合") + private List<String> deptIds; + + @ApiModelProperty(value = "状态 0=正常 1=停用") + private String status; + + @ApiModelProperty(value = "营业部id",hidden = true) + private String businessDeptId; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java new file mode 100644 index 0000000..b307776 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java @@ -0,0 +1,89 @@ +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); + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + public boolean selectCaptchaEnabled(); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List<SysConfig> selectConfigList(SysConfig config); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + public void deleteConfigByIds(Long[] configIds); + + /** + * 加载参数缓存数据 + */ + public void loadingConfigCache(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + public boolean checkConfigKeyUnique(SysConfig config); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java new file mode 100644 index 0000000..f228208 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java @@ -0,0 +1,124 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.common.core.domain.TreeSelect; +import com.ruoyi.common.core.domain.entity.SysDept; + +/** + * 部门管理 服务层 + * + * @author ruoyi + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List<SysDept> selectDeptList(SysDept dept); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + public List<TreeSelect> selectDeptTreeList(SysDept dept); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + public List<SysDept> buildDeptTree(List<SysDept> depts); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + public List<Long> selectDeptListByRoleId(Long roleId); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + public boolean checkDeptNameUnique(SysDept dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + public void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java new file mode 100644 index 0000000..9bc4f13 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +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<SysDictData> 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 dictCodes 需要删除的字典数据ID + */ + public void deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..01c1c1d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java @@ -0,0 +1,98 @@ +package com.ruoyi.system.service; + +import java.util.List; +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<SysDictType> selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List<SysDictType> selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List<SysDictData> selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 批量删除字典信息 + * + * @param dictIds 需要删除的字典ID + */ + public void deleteDictTypeByIds(Long[] dictIds); + + /** + * 加载字典缓存数据 + */ + 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); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java new file mode 100644 index 0000000..ce3151d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java @@ -0,0 +1,40 @@ +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<SysLogininfor> selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java new file mode 100644 index 0000000..af96e14 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java @@ -0,0 +1,166 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Set; +import com.ruoyi.common.core.domain.TreeSelect; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.system.domain.vo.RouterVo; + +/** + * 菜单 业务层 + * + * @author ruoyi + */ +public interface ISysMenuService +{ + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List<SysMenu> selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List<SysMenu> selectMenuList(SysMenu menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set<String> selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public Set<String> selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List<SysMenu> selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + public List<Long> selectMenuListByRoleId(Long roleId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + public List<RouterVo> buildMenus(List<SysMenu> menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List<SysMenu> buildMenuTree(List<SysMenu> menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List<TreeSelect> buildMenuTreeSelect(List<SysMenu> menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkMenuExistRole(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkMenuNameUnique(SysMenu menu); + + /** + * 获取当前角色的菜单列表 + * @param roleId + * @return + */ + List<SysMenu> selectListByRoleId(Long roleId); + + /** + * 通过所有菜单id查询菜单 + * @param menusId + * @return + */ + List<SysMenu> getAllInIds(List<Long> menusId); + + /** + * 查询所有菜单 + * @return + */ + List<SysMenu> selectList(); + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java new file mode 100644 index 0000000..47ce1b7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java @@ -0,0 +1,60 @@ +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<SysNotice> selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 删除公告信息 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java new file mode 100644 index 0000000..b8e7026 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java @@ -0,0 +1,68 @@ +package com.ruoyi.system.service; + +import java.util.List; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.query.SysOperLogQuery; +import com.ruoyi.system.vo.SysOperLogVO; + +/** + * 操作日志 服务层 + * + * @author ruoyi + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List<SysOperLog> selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(List<Long> operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); + + /** + * 操作日志分页列表 + * @param query + * @return + */ + PageInfo<SysOperLogVO> selectOperLogPageList(SysOperLogQuery query); + + /** + * 操作日志分页列表 + * @param query + * @return + */ +// List<SysOperLogVO> selectOperLogPageList(SysOperLogQuery query); + +// void getLogDetail(List<SysOperLogVO> sysOperLogVOS); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java new file mode 100644 index 0000000..84779bf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java @@ -0,0 +1,99 @@ +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<SysPost> selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List<SysPost> selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List<Long> selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostCodeUnique(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java new file mode 100644 index 0000000..063e8a5 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java @@ -0,0 +1,237 @@ +package com.ruoyi.system.service; + +import java.util.List; +import java.util.Set; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.dto.SysRoleDTO; +import com.ruoyi.system.query.SysRoleQuery; + +/** + * 角色业务层 + * + * @author ruoyi + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List<SysRole> selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List<SysRole> selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set<String> selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List<SysRole> selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List<Long> selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + public void checkRoleDataScope(Long roleId); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param dto 角色信息 + * @return 结果 + */ +// public int editRole(SysRoleDTO dto); + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRoleStatus(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(List<Long> roleIds); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + public int deleteAuthUsers(Long roleId, Long[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int insertAuthUsers(Long roleId, Long[] userIds); + + int selectCountByRoleName(String roleName); + +// void saveRole(SysRoleDTO dto); + + /** + * 判断是否存在该角色 + * + * @return + */ + Boolean isExit(Long id, String roleName); + + /** + * 角色列表 + * @param + * @return + */ +// List<SysRole> selectList(SysRoleQuery query); + + int selectCount(Integer status); + + void updateStatus(SysRole role); + + List<SysRole> selectListByDelFlag(Integer delFlag); + + /** + * + * @param userId + * @return + */ + SysRole selectRoleByUserId(Long userId); + + List<SysMenu> getMenuLevelList(List<Long> menusId); + + List<SysMenu> roleInfoFromUserId(Long userId); + + String selectByUserId(Long user_id); + + void saveRole(SysRoleDTO dto); + + PageInfo<SysRole> selectPageList(SysRoleQuery query); + + /** + * 修改保存角色信息 + * + * @param dto 角色信息 + * @return 结果 + */ + public int editRole(SysRoleDTO dto); + + List<SysRole> selectRoleByUserIds(List<String> roleIds); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java new file mode 100644 index 0000000..8eb5448 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.service; + +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author ruoyi + */ +public interface ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user); + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + public SysUserOnline loginUserToUserOnline(LoginUser user); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java new file mode 100644 index 0000000..4dd0046 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java @@ -0,0 +1,264 @@ +package com.ruoyi.system.service; + +import java.util.List; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.query.SysUserQuery; +import com.ruoyi.system.vo.SysUserVO; + +/** + * 用户 业务层 + * + * @author ruoyi + */ +public interface ISysUserService +{ + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List<SysUser> selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List<SysUser> selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List<SysUser> selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserRoleGroup(String userName); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserPostGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkUserNameUnique(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); + + /** + * 新增用户信息 + * + * @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 userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public boolean updateUserAvatar(String userName, String avatar); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(String userName, String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(List<Long> userIds); + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName); + + List<SysUser> selectList(); + + Integer selectCount(Integer status); + + /** + * 获取用户列表 + * @param query + * @return + */ +// List<SysUserVO> selectUserPageList(SysUserQuery query); + + /** + * 获取用户黑名单列表 + * @param + * @return + */ +// List<SysUserVO> selectBlackPageList(SysUserQuery query); + + List<SysUser> selectListByNamePhone(String name); + + +// UserInfoVo userInfo(Long userId); + + SysUser selectByPhone(String phonenumber); + /** + * 通过名字集合查询用户 + * @param names + * @return + */ + List<SysUser> selectUserByUserNameList(List<String> names); + +// UserInfoVo getUserInfoBy(String singleNum); + + Long getUserRole(Long userId); + + int updateUserIfBlack(List<Long> ids); + + /** + * 查询所有用户(包含删除的) + * @return + */ + List<SysUser> selectAllList(); + + /** + * 获取用户列表 + * @param query + * @return + */ + PageInfo<SysUserVO> pageList(SysUserQuery query); + + void updatePassword(Long id, String s); + + long selectIdByPhone(String phonenumber); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..93ccc97 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,233 @@ +package com.ruoyi.system.service.impl; + +import java.util.Collection; +import java.util.List; +import javax.annotation.PostConstruct; + +import com.ruoyi.system.mapper.SysConfigMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.annotation.DataSource; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.DataSourceType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 参数配置 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysConfigServiceImpl implements ISysConfigService +{ + @Autowired + private SysConfigMapper configMapper; + + @Autowired + private RedisCache redisCache; + + /** + * 项目启动时,初始化参数到缓存 + */ + @PostConstruct + public void init() + { + loadingConfigCache(); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + @DataSource(DataSourceType.MASTER) + 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(redisCache.getCacheObject(getCacheKey(configKey))); + if (StringUtils.isNotEmpty(configValue)) + { + return configValue; + } + SysConfig config = new SysConfig(); + config.setConfigKey(configKey); + SysConfig retConfig = configMapper.selectConfig(config); + if (StringUtils.isNotNull(retConfig)) + { + redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + @Override + public boolean selectCaptchaEnabled() + { + String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); + if (StringUtils.isEmpty(captchaEnabled)) + { + return true; + } + return Convert.toBool(captchaEnabled); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List<SysConfig> selectConfigList(SysConfig config) + { + return configMapper.selectConfigList(config); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int insertConfig(SysConfig config) + { + int row = configMapper.insertConfig(config); + if (row > 0) + { + redisCache.setCacheObject(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())) + { + redisCache.deleteObject(getCacheKey(temp.getConfigKey())); + } + + int row = configMapper.updateConfig(config); + if (row > 0) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + @Override + public void deleteConfigByIds(Long[] configIds) + { + 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); + redisCache.deleteObject(getCacheKey(config.getConfigKey())); + } + } + + /** + * 加载参数缓存数据 + */ + @Override + public void loadingConfigCache() + { + List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig()); +// for (SysConfig config : configsList) +// { +// redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); +// } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + Collection<String> keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); + redisCache.deleteObject(keys); + } + + /** + * 重置参数缓存数据 + */ + @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 key + * + * @param configKey 参数键 + * @return 缓存键key + */ + private String getCacheKey(String configKey) + { + return CacheConstants.SYS_CONFIG_KEY + configKey; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..d7449eb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,339 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.TreeSelect; +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.text.Convert; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.mapper.SysDeptMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.service.ISysDeptService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 部门管理 服务实现 + * + * @author ruoyi + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List<SysDept> selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + @Override + public List<TreeSelect> selectDeptTreeList(SysDept dept) + { + List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + return buildDeptTreeSelect(depts); + } + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + @Override + public List<SysDept> buildDeptTree(List<SysDept> depts) + { + List<SysDept> returnList = new ArrayList<SysDept>(); + List<Long> tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); + for (SysDept dept : depts) + { + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) + { + recursionFn(depts, dept); + returnList.add(dept); + } + } + if (returnList.isEmpty()) + { + returnList = depts; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts) + { + List<SysDept> deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List<Long> selectDeptListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 根据部门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 deptId 部门ID + * @return 结果 + */ + @Override + public boolean hasChildByDeptId(Long deptId) + { + int result = deptMapper.hasChildByDeptId(deptId); + return result > 0; + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) + { + int result = deptMapper.checkDeptExistUser(deptId); + return result > 0; + } + + /** + * 校验部门名称是否唯一 + * + * @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 (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } + } + + /** + * 新增保存部门信息 + * + * @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 + public int updateDept(SysDept dept) + { + SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); + SysDept oldDept = deptMapper.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<SysDept> children = deptMapper.selectChildrenDeptById(deptId); + for (SysDept child : children) + { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + } + if (children.size() > 0) + { + deptMapper.updateDeptChildren(children); + } + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public int deleteDeptById(Long deptId) + { + return deptMapper.deleteDeptById(deptId); + } + + /** + * 递归列表 + */ + private void recursionFn(List<SysDept> list, SysDept t) + { + // 得到子节点列表 + List<SysDept> childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List<SysDept> getChildList(List<SysDept> list, SysDept t) + { + List<SysDept> tlist = new ArrayList<SysDept>(); + Iterator<SysDept> it = list.iterator(); + while (it.hasNext()) + { + SysDept n = (SysDept) it.next(); + if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List<SysDept> list, SysDept t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..1dee12a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,112 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; + +import com.ruoyi.system.mapper.SysDictDataMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List<SysDictData> 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 dictCodes 需要删除的字典数据ID + */ + @Override + public void deleteDictDataByIds(Long[] dictCodes) + { + for (Long dictCode : dictCodes) + { + SysDictData data = selectDictDataById(dictCode); + dictDataMapper.deleteDictDataById(dictCode); + List<SysDictData> 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<SysDictData> 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<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..37b0559 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,224 @@ +package com.ruoyi.system.service.impl; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; + +import com.ruoyi.system.mapper.SysDictDataMapper; +import com.ruoyi.system.mapper.SysDictTypeMapper; +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.entity.SysDictData; +import com.ruoyi.common.core.domain.entity.SysDictType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +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<SysDictType> selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List<SysDictType> selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List<SysDictData> selectDictDataByType(String dictType) + { + List<SysDictData> 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 dictIds 需要删除的字典ID + */ + @Override + public void deleteDictTypeByIds(Long[] dictIds) + { + 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<String, List<SysDictData>> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry<String, List<SysDictData>> 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<SysDictData> 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; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..b14dc26 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,66 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; + +import com.ruoyi.system.mapper.SysLogininforMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.domain.SysLogininfor; +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<SysLogininfor> selectLogininforList(SysLogininfor logininfor) + { + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) + { + return logininforMapper.deleteLogininforByIds(infoIds); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..7d46e40 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,547 @@ +package com.ruoyi.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.ruoyi.system.mapper.SysMenuMapper; +import com.ruoyi.system.mapper.SysRoleMapper; +import com.ruoyi.system.mapper.SysRoleMenuMapper; +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.domain.TreeSelect; +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.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.vo.MetaVo; +import com.ruoyi.system.domain.vo.RouterVo; +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 SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Override + public List<SysMenu> selectMenuList(Long userId) + { + return selectMenuList(new SysMenu(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List<SysMenu> selectMenuList(SysMenu menu, Long userId) + { + List<SysMenu> menuList = null; + // 管理员显示所有菜单信息 + if (SysUser.isAdmin(userId)) + { + menuList = menuMapper.selectMenuList(menu); + } + else + { + menu.getParams().put("userId", userId); + menuList = menuMapper.selectMenuListByUserId(menu); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set<String> selectMenuPermsByUserId(Long userId) + { + List<String> perms = menuMapper.selectMenuPermsByUserId(userId); + Set<String> 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<String> selectMenuPermsByRoleId(Long roleId) + { + List<String> perms = menuMapper.selectMenuPermsByRoleId(roleId); + Set<String> permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户名称 + * @return 菜单列表 + */ + @Override + public List<SysMenu> selectMenuTreeByUserId(Long userId) + { + List<SysMenu> menus = null; + if (SecurityUtils.isAdmin(userId)) + { + menus = menuMapper.selectMenuTreeAll(); + } + else + { + menus = menuMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, 0); + } + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List<Long> selectMenuListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List<RouterVo> buildMenus(List<SysMenu> menus) + { + List<RouterVo> routers = new LinkedList<RouterVo>(); + for (SysMenu menu : menus) + { + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setQuery(menu.getQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + List<SysMenu> cMenus = menu.getChildren(); + if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) + { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } + else if (isMenuFrame(menu)) + { + router.setMeta(null); + List<RouterVo> childrenList = new ArrayList<RouterVo>(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(StringUtils.capitalize(menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } + else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) + { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List<RouterVo> childrenList = new ArrayList<RouterVo>(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(StringUtils.capitalize(routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + @Override + public List<SysMenu> buildMenuTree(List<SysMenu> menus) + { + List<SysMenu> returnList = new ArrayList<SysMenu>(); + List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext();) + { + SysMenu menu = (SysMenu) iterator.next(); + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(menu.getParentId())) + { + recursionFn(menus, menu); + returnList.add(menu); + } + } + if (returnList.isEmpty()) + { + returnList = menus; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + @Override + public List<TreeSelect> buildMenuTreeSelect(List<SysMenu> menus) + { + List<SysMenu> menuTrees = buildMenuTree(menus); + return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenu selectMenuById(Long menuId) + { + return menuMapper.selectMenuById(menuId); + } + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean hasChildByMenuId(Long menuId) + { + int result = menuMapper.hasChildByMenuId(menuId); + return result > 0; + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) + { + int result = roleMenuMapper.checkMenuExistRole(menuId); + return result > 0; + } + + /** + * 新增保存菜单信息 + * + * @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 menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) + { + return menuMapper.deleteMenuById(menuId); + } + + /** + * 校验菜单名称是否唯一 + * + * @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; + } + + @Override + public List<SysMenu> selectListByRoleId(Long roleId) { + return menuMapper.selectListByRoleId(roleId); + } + + @Override + public List<SysMenu> getAllInIds(List<Long> menusId) { + return menuMapper.getAllInIds(menusId); + } + + @Override + public List<SysMenu> selectList() { + return menuMapper.selectList(); + } + + /** + * 获取路由名称 + * + * @param menu 菜单信息 + * @return 路由名称 + */ + public String getRouteName(SysMenu menu) + { + String routerName = StringUtils.capitalize(menu.getPath()); + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) + { + routerName = StringUtils.EMPTY; + } + return routerName; + } + + /** + * 获取路由地址 + * + * @param menu 菜单信息 + * @return 路由地址 + */ + public String getRouterPath(SysMenu menu) + { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) + { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) + { + routerPath = "/" + menu.getPath(); + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame(menu)) + { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenu menu) + { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) + { + component = menu.getComponent(); + } + else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) + { + component = UserConstants.INNER_LINK; + } + else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) + { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isMenuFrame(SysMenu menu) + { + return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isInnerLink(SysMenu menu) + { + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); + } + + /** + * 是否为parent_view组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isParentView(SysMenu menu) + { + return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) + { + List<SysMenu> returnList = new ArrayList<SysMenu>(); + for (Iterator<SysMenu> 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<SysMenu> list, SysMenu t) + { + // 得到子节点列表 + List<SysMenu> childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t) + { + List<SysMenu> tlist = new ArrayList<SysMenu>(); + Iterator<SysMenu> 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<SysMenu> list, SysMenu t) + { + return getChildList(list, t).size() > 0; + } + + /** + * 内链域名特殊字符替换 + * + * @return 替换后的内链域名 + */ + public String innerLinkReplaceEach(String path) + { + return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, "." }, + new String[] { "", "", "", "/" }); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..d652399 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,93 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; + +import com.ruoyi.system.mapper.SysNoticeMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 服务层实现 + * + * @author ruoyi + */ +@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<SysNotice> 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 noticeId 公告ID + * @return 结果 + */ + @Override + public int deleteNoticeById(Long noticeId) + { + return noticeMapper.deleteNoticeById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) + { + return noticeMapper.deleteNoticeByIds(noticeIds); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..282019c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,260 @@ +package com.ruoyi.system.service.impl; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.mapper.SysOperLogMapper; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.query.SysOperLogQuery; +import com.ruoyi.system.service.ISysOperLogService; +import com.ruoyi.system.vo.SysOperLogVO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 操作日志 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysOperLogServiceImpl implements ISysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + @Autowired + private SysUserMapper userMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) + { + operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List<SysOperLog> selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(List<Long> operIds) + { + return operLogMapper.deleteOperLogByIds(operIds); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } + + @Override + public PageInfo<SysOperLogVO> selectOperLogPageList(SysOperLogQuery query) { + PageInfo<SysOperLogVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); + List<SysOperLogVO> list = operLogMapper.selectOperLogPageList(query,pageInfo); + pageInfo.setRecords(list); + return pageInfo; + } + +// @Override +// public void getLogDetail(List<SysOperLogVO> sysOperLogVOS) { +// for (SysOperLogVO sysOperLogVO : sysOperLogVOS) { +// if(sysOperLogVO.getTitle().contains("单位")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String companyName = jsonObject.getString("companyName"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+companyName+">单位"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+companyName+">单位"); +// break; +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("部门")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String deptName = jsonObject.getString("deptName"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+deptName+">部门"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+deptName+">部门"); +// break; +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("角色信息")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String roleName = jsonObject.getString("roleName"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+roleName+">角色"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+roleName+">角色"); +// break; +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("用户信息")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String nickName = jsonObject.getString("nickName"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+nickName+">用户"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+nickName+">用户"); +// break; +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("问题反馈")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String problemFeedback = jsonObject.getString("problemFeedback"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+problemFeedback+">问题反馈"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+problemFeedback+">问题反馈"); +// break; +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("现场作业")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String engineeringName = jsonObject.getString("engineeringName"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+engineeringName+">现场作业"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+engineeringName+">现场作业"); +// break; +// case 10: +// // 现场作业修改时间 +// String startTime = jsonObject.getString("startTime"); +// String endTime = jsonObject.getString("endTime"); +// sysOperLogVO.setOperDetail("编辑了<"+engineeringName+">现场作业时间为:"+startTime+"~"+endTime); +// case 11: +// // 现场作业修改经理 +// Long projectManager = jsonObject.getLong("projectManager"); +// SysUser manager = userMapper.selectUserById(projectManager); +// sysOperLogVO.setOperDetail("编辑了<"+engineeringName+">现场作业经理为:"+manager.getNickName()); +// case 12: +// // 现场作业修改负责人 +// Long workHeader = jsonObject.getLong("workHeader"); +// SysUser header = userMapper.selectUserById(workHeader); +// sysOperLogVO.setOperDetail("编辑了<"+engineeringName+">现场作业负责人为:"+header.getNickName()); +// case 13: +// // 现场作业修改人员新增 +// JSONArray addUserIds = jsonObject.getJSONArray("addUserIds"); +// JSONArray borrowUserIds = jsonObject.getJSONArray("borrowUserIds"); +// List<Long> userIds = new ArrayList<>(); +// for (Object addUserId : addUserIds) { +// userIds.add(Long.valueOf((String.valueOf(addUserId)))); +// } +// for (Object borrowUserId : borrowUserIds) { +// userIds.add(Long.valueOf((String.valueOf(borrowUserId)))); +// } +// List<SysUser> userList = userMapper.selectUserByIds(userIds); +// List<String> nickNameList = userList.stream().map(SysUser::getNickName).collect(Collectors.toList()); +// String nickNames = nickNameList.stream().map(Object::toString).collect(Collectors.joining("、")); +// sysOperLogVO.setOperDetail("编辑了<"+engineeringName+">现场作业人员新增:"+nickNames); +// case 14: +// // 现场作业修改人员减少 +// Long reduceUserId = jsonObject.getLong("reduceUserId"); +// SysUser reduceUser = userMapper.selectUserById(reduceUserId); +// sysOperLogVO.setOperDetail("编辑了<"+engineeringName+">现场作业人员减少:"+reduceUser.getNickName()); +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("培训类别")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String content = jsonObject.getString("content"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+content+">培训类别"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+content+">培训类别"); +// break; +// } +// } +// } +// if(sysOperLogVO.getTitle().contains("培训信息")){ +// JSONObject jsonObject = JSONObject.parseObject(sysOperLogVO.getOperParam()); +// if(Objects.nonNull(jsonObject)){ +// String trainName = jsonObject.getString("trainName"); +// switch (sysOperLogVO.getBusinessType()){ +// case 1: +// // 新增 +// sysOperLogVO.setOperDetail("新增了<"+trainName+">培训信息"); +// break; +// case 2: +// // 编辑 +// sysOperLogVO.setOperDetail("编辑了<"+trainName+">培训信息"); +// break; +// } +// } +// } +// } +// } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..303b504 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,179 @@ +package com.ruoyi.system.service.impl; + +import java.util.List; + +import com.ruoyi.system.mapper.SysPostMapper; +import com.ruoyi.system.mapper.SysUserPostMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysPost; +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<SysPost> selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List<SysPost> selectPostAll() + { + return postMapper.selectPostAll(); + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPost selectPostById(Long postId) + { + return postMapper.selectPostById(postId); + } + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + @Override + public List<Long> selectPostListByUserId(Long userId) + { + return postMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @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; + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) + { + return postMapper.deletePostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) + { + 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); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..a310cf7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,614 @@ +package com.ruoyi.system.service.impl; + +import java.util.*; +import java.util.stream.Collectors; + +import com.ruoyi.common.basic.PageInfo; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.system.dto.SysRoleDTO; +import com.ruoyi.system.mapper.*; +import com.ruoyi.system.query.SysRoleQuery; +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.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +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.service.ISysRoleService; +import org.springframework.util.CollectionUtils; + +/** + * 角色 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysRoleServiceImpl implements ISysRoleService +{ + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleDeptMapper roleDeptMapper; + @Autowired + private SysMenuMapper menuMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + public List<SysRole> selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List<SysRole> selectRolesByUserId(Long userId) + { + List<SysRole> userRoles = roleMapper.selectRolePermissionByUserId(userId); + List<SysRole> roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set<String> selectRolePermissionByUserId(Long userId) + { + List<SysRole> perms = roleMapper.selectRolePermissionByUserId(userId); + Set<String> permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List<SysRole> selectRoleAll() + { + return this.selectRoleList(new SysRole()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List<Long> selectRoleListByUserId(Long userId) + { + return roleMapper.selectRoleListByUserId(userId); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRole selectRoleById(Long roleId) + { + return roleMapper.selectRoleById(roleId); + } + + /** + * 校验角色名称是否唯一 + * + * @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 roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List<SysRole> 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 + @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 dto 角色信息 + * @return 结果 + */ +// @Override +// @Transactional +// public int editRole(SysRoleDTO dto) +// { +// // 修改角色信息 +// SysRole sysRole = new SysRole(); +// sysRole.setRoleName(dto.getRoleName()); +// sysRole.setPostType(dto.getPostType()); +// roleMapper.updateRole(sysRole); +// // 删除角色与菜单关联 +// roleMenuMapper.deleteRoleMenuByRoleId(dto.getRoleId()); +// +// // 添加角色权限中间表 +// List<Long> menuIds = dto.getMenuIds(); +// List<SysRoleMenu> sysRoleMenus = new ArrayList<>(); +// for (Long menuId : menuIds) { +// SysRoleMenu sysRoleMenu = new SysRoleMenu(); +// sysRoleMenu.setRoleId(dto.getRoleId()); +// sysRoleMenu.setMenuId(menuId); +// sysRoleMenus.add(sysRoleMenu); +// } +// +// return roleMenuMapper.batchRoleMenu(sysRoleMenus); +// } + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int updateRoleStatus(SysRole role) + { + return roleMapper.updateRole(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<SysRoleMenu> list = new ArrayList<SysRoleMenu>(); + 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<SysRoleDept> list = new ArrayList<SysRoleDept>(); + 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; + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleById(Long roleId) + { + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId); + } + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleByIds(List<Long> roleIds) + { + for (Long roleId : roleIds) + { + SysRole role = selectRoleById(roleId); + if (countUserRoleByRoleId(roleId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + return roleMapper.deleteRoleByIds(roleIds); + } + + /** + * 取消授权用户角色 + * + * @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, Long[] userIds) + { + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, Long[] userIds) + { + // 新增用户与角色管理 + List<SysUserRole> list = new ArrayList<SysUserRole>(); + for (Long userId : userIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } + + @Override + public int selectCountByRoleName(String roleName) { + return roleMapper.selectCountByRoleName(roleName); + } + +// @Override +// public void saveRole(SysRoleDTO dto) { +// +// // 添加角色 +// SysRole sysRole = new SysRole(); +// sysRole.setRoleName(dto.getRoleName()); +// sysRole.setPostType(dto.getPostType()); +// roleMapper.insertRole(sysRole); +// +// // 添加角色权限中间表 +// List<Long> menuIds = dto.getMenuIds(); +// List<SysRoleMenu> sysRoleMenus = new ArrayList<>(); +// for (Long menuId : menuIds) { +// SysRoleMenu sysRoleMenu = new SysRoleMenu(); +// sysRoleMenu.setRoleId(sysRole.getRoleId()); +// sysRoleMenu.setMenuId(menuId); +// sysRoleMenus.add(sysRoleMenu); +// } +// roleMenuMapper.batchRoleMenu(sysRoleMenus); +// } + + @Override + public Boolean isExit(Long id, String roleName) { + int count = this.selectCountByRoleName(roleName); + if (StringUtils.isNotNull(id)) { + // 修改 + SysRole sysRole = roleMapper.selectRoleById(id); + return Objects.nonNull(sysRole) && !sysRole.getRoleName().equals(roleName) && count > 0; + } else { + // 新增 + return count > 0; + } + } + +// @Override +// public List<SysRole> selectList(SysRoleQuery query) { +// return roleMapper.selectList(query); +// } + + @Override + public int selectCount(Integer status) { + return roleMapper.selectCount(status); + } + + @Override + public void updateStatus(SysRole role) { + roleMapper.updateStatus(role); + } + + @Override + public List<SysRole> selectListByDelFlag(Integer delFlag) { + return roleMapper.selectListByDelFlag(delFlag); + } + + @Override + public SysRole selectRoleByUserId(Long userId) { + return roleMapper.selectRoleByUserId(userId); + } + + @Override + public List<SysMenu> getMenuLevelList(List<Long> menusId) { + //获取当前的权限菜单 + List<SysMenu> all = menuMapper.getAllInIds(menusId); + // 第三级 + List<SysMenu> s3 = all.stream().filter(e -> e.getMenuType().equals("F")).collect(Collectors.toList()); + // 第二级 + List<SysMenu> s2 = all.stream().filter(e -> e.getMenuType().equals("C")).collect(Collectors.toList()); + // 第一级 + List<SysMenu> s1 = all.stream().filter(e -> e.getMenuType().equals("M")).collect(Collectors.toList()); + + for (SysMenu menu : s2) { + List<SysMenu> collect = s3.stream().filter(e -> e.getParentId().equals(menu.getMenuId())).collect(Collectors.toList()); + menu.setChildren(collect); + } + for (SysMenu menu : s1) { + List<SysMenu> collect = s2.stream().filter(e -> e.getParentId().equals(menu.getMenuId())).collect(Collectors.toList()); + menu.setChildren(collect); + } + return s1; + } + + @Override + public List<SysMenu> roleInfoFromUserId(Long userId) { + SysRole sysRole = roleMapper.selectRoleByUserId(userId); + // 获取当前角色的菜单列表 + List<SysMenu> menus = menuMapper.selectListByRoleId(sysRole.getRoleId()); + if(menus.size()==0){ + return new ArrayList<>(); + } + List<Long> menusId = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + // 获取当前的权限菜单(有层级) + return this.getMenuLevelList(menusId); + } + + @Override + public String selectByUserId(Long user_id) { + return roleMapper.selectByUserId(user_id); + } + + @Override + public void saveRole(SysRoleDTO dto) { + // 添加角色 + SysRole sysRole = new SysRole(); + sysRole.setRoleName(dto.getRoleName()); + sysRole.setPostType(dto.getPostType()); + sysRole.setRemark(dto.getRemark()); + roleMapper.insertRole(sysRole); + + // 添加角色权限中间表 + List<Long> menuIds = dto.getMenuIds(); + List<SysRoleMenu> sysRoleMenus = new ArrayList<>(); + for (Long menuId : menuIds) { + SysRoleMenu sysRoleMenu = new SysRoleMenu(); + sysRoleMenu.setRoleId(sysRole.getRoleId()); + sysRoleMenu.setMenuId(menuId); + sysRoleMenus.add(sysRoleMenu); + } + roleMenuMapper.batchRoleMenu(sysRoleMenus); + } + + @Override + public PageInfo<SysRole> selectPageList(SysRoleQuery query) { + PageInfo<SysRole> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); + List<SysRole> list = roleMapper.selectPageList(query,pageInfo); + pageInfo.setRecords(list); + return pageInfo; + } + + @Override + public int editRole(SysRoleDTO dto) { + // 修改角色信息 + SysRole sysRole = new SysRole(); + sysRole.setRoleId(dto.getRoleId()); + sysRole.setRoleName(dto.getRoleName()); + sysRole.setPostType(dto.getPostType()); + sysRole.setRemark(dto.getRemark()); + roleMapper.updateRole(sysRole); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(dto.getRoleId()); + + // 添加角色权限中间表 + List<Long> menuIds = dto.getMenuIds(); + List<SysRoleMenu> sysRoleMenus = new ArrayList<>(); + for (Long menuId : menuIds) { + SysRoleMenu sysRoleMenu = new SysRoleMenu(); + sysRoleMenu.setRoleId(dto.getRoleId()); + sysRoleMenu.setMenuId(menuId); + sysRoleMenus.add(sysRoleMenu); + } + + return roleMenuMapper.batchRoleMenu(sysRoleMenus); + } + + @Override + public List<SysRole> selectRoleByUserIds(List<String> roleIds) { + return roleMapper.selectRoleByUserIds(roleIds); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 0000000..f80a877 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.system.service.impl; + +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user) + { + if (StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + @Override + public SysUserOnline loginUserToUserOnline(LoginUser user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) + { + return null; + } + SysUserOnline sysUserOnline = new SysUserOnline(); + sysUserOnline.setTokenId(user.getToken()); + sysUserOnline.setUserName(user.getUsername()); + sysUserOnline.setIpaddr(user.getIpaddr()); + sysUserOnline.setLoginLocation(user.getLoginLocation()); + sysUserOnline.setBrowser(user.getBrowser()); + sysUserOnline.setOs(user.getOs()); + sysUserOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) + { + sysUserOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return sysUserOnline; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..9b92f26 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,698 @@ +package com.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.basic.PageInfo; +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.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanValidators; +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.*; +import com.ruoyi.system.query.SysUserQuery; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; +import com.ruoyi.system.vo.SysUserVO; +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 javax.validation.Validator; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 用户 业务层处理 + * + * @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 SysUserRoleMapper userRoleMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + @Autowired + private ISysConfigService configService; + + @Autowired + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List<SysUser> selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List<SysUser> selectAllocatedList(SysUser user) + { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List<SysUser> selectUnallocatedList(SysUser user) + { + return userMapper.selectUnallocatedList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByUserName(String userName) + { + return userMapper.selectUserByUserName(userName); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) + { + return userMapper.selectUserById(userId); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) + { + List<SysRole> list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserPostGroup(String userName) + { + List<SysPost> list = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkUserNameUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkUserNameUnique(user.getUserName()); + 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 (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysUser user = new SysUser(); + user.setUserId(userId); + List<SysUser> users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) + { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int insertUser(SysUser user) + { + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserDept(user); + // 新增用户与角色管理 + insertUserRoleId(user); + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) + { + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) + { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRoleId(user); + // 删除用户与部门关联 +// deptToUserMapper.deleteUserDeptByUserId(userId); + // 新增用户与部门管理 + insertUserDept(user); + return userMapper.updateUser(user); + } + + /** + * 用户授权角色 + * + * @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 updateUserStatus(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(String userName, String avatar) + { + return userMapper.updateUserAvatar(userName, avatar) > 0; + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(String userName, String password) + { + return userMapper.resetUserPwd(userName, password); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) + { + this.insertUserRole(user.getUserId(), user.getRoleIds()); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRoleId(SysUser user) + { + this.insertUserRoleId(user.getUserId(), user.getRoleId()); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserDept(SysUser user) + { + this.insertUserDept(user.getUserId(), user.getDeptIds()); + } + + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) + { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotEmpty(posts)) + { + // 新增用户与岗位管理 + List<SysUserPost> list = new ArrayList<SysUserPost>(posts.length); + for (Long postId : posts) + { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + userPostMapper.batchUserPost(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) + { + if (StringUtils.isNotEmpty(roleIds)) + { + // 新增用户与角色管理 + List<SysUserRole> list = new ArrayList<SysUserRole>(roleIds.length); + for (Long roleId : roleIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + userRoleMapper.batchUserRole(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleId 角色组 + */ + public void insertUserRoleId(Long userId, Long roleId) + { + if (Objects.nonNull(userId) && Objects.nonNull(roleId)){ + // 新增用户与角色管理 + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + userRoleMapper.insertUserRole(ur); + } + } + /** + * 新增用户部门信息 + * + * @param userId 用户ID + * @param deptIds 部门id集合 + */ + public void insertUserDept(Long userId, List<String> deptIds) + { + if (Objects.nonNull(userId) && !CollectionUtils.isEmpty(deptIds)){ +// List<TDeptToUser> deptToUserList = new ArrayList<>(); +// for (String deptId : deptIds) { +// // 新增用户与角色管理 +// TDeptToUser deptToUser = new TDeptToUser(); +// deptToUser.setUserId(userId); +// deptToUser.setDeptId(deptId); +// deptToUserList.add(deptToUser); +// } +// deptToUserService.saveBatch(deptToUserList); + } + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserById(Long userId) + { + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(List<Long> userIds) + { + for (Long userId : userIds) + { + checkUserAllowed(new SysUser(userId)); +// checkUserDataScope(userId); + } + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 +// userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + @Override + public String importUser(List<SysUser> 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(); + String password = configService.selectConfigByKey("sys.user.initPassword"); + for (SysUser user : userList) + { + try + { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByUserName(user.getUserName()); + if (StringUtils.isNull(u)) + { + BeanValidators.validateWithException(validator, user); + user.setPassword(SecurityUtils.encryptPassword(password)); + user.setCreateBy(operName); + userMapper.insertUser(user); + successNum++; + successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功"); + } + else if (isUpdateSupport) + { + BeanValidators.validateWithException(validator, user); + checkUserAllowed(u); + checkUserDataScope(u.getUserId()); + user.setUserId(u.getUserId()); + user.setUpdateBy(operName); + userMapper.updateUser(user); + successNum++; + successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; + 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(); + } + + @Override + public List<SysUser> selectList() { + return userMapper.selectList(); + } + + @Override + public Integer selectCount(Integer status) { + return userMapper.selectCount(status); + } + +// @Override +// public List<SysUserVO> selectUserPageList(SysUserQuery query) { +// return userMapper.selectUserPageList(query); +// } + +// @Override +// public List<SysUserVO> selectBlackPageList(SysUserQuery query) { +// return userMapper.selectBlackPageList(query); +// } + + @Override + public List<SysUser> selectListByNamePhone(String name) { + return userMapper.selectListByNamePhone(name); + } + + @Override + public List<SysUser> selectUserByUserNameList(List<String> names) { + return userMapper.selectUserByUserNameList(names); + } + +// @Override +// public UserInfoVo getUserInfoBy(String singleNum) { +// return userMapper.getUserInfoBy(singleNum); +// } + + @Override + public Long getUserRole(Long userId) { + return userMapper.getUserRole(userId); + } + + @Override + public int updateUserIfBlack(List<Long> ids) { + return userMapper.updateUserIfBlack(ids); + } + + @Override + public List<SysUser> selectAllList() { + return userMapper.selectAllList(); + } + + @Override + public PageInfo<SysUserVO> pageList(SysUserQuery query) { + PageInfo<SysUserVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); + String businessDeptId = SecurityUtils.getBusinessDeptId(); + query.setBusinessDeptId(businessDeptId); + List<SysUserVO> list = userMapper.pageList(query,pageInfo); + if(CollectionUtils.isEmpty(list)){ + return pageInfo; + } + List<Long> userIds = list.stream().map(SysUserVO::getUserId).collect(Collectors.toList()); + // 查询所有部门 +// List<TDept> depts = deptMapper.selectList(Wrappers.lambdaQuery(TDept.class)); +// List<TDeptToUser> tDeptToUsers = deptToUserService.list(Wrappers.lambdaQuery(TDeptToUser.class) +// .in(TDeptToUser::getUserId, userIds)); +// for (SysUserVO sysUserVO : list) { +// tDeptToUsers.stream().filter(tDeptToUser -> tDeptToUser.getUserId().equals(sysUserVO.getUserId())).forEach(tDeptToUser -> { +// sysUserVO.setDeptList(depts.stream().filter(tDept -> tDept.getId().equals(tDeptToUser.getDeptId())).map(TDept::getDeptName).collect(Collectors.toList())); +// sysUserVO.setDeptIds(depts.stream().map(TDept::getId).filter(id -> id.equals(tDeptToUser.getDeptId())).collect(Collectors.toList())); +// }); +// } + pageInfo.setRecords(list); + return pageInfo; + } + + @Override + public void updatePassword(Long id, String s) { + userMapper.updatePassword(id,s); + } + + @Override + public long selectIdByPhone(String phonenumber) { + return userMapper.selectIdByPhone(phonenumber); + } + +// @Override +// public UserInfoVo userInfo(Long userId) { +// return userMapper.userInfo(userId); +// } + + @Override + public SysUser selectByPhone(String phonenumber) { + return userMapper.selectByPhone(phonenumber); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/AccessTokenRespBody.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/AccessTokenRespBody.java new file mode 100644 index 0000000..db2d1b4 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/AccessTokenRespBody.java @@ -0,0 +1,28 @@ +package com.ruoyi.system.utils.wx.body.resp; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * AccessToken 全局唯一 + * + * @author xiaochen + */ +@Data +public class AccessTokenRespBody extends RespBody implements Serializable { + + /** + * 获取到的凭证 + */ + @JsonProperty("access_token") + private String accessToken; + /** + * 凭证有效时间,单位:秒 + */ + @JsonProperty("expires_in") + private int expiresIn; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/Code2SessionRespBody.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/Code2SessionRespBody.java new file mode 100644 index 0000000..d741edb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/Code2SessionRespBody.java @@ -0,0 +1,29 @@ +package com.ruoyi.system.utils.wx.body.resp; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author xiaochen + * @ClassName Code2SessionRespBody + * @Description + * @date 2021-07-28 12:35 + */ +@Data +public class Code2SessionRespBody extends RespBody { + /** + * 用户唯一标识 + */ + @JsonProperty("openid") + private String openid; + /** + * 会话密钥 + */ + @JsonProperty("session_key") + private String sessionKey; + /** + * 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。 + */ + @JsonProperty("unionid") + private String unionid; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/RespBody.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/RespBody.java new file mode 100644 index 0000000..ee60ab3 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/RespBody.java @@ -0,0 +1,19 @@ +package com.ruoyi.system.utils.wx.body.resp; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author xiaochen + * @ClassName RespBody + * @Description + * @date 2021-07-28 11:44 + */ +@Data +public class RespBody { + @JsonProperty("errcode") + private Integer errorCode; + + @JsonProperty("errmsg") + private String errorMsg; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resq/Code2SessionResqBody.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resq/Code2SessionResqBody.java new file mode 100644 index 0000000..424e376 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resq/Code2SessionResqBody.java @@ -0,0 +1,21 @@ +package com.ruoyi.system.utils.wx.body.resq; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author xiaochen + * @ClassName Code2SessionResqBody + * @Description + * @date 2021-07-28 11:47 + */ +@Data +public class Code2SessionResqBody { + @JsonProperty("js_code") + private String jsCode; + + public Code2SessionResqBody build(String jsCode) { + this.jsCode = jsCode; + return this; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java new file mode 100644 index 0000000..fdeb5ab --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java @@ -0,0 +1,79 @@ +package com.ruoyi.system.utils.wx.model; + +import lombok.ToString; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author xiaochen + * @ClassName WeixinProperties + * @Description + * @date 2024-08-14 13:55 + */ +@ToString +@Component +@ConfigurationProperties(prefix = "wx.conf") +public class WeixinProperties { + /** + * 默认开启 + */ + private boolean enabled = true; + /** + * 获取 App ID + * + * @return App ID + */ + private String appId; + /** + * 获取 Mch ID + * + * @return Mch ID + */ + private String mchId; + + /** + * 获取 secret ID + * + * @return secret ID + */ + private String secretId; + + public String getSecretId() { + return secretId; + } + + public void setSecretId(String secretId) { + this.secretId = secretId; + } + + /** + * HTTP(S) 连接超时时间,单位毫秒 + * + */ + public int getHttpConnectTimeoutMs() { + return 6 * 1000; + } + + /** + * HTTP(S) 读数据超时时间,单位毫秒 + */ + public int getHttpReadTimeoutMs() { + return 8 * 1000; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletPhoneEncrypteData.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletPhoneEncrypteData.java new file mode 100644 index 0000000..c7f5257 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletPhoneEncrypteData.java @@ -0,0 +1,19 @@ +package com.ruoyi.system.utils.wx.pojo; + +import lombok.Data; + +/** + * @author xiaochen + * @ClassName AppletUserDecodeData + * @Description + * @date 2021-08-13 17:46 + * 小程序加密数据体 + * + */ +@Data +public class AppletPhoneEncrypteData { + private String encryptedData; + private String openid; + private String unionid; + private String iv; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserDecodeData.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserDecodeData.java new file mode 100644 index 0000000..a3573ad --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserDecodeData.java @@ -0,0 +1,52 @@ +package com.ruoyi.system.utils.wx.pojo; + +import lombok.Data; + +/** + * @author xiaochen + * @ClassName AppletUserDecodeData + * @Description + * 用户主体信息部分 + * { + * "openId": "OPENID", + * "nickName": "NICKNAME", + * "gender": GENDER, + * "city": "CITY", + * "province": "PROVINCE", + * "country": "COUNTRY", + * "avatarUrl": "AVATARURL", + * "unionId": "UNIONID", + * "watermark": + * { + * "appid":"APPID", + * "timestamp":TIMESTAMP + * } + * } + * 电话部分 + * { + * "phoneNumber": "13580006666", + * "purePhoneNumber": "13580006666", + * "countryCode": "86", + * "watermark": + * { + * "appid":"APPID", + * "timestamp": TIMESTAMP + * } + * } + * + */ +@Data +public class AppletUserDecodeData { + private String openId; + private String unionId; + private String nickName; + private int gender; + private String city; + private String province; + private String country; + private String avatarUrl; + private Watermark watermark; + private String phoneNumber; + private String purePhoneNumber; + private String countryCode; +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java new file mode 100644 index 0000000..16d0057 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java @@ -0,0 +1,20 @@ +package com.ruoyi.system.utils.wx.pojo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author xiaochen + * @ClassName AppletUserDecodeData + * @Description + * 小程序加密数据体 + * + */ +@Data +public class AppletUserEncrypteData extends AppletPhoneEncrypteData { + private String rawData; + private String signature; + private String code; + @ApiModelProperty(value = "邀请用户id") + private Long inviteUserId; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/Watermark.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/Watermark.java new file mode 100644 index 0000000..16f4f7a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/Watermark.java @@ -0,0 +1,9 @@ +package com.ruoyi.system.utils.wx.pojo; + +import lombok.Data; + +@Data +public class Watermark { + private String appid; + private String timestamp; +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/SHA1.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/SHA1.java new file mode 100644 index 0000000..2b74822 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/SHA1.java @@ -0,0 +1,36 @@ +package com.ruoyi.system.utils.wx.tools; + +import java.security.MessageDigest; + +public class SHA1 { + + + /** + * 用SHA1算法生成安全签名 + * + * @param str + * @return + * @throws WxException + */ + public static String getSHA1(String str) throws WxException { + try { + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + StringBuffer hexstr = new StringBuffer(); + String shaHex; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + throw new WxException(WxException.ComputeSignatureError); + } + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WebUtils.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WebUtils.java new file mode 100644 index 0000000..c2e15e1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WebUtils.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.utils.wx.tools; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * @Author xiaochen + * @Date 2019/08/26 10:28 AM + * @Description + */ +public final class WebUtils { + + private WebUtils() { + } + + /** + * 当前请求 + */ + public static HttpServletRequest request() { + return contextHolder() == null ? null : contextHolder().getRequest(); + } + + /** + * 当前响应 + */ + public static HttpServletResponse response() { + return contextHolder() == null ? null : contextHolder().getResponse(); + } + + /** + * 当前session + */ + public static HttpSession session() { + return request() == null ? null : request().getSession(); + } + + /** + * 当前ServletRequest + */ + public static ServletRequestAttributes contextHolder() { + return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java new file mode 100644 index 0000000..2298a44 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java @@ -0,0 +1,123 @@ +package com.ruoyi.system.utils.wx.tools; + + +import com.ruoyi.common.redis.service.RedisService; +import com.ruoyi.system.utils.wx.body.resp.AccessTokenRespBody; +import com.ruoyi.system.utils.wx.body.resp.Code2SessionRespBody; +import com.ruoyi.system.utils.wx.body.resq.Code2SessionResqBody; +import com.ruoyi.system.utils.wx.model.WeixinProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; + +/** + * @author xiaochen + * @ClassName WxAppletTools + * @Description + * @date 2024-8-04 13:55 + */ +@Slf4j +public class WxAppletTools { + private final static String ACCESSTOKEN_CACHE_KEY = "accessToken"; + /** + * 请求参数 + * 属性 类型 默认值 必填 说明 + * appid string 是 小程序 appId + * secret string 是 小程序 appSecret + * js_code string 是 登录时获取的 code + * grant_type string 是 授权类型,此处只需填写 authorization_cod + * <p> + * 返回值: + * <p> + * 属性 类型 说明 + * openid string 用户唯一标识 + * session_key string 会话密钥 + * unionid string 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。 + * errcode number 错误码 + * errmsg string 错误信息 + */ + private static final String JSCODE_2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code"; + /** + * 请求参数 + * 属性 类型 默认值 必填 说明 + * grant_type string 是 填写 client_credential + * appid string 是 小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态) + * secret string 是 小程序唯一凭证密钥,即 AppSecret,获取方式同 appid + * 返回值 + * Object + * 返回的 JSON 数据包 + * <p> + * 属性 类型 说明 + * access_token string 获取到的凭证 + * expires_in number 凭证有效时间,单位:秒。目前是7200秒之内的值。 + * errcode number 错误码 + * errmsg string 错误信息 + */ + public static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}"; + private WeixinProperties wxConfig; + private RestTemplate wxRestTemplate; + private RedisService redisService; + + public WxAppletTools(RestTemplate wxRestTemplate, WeixinProperties wxConfig, RedisService redisService) { + this.wxRestTemplate = wxRestTemplate; + this.wxConfig = wxConfig; + this.redisService = redisService; + } + + /** + * 自定义部分数据 + * + * @param wxConfig + * @return + */ + public WxAppletTools build(WeixinProperties wxConfig) { + this.wxConfig = wxConfig; + return this; + } + + /** + * @param resqBody + * @return + */ + public Code2SessionRespBody getOpenIdByJscode2session(Code2SessionResqBody resqBody) { + long start = System.currentTimeMillis(); + String requestUrl = MessageFormat.format(JSCODE_2_SESSION_URL, wxConfig.getAppId(), wxConfig.getSecretId(), resqBody.getJsCode()); + long end = System.currentTimeMillis(); + log.info("code换取sessionKey时间:{}", (end - start)); + String respBody = wxRestTemplate.getForEntity(requestUrl, String.class).getBody(); + end = System.currentTimeMillis(); + log.info("code换取sessionKey时间:{}", (end - start)); + log.info("Jscode2session:{}", respBody); + Code2SessionRespBody code2SessionRespBody = WxJsonUtils.parseObject(respBody, Code2SessionRespBody.class); + // 判断有误异常 + if (StringUtils.hasLength(code2SessionRespBody.getErrorMsg())) { + // 抛出错误 + throw new WxException(code2SessionRespBody.getErrorCode() + ":" + code2SessionRespBody.getErrorMsg()); + } + return code2SessionRespBody; + } + + /** + * @return + */ + public String getAccessToken(String version) { + String accessToken = redisService.getCacheObject(ACCESSTOKEN_CACHE_KEY + version); + if (StringUtils.hasLength(accessToken)) { + return accessToken; + } + String requestUrl = MessageFormat.format(ACCESS_TOKEN_URL, wxConfig.getAppId(), wxConfig.getSecretId()); + String respBody = wxRestTemplate.getForEntity(requestUrl, String.class).getBody(); + AccessTokenRespBody accessTokenRespBody = WxJsonUtils.parseObject(respBody, AccessTokenRespBody.class); + // 判断有误异常 + if (StringUtils.hasLength(accessTokenRespBody.getErrorMsg())) { + // 抛出错误 + throw new WxException(accessTokenRespBody.getErrorCode() + ":" + accessTokenRespBody.getErrorMsg()); + } + redisService.setCacheObject(ACCESSTOKEN_CACHE_KEY + version, accessTokenRespBody.getAccessToken(), 7200L, TimeUnit.SECONDS); + return accessTokenRespBody.getAccessToken(); + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCache.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCache.java new file mode 100644 index 0000000..fa82920 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCache.java @@ -0,0 +1,117 @@ +package com.ruoyi.system.utils.wx.tools; + +import java.util.concurrent.TimeUnit; + +/** + * 缓存 + * + * @author xiaochen + */ +class WxCache { + /** + * 缓存的初始化容量 + */ + private int initialCapacity = 50; + /** + * 缓存最大容量 + */ + private long maximumSize = 200L; + /** + * 缓存时长 + */ + private long duration = 7000L; + /** + * 时长单位,自动转换 + * 支持: + * 时 + * 分 + * 秒 + * 天 + */ + private TimeUnit timeunit = TimeUnit.SECONDS; + + public int getInitialCapacity() { + return initialCapacity; + } + + public void setInitialCapacity(int initialCapacity) { + this.initialCapacity = initialCapacity; + } + + public long getMaximumSize() { + return maximumSize; + } + + public void setMaximumSize(long maximumSize) { + this.maximumSize = maximumSize; + } + + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public TimeUnit getTimeunit() { + return timeunit; + } + + public void setTimeunit(TimeUnit timeunit) { + this.timeunit = timeunit; + } + + public static class Builder { + private int initialCapacity; + private long maximumSize; + private long duration; + private TimeUnit timeunit; + + public Builder setInitialCapacity(int initialCapacity) { + this.initialCapacity = initialCapacity; + return this; + } + + public Builder setMaximumSize(long maximumSize) { + this.maximumSize = maximumSize; + return this; + } + + public Builder setDuration(long duration) { + this.duration = duration; + return this; + } + + public Builder setTimeUnit(TimeUnit timeunit) { + this.timeunit = timeunit; + return this; + } + + public WxCache build() { + return new WxCache(this); + } + } + + public static Builder options() { + return new Builder(); + } + + private WxCache(Builder builder) { + this.initialCapacity = 0 == builder.initialCapacity ? this.initialCapacity : builder.initialCapacity; + this.maximumSize = 0L == builder.maximumSize ? this.maximumSize : builder.maximumSize; + this.duration = 0L == builder.duration ? this.duration : builder.duration; + this.timeunit = null == builder.timeunit ? this.timeunit : builder.timeunit; + } + + @Override + public String toString() { + return "WxCache{" + + "initialCapacity=" + initialCapacity + + ", maximumSize=" + maximumSize + + ", duration=" + duration + + ", timeunit=" + timeunit + + '}'; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCacheTemplate.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCacheTemplate.java new file mode 100644 index 0000000..8640177 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCacheTemplate.java @@ -0,0 +1,34 @@ +package com.ruoyi.system.utils.wx.tools; + +/** + * @author xiaochen + * @ClassName WxCacheTemplate + * @Description + * @date 2021-01-11 11:27 + */ +public interface WxCacheTemplate<T> { + /** + * 保存key + * + * @param key + * @param value + * @return + */ + boolean setKey(String key, T value); + + /** + * 获取缓存 + * + * @param key + * @return + */ + T getKey(String key); + + /** + * 删除 + * + * @param key + * @return + */ + boolean delKey(String key); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxException.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxException.java new file mode 100644 index 0000000..09d46ae --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxException.java @@ -0,0 +1,55 @@ +package com.ruoyi.system.utils.wx.tools; + +/** + * @author lihen + */ +public class WxException extends RuntimeException { + + private final static int OK = 0; + private final static int ValidateSignatureError = -40001; + private final static int ParseXmlError = -40002; + public final static int ComputeSignatureError = -40003; + private final static int IllegalAesKey = -40004; + private final static int ValidateAppidError = -40005; + private final static int EncryptAESError = -40006; + private final static int DecryptAESError = -40007; + private final static int IllegalBuffer = -40008; + + private int code; + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "签名验证错误"; + case ParseXmlError: + return "xml解析失败"; + case ComputeSignatureError: + return "sha加密生成签名失败"; + case IllegalAesKey: + return "SymmetricKey非法"; + case ValidateAppidError: + return "appid校验失败"; + case EncryptAESError: + return "aes加密失败"; + case DecryptAESError: + return "aes解密失败"; + case IllegalBuffer: + return "解密后得到的buffer非法"; + default: + return null; + } + } + + public int getCode() { + return code; + } + + WxException(int code) { + super(getMessage(code)); + this.code = code; + } + + public WxException(String message) { + super(message); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxJsonUtils.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxJsonUtils.java new file mode 100644 index 0000000..cc6113e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxJsonUtils.java @@ -0,0 +1,109 @@ +package com.ruoyi.system.utils.wx.tools; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Json转换工具类 + * 参考:https://blog.csdn.net/weixin_38413579/article/details/82562634 + * @author madman + */ +@Slf4j +public final class WxJsonUtils { + public static final String dateFormat = "yyyy-MM-dd"; + public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + private static final ObjectMapper OM = new ObjectMapper(); + private static final JavaTimeModule timeModule = new JavaTimeModule(); + + /** + * 转换LocalDateTime + */ + static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> { + @Override + public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(localDateTime.format(DateTimeFormatter.ofPattern(dateTimeFormat))); + } + } + + /** + * 转换LocalDate + */ + static class LocalDateSerializer extends JsonSerializer<LocalDate> { + @Override + public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(localDate.format(DateTimeFormatter.ofPattern(dateFormat))); + } + } + + /** + * 设置 ObjectMapper + * + * @return + */ + private static ObjectMapper getObjectMapper() { + // 序列化 + timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); + timeModule.addSerializer(LocalDate.class, new LocalDateSerializer()); + // 反序列化 + timeModule.addDeserializer(LocalDateTime.class, + new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat))); + timeModule.addDeserializer(LocalDate.class, + new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat))); + // 允许对象忽略json中不存在的属性 + OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + OM.registerModule(timeModule); + return OM; + } + + /** + * 将对象序列化 + */ + public static <T> String toJsonString(T obj) { + try { + ObjectMapper om = getObjectMapper(); + return om.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("转json字符串失败:{}", obj); + return null; + } + } + + /** + * 反序列化对象字符串 + */ + public static <T> T parseObject(String json, Class<T> clazz) { + try { + ObjectMapper om = getObjectMapper(); + return om.readValue(json, clazz); + } catch (JsonProcessingException e) { + throw new RuntimeException("反序列化对象字符串失败"); + } + } + + /** + * 反序列化字符串成为对象 + */ + public static <T> T parseObject(String json, TypeReference<T> valueTypeRef) { + try { + ObjectMapper om = getObjectMapper(); + return om.readValue(json, valueTypeRef); + } catch (JsonProcessingException e) { + throw new RuntimeException("反序列化字符串成为对象失败"); + } + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxUtils.java b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxUtils.java new file mode 100644 index 0000000..6287358 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxUtils.java @@ -0,0 +1,175 @@ +package com.ruoyi.system.utils.wx.tools; + +import com.ruoyi.system.utils.wx.pojo.AppletUserDecodeData; +import com.ruoyi.common.utils.sign.Base64; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.CharEncoding; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.AlgorithmParameters; +import java.security.Security; +import java.util.Arrays; + +/** + * @Description 获取用户信息工具类 + * @Author xiaochen + * @Date 2021/8/12 15:45 + */ +@Slf4j +public class WxUtils { + + /** + * 微信小程序API 用户数据的解密 + * + * @param encryptedData + * @param sessionKey + * @param iv + * @return + */ + public static AppletUserDecodeData encryptedData(String encryptedData, String sessionKey, String iv) { + // 被加密的数据 + byte[] dataByte = Base64.decode(encryptedData); + // 加密秘钥 + byte[] keyByte = Base64.decode(sessionKey); + // 偏移量 + byte[] ivByte = Base64.decode(iv); + try { + // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要 + int base = 16; + if (keyByte.length % base != 0) { + int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0); + byte[] temp = new byte[groups * base]; + Arrays.fill(temp, (byte) 0); + System.arraycopy(keyByte, 0, temp, 0, keyByte.length); + keyByte = temp; + } + // 初始化 + Security.addProvider(new BouncyCastleProvider()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC"); + SecretKeySpec spec = new SecretKeySpec(keyByte, "AES"); + AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES"); + parameters.init(new IvParameterSpec(ivByte)); + cipher.init(Cipher.DECRYPT_MODE, spec, parameters); + byte[] resultByte = cipher.doFinal(dataByte); + if (null != resultByte && resultByte.length > 0) { + String result = new String(resultByte, CharEncoding.UTF_8); + log.info("解密原串:{}", result); + return WxJsonUtils.parseObject(result, AppletUserDecodeData.class); + } + throw new RuntimeException("解密的数据为空"); + } catch (Exception e) { + log.error("解密失败. error = {}", e.getMessage(), e); + throw new RuntimeException(e.getMessage()); + } + } + + /** + * 微信小程序API 用户数据的签名验证 + * signature = sha1( rawData + session_key ) + * + * @param rawData 不包括敏感信息的原始数据字符串,用于计算签名。 + * @param sessionKey + */ + public static void verifySignature(String rawData, String sessionKey, String signature) { + String serverSignature = SHA1.getSHA1(rawData + sessionKey); + log.info(rawData + ">>>>>>:" + sessionKey + " === " + serverSignature + " ======" + signature); + if (!signature.equals(serverSignature)) { + throw new RuntimeException("数据验签不通过"); + } + } + + /** + * 根据流接收请求数据 + * + * @param request + * @return + */ + public static String streamBodyByReceive(HttpServletRequest request) throws IOException { + log.info("微信异步回调地址:{}", request.getRequestURL()); + StringBuffer buffer = new StringBuffer(); + InputStream inputStream = request.getInputStream(); + InputStreamReader reader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(reader); + String body = null; + while ((body = bufferedReader.readLine()) != null) { + buffer.append(body); + } + String data = buffer.toString(); + reader.close(); + inputStream.close(); + log.info("微信异步回调数据:{}", data); + return data; + } + + /** + * 日志 + * + * @return + */ + public static Logger getLogger() { + Logger logger = LoggerFactory.getLogger("wxpay java sdk"); + return logger; + } + + /** + * debug + * + * @param msg + * @param args + */ + public static void debug(String msg, Object... args) { + Logger log = getLogger(); + if (log.isDebugEnabled()) { + log.debug(msg, args); + } + } + + /** + * info + * + * @param msg + * @param args + */ + public static void info(String msg, Object... args) { + Logger log = getLogger(); + if (log.isInfoEnabled()) { + log.info(msg, args); + } + } + + /** + * warn + * + * @param msg + * @param args + */ + public static void warn(String msg, Object... args) { + Logger log = getLogger(); + if (log.isWarnEnabled()) { + log.warn(msg, args); + } + } + + /** + * error + * + * @param msg + * @param args + */ + public static void error(String msg, Object... args) { + Logger log = getLogger(); + if (log.isErrorEnabled()) { + log.error(msg, args); + } + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/vo/RoleInfoVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/vo/RoleInfoVO.java new file mode 100644 index 0000000..091bba6 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/vo/RoleInfoVO.java @@ -0,0 +1,16 @@ +package com.ruoyi.system.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class RoleInfoVO { + private Long roleId; + + private String roleName; + + @ApiModelProperty("菜单id") + private List<Long> menus; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysOperLogVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysOperLogVO.java new file mode 100644 index 0000000..9775650 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysOperLogVO.java @@ -0,0 +1,24 @@ +package com.ruoyi.system.vo; + +import com.ruoyi.system.domain.SysOperLog; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "操作日志VO") +public class SysOperLogVO extends SysOperLog { + + @ApiModelProperty(value = "公司名称") + private String companyName; + + @ApiModelProperty(value = "部门名称") + private String deptName; + + @ApiModelProperty(value = "角色名称") + private String roleName; + + @ApiModelProperty(value = "操作详情") + private String operDetail; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysUserVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysUserVO.java new file mode 100644 index 0000000..09a4e0d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/vo/SysUserVO.java @@ -0,0 +1,23 @@ +package com.ruoyi.system.vo; + +import com.ruoyi.common.core.domain.entity.SysUser; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "账户列表VO") +public class SysUserVO extends SysUser { + + @ApiModelProperty(value = "身份") + private Integer companyType; + @ApiModelProperty(value = "单位名称") + private String companyName; + @ApiModelProperty(value = "部门") + private List<String> deptList; + @ApiModelProperty(value = "角色") + private String roleName; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/vo/UserInfoVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/vo/UserInfoVo.java new file mode 100644 index 0000000..a0113a1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/vo/UserInfoVo.java @@ -0,0 +1,175 @@ +package com.ruoyi.system.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.entity.SysDept; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +@Data +public class UserInfoVo { + @ApiModelProperty(value = "用户id") + private Long user_id; + + @ApiModelProperty(value = "部门id") + private Long deptId; + + @ApiModelProperty(value = "登录名称") + private String user_name; + + @ApiModelProperty(value = "用户名称") + private String nick_name; + + /** 用户邮箱 */ + @ApiModelProperty(value = "用户邮箱") + private String email; + + /** 手机号码 */ + @ApiModelProperty(value = "手机号码") + private String phonenumber; + + /** 用户性别 */ + @ApiModelProperty(value = "用户性别 0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + @ApiModelProperty(value = "用户头像") + private String avatar; + + /** 密码 */ + @ApiModelProperty(value = "密码") + private String password; + + /** 帐号状态(0正常 1停用) */ + @ApiModelProperty(value = "帐号状态 0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty(value = "删除标志(0代表存在 2代表删除)") + private String delFlag; + + @ApiModelProperty(value = "部门对象") + private SysDept dept; + + /** 单位id */ + @ApiModelProperty(value = "单位id") + private Long companyId; + + /** + * 身份证号 + */ + @ApiModelProperty(value = "身份证号") + private String idCard; + + /** + * 家庭住址 + */ + @ApiModelProperty(value = "家庭住址") + private String address; + + /** + * 工种 1=工作负责人 2=技工 3=普工 4=机械工 + */ + @ApiModelProperty(value = "工种 1=工作负责人 2=技工 3=普工 4=机械工") + private Integer workType; + + /** + * 保险单号 + */ + @ApiModelProperty(value = "保险单号") + private String insureNumber; + + /** + * 保险到期时间 + */ + @ApiModelProperty(value = "保险到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date insureEndTime; + + /** + * 资质证明单号 + */ + @ApiModelProperty(value = "资质证明单号") + private String qualificationNumber; + + /** + * 资质到期时间 + */ + @ApiModelProperty(value = "资质到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date qualificationEndTime; + + /** + * 健康状况(1=合格 0不合格) + */ + @ApiModelProperty(value = "健康状况(1=合格 0不合格)") + private Integer healthCondition; + + /** + * 安全积分 + */ + @ApiModelProperty(value = "安全积分") + private Integer safetyPoints; + + /** + * 安全考试情况 1=合格 0不合格 + */ + @ApiModelProperty(value = "安全考试情况 1=合格 0不合格") + private Integer secureTest; + + /** + * 身份证图片 + */ + @ApiModelProperty(value = "身份证图片") + private String idCardPicture; + /** + * 保单图片 + */ + @ApiModelProperty(value = "保单图片") + private String insurePicture; + /** + * 体检表图片 + */ + @ApiModelProperty(value = "体检表图片") + private String medicalExaminationPicture; + /** + * 资质证明图片 + */ + @ApiModelProperty(value = "资质证明图片") + private String qualificationPicture; + /** + * 二维码(唯一标识) + */ + @ApiModelProperty(value = "二维码(唯一标识)") + private String qrcodeLink; + /** + * 身份 + */ + @ApiModelProperty(value = "身份 1= 分包 2= 新能源 3 = 劳务借工") + private String companyType; + /** + * 工作单位 + */ + @ApiModelProperty(value = "工作单位") + private String companyName; + /** + * 用户角色 + */ + @ApiModelProperty(value = "角色名称") + private String roleName; + /** + * 用户角色id + */ + @ApiModelProperty(value = "角色id") + private Long roleId; + + @ApiModelProperty(value = "类型 1经理 2负责人 3专员") + private Integer userType; +} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..ca39f47 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysConfigMapper"> + + <resultMap type="SysConfig" id="SysConfigResult"> + <id property="configId" column="config_id" /> + <result property="configName" column="config_name" /> + <result property="configKey" column="config_key" /> + <result property="configValue" column="config_value" /> + <result property="configType" column="config_type" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + </resultMap> + + <sql id="selectConfigVo"> + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + </sql> + + <!-- 查询条件 --> + <sql id="sqlwhereSearch"> + <where> + <if test="configId !=null"> + and config_id = #{configId} + </if> + <if test="configKey !=null and configKey != ''"> + and config_key = #{configKey} + </if> + </where> + </sql> + + <select id="selectConfig" parameterType="SysConfig" resultMap="SysConfigResult"> + <include refid="selectConfigVo"/> + <include refid="sqlwhereSearch"/> + </select> + + <select id="selectConfigList" parameterType="SysConfig" resultMap="SysConfigResult"> + <include refid="selectConfigVo"/> + <where> + <if test="configName != null and configName != ''"> + AND config_name like concat('%', #{configName}, '%') + </if> + <if test="configType != null and configType != ''"> + AND config_type = #{configType} + </if> + <if test="configKey != null and configKey != ''"> + AND config_key like concat('%', #{configKey}, '%') + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + and date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + and date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + </if> + </where> + </select> + + <select id="selectConfigById" parameterType="Long" resultMap="SysConfigResult"> + <include refid="selectConfigVo"/> + where config_id = #{configId} + </select> + + <select id="checkConfigKeyUnique" parameterType="String" resultMap="SysConfigResult"> + <include refid="selectConfigVo"/> + where config_key = #{configKey} limit 1 + </select> + + <insert id="insertConfig" parameterType="SysConfig"> + insert into sys_config ( + <if test="configName != null and configName != '' ">config_name,</if> + <if test="configKey != null and configKey != '' ">config_key,</if> + <if test="configValue != null and configValue != '' ">config_value,</if> + <if test="configType != null and configType != '' ">config_type,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + <if test="remark != null and remark != ''">remark,</if> + create_time + )values( + <if test="configName != null and configName != ''">#{configName},</if> + <if test="configKey != null and configKey != ''">#{configKey},</if> + <if test="configValue != null and configValue != ''">#{configValue},</if> + <if test="configType != null and configType != ''">#{configType},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + <if test="remark != null and remark != ''">#{remark},</if> + sysdate() + ) + </insert> + + <update id="updateConfig" parameterType="SysConfig"> + update sys_config + <set> + <if test="configName != null and configName != ''">config_name = #{configName},</if> + <if test="configKey != null and configKey != ''">config_key = #{configKey},</if> + <if test="configValue != null and configValue != ''">config_value = #{configValue},</if> + <if test="configType != null and configType != ''">config_type = #{configType},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + <if test="remark != null">remark = #{remark},</if> + update_time = sysdate() + </set> + where config_id = #{configId} + </update> + + <delete id="deleteConfigById" parameterType="Long"> + delete from sys_config where config_id = #{configId} + </delete> + + <delete id="deleteConfigByIds" parameterType="Long"> + delete from sys_config where config_id in + <foreach item="configId" collection="array" open="(" separator="," close=")"> + #{configId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..cf439f6 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysDeptMapper"> + + <resultMap type="SysDept" id="SysDeptResult"> + <id property="deptId" column="dept_id" /> + <result property="parentId" column="parent_id" /> + <result property="ancestors" column="ancestors" /> + <result property="deptName" column="dept_name" /> + <result property="orderNum" column="order_num" /> + <result property="leader" column="leader" /> + <result property="phone" column="phone" /> + <result property="email" column="email" /> + <result property="status" column="status" /> + <result property="delFlag" column="del_flag" /> + <result property="parentName" column="parent_name" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + </resultMap> + + <sql id="selectDeptVo"> + 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 + </sql> + + <select id="selectDeptList" parameterType="SysDept" resultMap="SysDeptResult"> + <include refid="selectDeptVo"/> + where d.del_flag = '0' + <if test="deptId != null and deptId != 0"> + AND dept_id = #{deptId} + </if> + <if test="parentId != null and parentId != 0"> + AND parent_id = #{parentId} + </if> + <if test="deptName != null and deptName != ''"> + AND dept_name like concat('%', #{deptName}, '%') + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + <!-- 数据范围过滤 --> + ${params.dataScope} + order by d.parent_id, d.order_num + </select> + + <select id="selectDeptListByRoleId" resultType="Long"> + select d.dept_id + from sys_dept d + left join sys_role_dept rd on d.dept_id = rd.dept_id + where rd.role_id = #{roleId} + <if test="deptCheckStrictly"> + and d.dept_id not in (select d.parent_id from sys_dept d inner join sys_role_dept rd on d.dept_id = rd.dept_id and rd.role_id = #{roleId}) + </if> + order by d.parent_id, d.order_num + </select> + + <select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult"> + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, + (select dept_name from sys_dept where dept_id = d.parent_id) parent_name + from sys_dept d + where d.dept_id = #{deptId} + </select> + + <select id="checkDeptExistUser" parameterType="Long" resultType="int"> + select count(1) from sys_user where dept_id = #{deptId} and del_flag = '0' + </select> + + <select id="hasChildByDeptId" parameterType="Long" resultType="int"> + select count(1) from sys_dept + where del_flag = '0' and parent_id = #{deptId} limit 1 + </select> + + <select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult"> + select * from sys_dept where find_in_set(#{deptId}, ancestors) + </select> + + <select id="selectNormalChildrenDeptById" parameterType="Long" resultType="int"> + select count(*) from sys_dept where status = 0 and del_flag = '0' and find_in_set(#{deptId}, ancestors) + </select> + + <select id="checkDeptNameUnique" resultMap="SysDeptResult"> + <include refid="selectDeptVo"/> + where dept_name=#{deptName} and parent_id = #{parentId} and del_flag = '0' limit 1 + </select> + + <insert id="insertDept" parameterType="SysDept"> + insert into sys_dept( + <if test="deptId != null and deptId != 0">dept_id,</if> + <if test="parentId != null and parentId != 0">parent_id,</if> + <if test="deptName != null and deptName != ''">dept_name,</if> + <if test="ancestors != null and ancestors != ''">ancestors,</if> + <if test="orderNum != null">order_num,</if> + <if test="leader != null and leader != ''">leader,</if> + <if test="phone != null and phone != ''">phone,</if> + <if test="email != null and email != ''">email,</if> + <if test="status != null">status,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="deptId != null and deptId != 0">#{deptId},</if> + <if test="parentId != null and parentId != 0">#{parentId},</if> + <if test="deptName != null and deptName != ''">#{deptName},</if> + <if test="ancestors != null and ancestors != ''">#{ancestors},</if> + <if test="orderNum != null">#{orderNum},</if> + <if test="leader != null and leader != ''">#{leader},</if> + <if test="phone != null and phone != ''">#{phone},</if> + <if test="email != null and email != ''">#{email},</if> + <if test="status != null">#{status},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + + <update id="updateDept" parameterType="SysDept"> + update sys_dept + <set> + <if test="parentId != null and parentId != 0">parent_id = #{parentId},</if> + <if test="deptName != null and deptName != ''">dept_name = #{deptName},</if> + <if test="ancestors != null and ancestors != ''">ancestors = #{ancestors},</if> + <if test="orderNum != null">order_num = #{orderNum},</if> + <if test="leader != null">leader = #{leader},</if> + <if test="phone != null">phone = #{phone},</if> + <if test="email != null">email = #{email},</if> + <if test="status != null and status != ''">status = #{status},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where dept_id = #{deptId} + </update> + + <update id="updateDeptChildren" parameterType="java.util.List"> + update sys_dept set ancestors = + <foreach collection="depts" item="item" index="index" + separator=" " open="case dept_id" close="end"> + when #{item.deptId} then #{item.ancestors} + </foreach> + where dept_id in + <foreach collection="depts" item="item" index="index" + separator="," open="(" close=")"> + #{item.deptId} + </foreach> + </update> + + <update id="updateDeptStatusNormal" parameterType="Long"> + update sys_dept set status = '0' where dept_id in + <foreach collection="array" item="deptId" open="(" separator="," close=")"> + #{deptId} + </foreach> + </update> + + <delete id="deleteDeptById" parameterType="Long"> + update sys_dept set del_flag = '2' where dept_id = #{deptId} + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..4fae8d7 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysDictDataMapper"> + + <resultMap type="SysDictData" id="SysDictDataResult"> + <id property="dictCode" column="dict_code" /> + <result property="dictSort" column="dict_sort" /> + <result property="dictLabel" column="dict_label" /> + <result property="dictValue" column="dict_value" /> + <result property="dictType" column="dict_type" /> + <result property="cssClass" column="css_class" /> + <result property="listClass" column="list_class" /> + <result property="isDefault" column="is_default" /> + <result property="status" column="status" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + </resultMap> + + <sql id="selectDictDataVo"> + 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 + </sql> + + <select id="selectDictDataList" parameterType="SysDictData" resultMap="SysDictDataResult"> + <include refid="selectDictDataVo"/> + <where> + <if test="dictType != null and dictType != ''"> + AND dict_type = #{dictType} + </if> + <if test="dictLabel != null and dictLabel != ''"> + AND dict_label like concat('%', #{dictLabel}, '%') + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + </where> + order by dict_sort asc + </select> + + <select id="selectDictDataByType" parameterType="SysDictData" resultMap="SysDictDataResult"> + <include refid="selectDictDataVo"/> + where status = '0' and dict_type = #{dictType} order by dict_sort asc + </select> + + <select id="selectDictLabel" resultType="String"> + select dict_label from sys_dict_data + where dict_type = #{dictType} and dict_value = #{dictValue} + </select> + + <select id="selectDictDataById" parameterType="Long" resultMap="SysDictDataResult"> + <include refid="selectDictDataVo"/> + where dict_code = #{dictCode} + </select> + + <select id="countDictDataByType" resultType="Integer"> + select count(1) from sys_dict_data where dict_type=#{dictType} + </select> + <select id="selectDictDataByTypeAndValue" resultType="java.lang.String"> + select dict_label from sys_dict_data where dict_type=#{dictType} and dict_value=#{dictValue} + </select> + + <delete id="deleteDictDataById" parameterType="Long"> + delete from sys_dict_data where dict_code = #{dictCode} + </delete> + + <delete id="deleteDictDataByIds" parameterType="Long"> + delete from sys_dict_data where dict_code in + <foreach collection="array" item="dictCode" open="(" separator="," close=")"> + #{dictCode} + </foreach> + </delete> + + <update id="updateDictData" parameterType="SysDictData"> + update sys_dict_data + <set> + <if test="dictSort != null">dict_sort = #{dictSort},</if> + <if test="dictLabel != null and dictLabel != ''">dict_label = #{dictLabel},</if> + <if test="dictValue != null and dictValue != ''">dict_value = #{dictValue},</if> + <if test="dictType != null and dictType != ''">dict_type = #{dictType},</if> + <if test="cssClass != null">css_class = #{cssClass},</if> + <if test="listClass != null">list_class = #{listClass},</if> + <if test="isDefault != null and isDefault != ''">is_default = #{isDefault},</if> + <if test="status != null">status = #{status},</if> + <if test="remark != null">remark = #{remark},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where dict_code = #{dictCode} + </update> + + <update id="updateDictDataType" parameterType="String"> + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + </update> + + <insert id="insertDictData" parameterType="SysDictData"> + insert into sys_dict_data( + <if test="dictSort != null">dict_sort,</if> + <if test="dictLabel != null and dictLabel != ''">dict_label,</if> + <if test="dictValue != null and dictValue != ''">dict_value,</if> + <if test="dictType != null and dictType != ''">dict_type,</if> + <if test="cssClass != null and cssClass != ''">css_class,</if> + <if test="listClass != null and listClass != ''">list_class,</if> + <if test="isDefault != null and isDefault != ''">is_default,</if> + <if test="status != null">status,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="dictSort != null">#{dictSort},</if> + <if test="dictLabel != null and dictLabel != ''">#{dictLabel},</if> + <if test="dictValue != null and dictValue != ''">#{dictValue},</if> + <if test="dictType != null and dictType != ''">#{dictType},</if> + <if test="cssClass != null and cssClass != ''">#{cssClass},</if> + <if test="listClass != null and listClass != ''">#{listClass},</if> + <if test="isDefault != null and isDefault != ''">#{isDefault},</if> + <if test="status != null">#{status},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..55b4075 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysDictTypeMapper"> + + <resultMap type="SysDictType" id="SysDictTypeResult"> + <id property="dictId" column="dict_id" /> + <result property="dictName" column="dict_name" /> + <result property="dictType" column="dict_type" /> + <result property="status" column="status" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + </resultMap> + + <sql id="selectDictTypeVo"> + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + </sql> + + <select id="selectDictTypeList" parameterType="SysDictType" resultMap="SysDictTypeResult"> + <include refid="selectDictTypeVo"/> + <where> + <if test="dictName != null and dictName != ''"> + AND dict_name like concat('%', #{dictName}, '%') + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + <if test="dictType != null and dictType != ''"> + AND dict_type like concat('%', #{dictType}, '%') + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + and date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + and date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + </if> + </where> + </select> + + <select id="selectDictTypeAll" resultMap="SysDictTypeResult"> + <include refid="selectDictTypeVo"/> + </select> + + <select id="selectDictTypeById" parameterType="Long" resultMap="SysDictTypeResult"> + <include refid="selectDictTypeVo"/> + where dict_id = #{dictId} + </select> + + <select id="selectDictTypeByType" parameterType="String" resultMap="SysDictTypeResult"> + <include refid="selectDictTypeVo"/> + where dict_type = #{dictType} + </select> + + <select id="checkDictTypeUnique" parameterType="String" resultMap="SysDictTypeResult"> + <include refid="selectDictTypeVo"/> + where dict_type = #{dictType} limit 1 + </select> + + <delete id="deleteDictTypeById" parameterType="Long"> + delete from sys_dict_type where dict_id = #{dictId} + </delete> + + <delete id="deleteDictTypeByIds" parameterType="Long"> + delete from sys_dict_type where dict_id in + <foreach collection="array" item="dictId" open="(" separator="," close=")"> + #{dictId} + </foreach> + </delete> + + <update id="updateDictType" parameterType="SysDictType"> + update sys_dict_type + <set> + <if test="dictName != null and dictName != ''">dict_name = #{dictName},</if> + <if test="dictType != null and dictType != ''">dict_type = #{dictType},</if> + <if test="status != null">status = #{status},</if> + <if test="remark != null">remark = #{remark},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where dict_id = #{dictId} + </update> + + <insert id="insertDictType" parameterType="SysDictType"> + insert into sys_dict_type( + <if test="dictName != null and dictName != ''">dict_name,</if> + <if test="dictType != null and dictType != ''">dict_type,</if> + <if test="status != null">status,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="dictName != null and dictName != ''">#{dictName},</if> + <if test="dictType != null and dictType != ''">#{dictType},</if> + <if test="status != null">#{status},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..822d665 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysLogininforMapper"> + + <resultMap type="SysLogininfor" id="SysLogininforResult"> + <id property="infoId" column="info_id" /> + <result property="userName" column="user_name" /> + <result property="status" column="status" /> + <result property="ipaddr" column="ipaddr" /> + <result property="loginLocation" column="login_location" /> + <result property="browser" column="browser" /> + <result property="os" column="os" /> + <result property="msg" column="msg" /> + <result property="loginTime" column="login_time" /> + </resultMap> + + <insert id="insertLogininfor" parameterType="SysLogininfor"> + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + </insert> + + <select id="selectLogininforList" parameterType="SysLogininfor" resultMap="SysLogininforResult"> + select info_id, user_name, ipaddr, login_location, browser, os, status, msg, login_time from sys_logininfor + <where> + <if test="ipaddr != null and ipaddr != ''"> + AND ipaddr like concat('%', #{ipaddr}, '%') + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + <if test="userName != null and userName != ''"> + AND user_name like concat('%', #{userName}, '%') + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + AND login_time >= #{params.beginTime} + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + AND login_time <= #{params.endTime} + </if> + </where> + order by info_id desc + </select> + + <delete id="deleteLogininforByIds" parameterType="Long"> + delete from sys_logininfor where info_id in + <foreach collection="array" item="infoId" open="(" separator="," close=")"> + #{infoId} + </foreach> + </delete> + + <update id="cleanLogininfor"> + truncate table sys_logininfor + </update> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..17f8a9b --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysMenuMapper"> + + <resultMap type="SysMenu" id="SysMenuResult"> + <id property="menuId" column="menu_id" /> + <result property="menuName" column="menu_name" /> + <result property="parentName" column="parent_name" /> + <result property="parentId" column="parent_id" /> + <result property="orderNum" column="order_num" /> + <result property="path" column="path" /> + <result property="component" column="component" /> + <result property="query" column="query" /> + <result property="isFrame" column="is_frame" /> + <result property="isCache" column="is_cache" /> + <result property="menuType" column="menu_type" /> + <result property="visible" column="visible" /> + <result property="status" column="status" /> + <result property="perms" column="perms" /> + <result property="icon" column="icon" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateTime" column="update_time" /> + <result property="updateBy" column="update_by" /> + <result property="remark" column="remark" /> + </resultMap> + + <sql id="selectMenuVo"> + select menu_id, menu_name, parent_id, order_num, path, component, `query`, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time + from sys_menu + </sql> + + <select id="selectMenuList" parameterType="SysMenu" resultMap="SysMenuResult"> + <include refid="selectMenuVo"/> + <where> + <if test="menuName != null and menuName != ''"> + AND menu_name like concat('%', #{menuName}, '%') + </if> + <if test="visible != null and visible != ''"> + AND visible = #{visible} + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + </where> + order by parent_id, order_num + </select> + + <select id="selectMenuTreeAll" resultMap="SysMenuResult"> + select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time + from sys_menu m where m.menu_type in ('M', 'C') and m.status = 0 + order by m.parent_id, m.order_num + </select> + + <select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult"> + select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role ro on ur.role_id = ro.role_id + where ur.user_id = #{params.userId} + <if test="menuName != null and menuName != ''"> + AND m.menu_name like concat('%', #{menuName}, '%') + </if> + <if test="visible != null and visible != ''"> + AND m.visible = #{visible} + </if> + <if test="status != null and status != ''"> + AND m.status = #{status} + </if> + order by m.parent_id, m.order_num + </select> + + <select id="selectMenuTreeByUserId" parameterType="Long" resultMap="SysMenuResult"> + select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role ro on ur.role_id = ro.role_id + left join sys_user u on ur.user_id = u.user_id + where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0 AND ro.status = 0 + order by m.parent_id, m.order_num + </select> + + <select id="selectMenuListByRoleId" resultType="Long"> + select m.menu_id + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + where rm.role_id = #{roleId} + <if test="menuCheckStrictly"> + and m.menu_id not in (select m.parent_id from sys_menu m inner join sys_role_menu rm on m.menu_id = rm.menu_id and rm.role_id = #{roleId}) + </if> + order by m.parent_id, m.order_num + </select> + + <select id="selectMenuPerms" resultType="String"> + select distinct m.perms + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + </select> + + <select id="selectMenuPermsByUserId" parameterType="Long" resultType="String"> + select distinct m.perms + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role r on r.role_id = ur.role_id + where m.status = '0' and r.status = '0' and ur.user_id = #{userId} + </select> + + <select id="selectMenuPermsByRoleId" parameterType="Long" resultType="String"> + select distinct m.perms + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + where m.status = '0' and rm.role_id = #{roleId} + </select> + + <select id="selectMenuById" parameterType="Long" resultMap="SysMenuResult"> + <include refid="selectMenuVo"/> + where menu_id = #{menuId} + </select> + + <select id="hasChildByMenuId" resultType="Integer"> + select count(1) from sys_menu where parent_id = #{menuId} + </select> + + <select id="checkMenuNameUnique" parameterType="SysMenu" resultMap="SysMenuResult"> + <include refid="selectMenuVo"/> + where menu_name=#{menuName} and parent_id = #{parentId} limit 1 + </select> + <select id="selectListByRoleId" resultType="com.ruoyi.common.core.domain.entity.SysMenu"> + select sm.menu_id AS menuId, sm.menu_name AS menuName, sm.parent_id AS parentId, sm.order_num AS orderNum, sm.`path` AS path, sm.component AS component, + sm.`query` AS query, sm.is_frame AS isFrame,sm.is_cache AS isCache, sm.menu_type AS menuType, sm.visible AS visible, sm.status AS status, + ifnull(sm.perms,'') as perms, sm.icon AS icon, sm.create_time AS createTime + from sys_role_menu srm + left join sys_menu sm on srm.menu_id = sm.menu_id + WHERE srm.role_id = #{roleId} + </select> + + <select id="getAllInIds" resultType="com.ruoyi.common.core.domain.entity.SysMenu"> + select + menu_id AS menuId, + menu_name AS menuName, + parent_id AS parentId, + order_num AS orderNum, + `path` AS path, + component AS component, + `query` AS query, + is_frame AS isFrame, + is_cache AS isCache, + menu_type AS menuType, + visible AS visible, + STATUS AS STATUS, + IFNULL( perms, '' ) AS perms, + icon AS icon, + create_time AS createTime + from sys_menu where menu_id in + <foreach collection="menusId" close=")" index="index" item="id" open="(" separator=","> + #{id} + </foreach> + </select> + <select id="selectList" resultType="com.ruoyi.common.core.domain.entity.SysMenu"> + select + menu_id AS menuId, + menu_name AS menuName, + parent_id AS parentId, + order_num AS orderNum, + `path` AS path, + component AS component, + `query` AS query, + is_frame AS isFrame, + is_cache AS isCache, + menu_type AS menuType, + visible AS visible, + STATUS AS STATUS, + IFNULL( perms, '' ) AS perms, + icon AS icon, + create_time AS createTime + from sys_menu + </select> + + <update id="updateMenu" parameterType="SysMenu"> + update sys_menu + <set> + <if test="menuName != null and menuName != ''">menu_name = #{menuName},</if> + <if test="parentId != null">parent_id = #{parentId},</if> + <if test="orderNum != null">order_num = #{orderNum},</if> + <if test="path != null and path != ''">path = #{path},</if> + <if test="component != null">component = #{component},</if> + <if test="query != null">`query` = #{query},</if> + <if test="isFrame != null and isFrame != ''">is_frame = #{isFrame},</if> + <if test="isCache != null and isCache != ''">is_cache = #{isCache},</if> + <if test="menuType != null and menuType != ''">menu_type = #{menuType},</if> + <if test="visible != null">visible = #{visible},</if> + <if test="status != null">status = #{status},</if> + <if test="perms !=null">perms = #{perms},</if> + <if test="icon !=null and icon != ''">icon = #{icon},</if> + <if test="remark != null and remark != ''">remark = #{remark},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where menu_id = #{menuId} + </update> + + <insert id="insertMenu" parameterType="SysMenu"> + insert into sys_menu( + <if test="menuId != null and menuId != 0">menu_id,</if> + <if test="parentId != null and parentId != 0">parent_id,</if> + <if test="menuName != null and menuName != ''">menu_name,</if> + <if test="orderNum != null">order_num,</if> + <if test="path != null and path != ''">path,</if> + <if test="component != null and component != ''">component,</if> + <if test="query != null and query != ''">`query`,</if> + <if test="isFrame != null and isFrame != ''">is_frame,</if> + <if test="isCache != null and isCache != ''">is_cache,</if> + <if test="menuType != null and menuType != ''">menu_type,</if> + <if test="visible != null">visible,</if> + <if test="status != null">status,</if> + <if test="perms !=null and perms != ''">perms,</if> + <if test="icon != null and icon != ''">icon,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="menuId != null and menuId != 0">#{menuId},</if> + <if test="parentId != null and parentId != 0">#{parentId},</if> + <if test="menuName != null and menuName != ''">#{menuName},</if> + <if test="orderNum != null">#{orderNum},</if> + <if test="path != null and path != ''">#{path},</if> + <if test="component != null and component != ''">#{component},</if> + <if test="query != null and query != ''">#{query},</if> + <if test="isFrame != null and isFrame != ''">#{isFrame},</if> + <if test="isCache != null and isCache != ''">#{isCache},</if> + <if test="menuType != null and menuType != ''">#{menuType},</if> + <if test="visible != null">#{visible},</if> + <if test="status != null">#{status},</if> + <if test="perms !=null and perms != ''">#{perms},</if> + <if test="icon != null and icon != ''">#{icon},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + + <delete id="deleteMenuById" parameterType="Long"> + delete from sys_menu where menu_id = #{menuId} + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..65d3079 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysNoticeMapper"> + + <resultMap type="SysNotice" id="SysNoticeResult"> + <result property="noticeId" column="notice_id" /> + <result property="noticeTitle" column="notice_title" /> + <result property="noticeType" column="notice_type" /> + <result property="noticeContent" column="notice_content" /> + <result property="status" column="status" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + <result property="remark" column="remark" /> + </resultMap> + + <sql id="selectNoticeVo"> + 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 + </sql> + + <select id="selectNoticeById" parameterType="Long" resultMap="SysNoticeResult"> + <include refid="selectNoticeVo"/> + where notice_id = #{noticeId} + </select> + + <select id="selectNoticeList" parameterType="SysNotice" resultMap="SysNoticeResult"> + <include refid="selectNoticeVo"/> + <where> + <if test="noticeTitle != null and noticeTitle != ''"> + AND notice_title like concat('%', #{noticeTitle}, '%') + </if> + <if test="noticeType != null and noticeType != ''"> + AND notice_type = #{noticeType} + </if> + <if test="createBy != null and createBy != ''"> + AND create_by like concat('%', #{createBy}, '%') + </if> + </where> + </select> + + <insert id="insertNotice" parameterType="SysNotice"> + insert into sys_notice ( + <if test="noticeTitle != null and noticeTitle != '' ">notice_title, </if> + <if test="noticeType != null and noticeType != '' ">notice_type, </if> + <if test="noticeContent != null and noticeContent != '' ">notice_content, </if> + <if test="status != null and status != '' ">status, </if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="noticeTitle != null and noticeTitle != ''">#{noticeTitle}, </if> + <if test="noticeType != null and noticeType != ''">#{noticeType}, </if> + <if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </if> + <if test="status != null and status != ''">#{status}, </if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + + <update id="updateNotice" parameterType="SysNotice"> + update sys_notice + <set> + <if test="noticeTitle != null and noticeTitle != ''">notice_title = #{noticeTitle}, </if> + <if test="noticeType != null and noticeType != ''">notice_type = #{noticeType}, </if> + <if test="noticeContent != null">notice_content = #{noticeContent}, </if> + <if test="status != null and status != ''">status = #{status}, </if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where notice_id = #{noticeId} + </update> + + <delete id="deleteNoticeById" parameterType="Long"> + delete from sys_notice where notice_id = #{noticeId} + </delete> + + <delete id="deleteNoticeByIds" parameterType="Long"> + delete from sys_notice where notice_id in + <foreach item="noticeId" collection="array" open="(" separator="," close=")"> + #{noticeId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..cc568e7 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysOperLogMapper"> + + <resultMap type="SysOperLog" id="SysOperLogResult"> + <id property="operId" column="oper_id" /> + <result property="title" column="title" /> + <result property="businessType" column="business_type" /> + <result property="method" column="method" /> + <result property="requestMethod" column="request_method" /> + <result property="operatorType" column="operator_type" /> + <result property="operName" column="oper_name" /> + <result property="deptName" column="dept_name" /> + <result property="operUrl" column="oper_url" /> + <result property="operIp" column="oper_ip" /> + <result property="operLocation" column="oper_location" /> + <result property="operParam" column="oper_param" /> + <result property="jsonResult" column="json_result" /> + <result property="status" column="status" /> + <result property="errorMsg" column="error_msg" /> + <result property="operTime" column="oper_time" /> + <result property="costTime" column="cost_time" /> + <result property="companyName" column="companyName" /> + <result property="roleName" column="roleName" /> + <result property="phonenumber" column="phonenumber" /> + <result property="userId" column="userId" /> + <result property="nickName" column="nickName" /> + </resultMap> + + <sql id="selectOperLogVo"> + 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,companyName,roleName,phonenumber,userId,nickName + from sys_oper_log + </sql> + + <insert id="insertOperlog" parameterType="SysOperLog"> + 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,companyName,roleName,phonenumber,userId,nickName, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime},#{companyName},#{roleName},#{phonenumber},#{userId},#{nickName}, sysdate()) + </insert> + + <select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult"> + <include refid="selectOperLogVo"/> + <where> + <if test="operIp != null and operIp != ''"> + AND oper_ip like concat('%', #{operIp}, '%') + </if> + <if test="title != null and title != ''"> + AND title like concat('%', #{title}, '%') + </if> + <if test="businessType != null"> + AND business_type = #{businessType} + </if> + <if test="businessTypes != null and businessTypes.length > 0"> + AND business_type in + <foreach collection="businessTypes" item="businessType" open="(" separator="," close=")"> + #{businessType} + </foreach> + </if> + <if test="status != null"> + AND status = #{status} + </if> + <if test="operName != null and operName != ''"> + AND oper_name like concat('%', #{operName}, '%') + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + AND oper_time >= #{params.beginTime} + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + AND oper_time <= #{params.endTime} + </if> + </where> + order by oper_id desc + </select> + + <delete id="deleteOperLogByIds" parameterType="Long"> + delete from sys_oper_log where oper_id in + <foreach collection="operIds" item="operId" open="(" separator="," close=")"> + #{operId} + </foreach> + </delete> + + <select id="selectOperLogById" parameterType="Long" resultMap="SysOperLogResult"> + <include refid="selectOperLogVo"/> + where oper_id = #{operId} + </select> + <select id="selectOperLogPageList" resultType="com.ruoyi.system.vo.SysOperLogVO"> + select sol.oper_id AS operId, sol.title AS title, sol.business_type AS businessType, sol.`method` AS method, sol.request_method AS requestMethod, + sol.operator_type AS operatorType,sol.oper_name AS operName,sol.dept_name AS deptName, sol.oper_url AS operUrl, sol.oper_ip AS operIp, + sol.oper_location AS operLocation, sol.oper_param AS operLocation,sol.json_result AS jsonResult, sol.status AS status,sol.error_msg AS errorMsg, + sol.oper_time AS operTime, sol.cost_time AS costTime,sol.companyName AS companyName,sol.roleName AS roleName,sol.phonenumber AS phonenumber, + sol.userId AS userId,sol.nickName AS nickName + from sys_oper_log sol + ORDER BY sol.oper_time DESC + </select> + + <update id="cleanOperLog"> + truncate table sys_oper_log + </update> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..227c459 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysPostMapper"> + + <resultMap type="SysPost" id="SysPostResult"> + <id property="postId" column="post_id" /> + <result property="postCode" column="post_code" /> + <result property="postName" column="post_name" /> + <result property="postSort" column="post_sort" /> + <result property="status" column="status" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + <result property="remark" column="remark" /> + </resultMap> + + <sql id="selectPostVo"> + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + </sql> + + <select id="selectPostList" parameterType="SysPost" resultMap="SysPostResult"> + <include refid="selectPostVo"/> + <where> + <if test="postCode != null and postCode != ''"> + AND post_code like concat('%', #{postCode}, '%') + </if> + <if test="status != null and status != ''"> + AND status = #{status} + </if> + <if test="postName != null and postName != ''"> + AND post_name like concat('%', #{postName}, '%') + </if> + </where> + </select> + + <select id="selectPostAll" resultMap="SysPostResult"> + <include refid="selectPostVo"/> + </select> + + <select id="selectPostById" parameterType="Long" resultMap="SysPostResult"> + <include refid="selectPostVo"/> + where post_id = #{postId} + </select> + + <select id="selectPostListByUserId" parameterType="Long" resultType="Long"> + select p.post_id + from sys_post p + left join sys_user_post up on up.post_id = p.post_id + left join sys_user u on u.user_id = up.user_id + where u.user_id = #{userId} + </select> + + <select id="selectPostsByUserName" parameterType="String" resultMap="SysPostResult"> + select p.post_id, p.post_name, p.post_code + from sys_post p + left join sys_user_post up on up.post_id = p.post_id + left join sys_user u on u.user_id = up.user_id + where u.user_name = #{userName} + </select> + + <select id="checkPostNameUnique" parameterType="String" resultMap="SysPostResult"> + <include refid="selectPostVo"/> + where post_name=#{postName} limit 1 + </select> + + <select id="checkPostCodeUnique" parameterType="String" resultMap="SysPostResult"> + <include refid="selectPostVo"/> + where post_code=#{postCode} limit 1 + </select> + + <update id="updatePost" parameterType="SysPost"> + update sys_post + <set> + <if test="postCode != null and postCode != ''">post_code = #{postCode},</if> + <if test="postName != null and postName != ''">post_name = #{postName},</if> + <if test="postSort != null">post_sort = #{postSort},</if> + <if test="status != null and status != ''">status = #{status},</if> + <if test="remark != null">remark = #{remark},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where post_id = #{postId} + </update> + + <insert id="insertPost" parameterType="SysPost" useGeneratedKeys="true" keyProperty="postId"> + insert into sys_post( + <if test="postId != null and postId != 0">post_id,</if> + <if test="postCode != null and postCode != ''">post_code,</if> + <if test="postName != null and postName != ''">post_name,</if> + <if test="postSort != null">post_sort,</if> + <if test="status != null and status != ''">status,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + create_time + )values( + <if test="postId != null and postId != 0">#{postId},</if> + <if test="postCode != null and postCode != ''">#{postCode},</if> + <if test="postName != null and postName != ''">#{postName},</if> + <if test="postSort != null">#{postSort},</if> + <if test="status != null and status != ''">#{status},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + sysdate() + ) + </insert> + + <delete id="deletePostById" parameterType="Long"> + delete from sys_post where post_id = #{postId} + </delete> + + <delete id="deletePostByIds" parameterType="Long"> + delete from sys_post where post_id in + <foreach collection="array" item="postId" open="(" separator="," close=")"> + #{postId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..7c4139b --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysRoleDeptMapper"> + + <resultMap type="SysRoleDept" id="SysRoleDeptResult"> + <result property="roleId" column="role_id" /> + <result property="deptId" column="dept_id" /> + </resultMap> + + <delete id="deleteRoleDeptByRoleId" parameterType="Long"> + delete from sys_role_dept where role_id=#{roleId} + </delete> + + <select id="selectCountRoleDeptByDeptId" resultType="Integer"> + select count(1) from sys_role_dept where dept_id=#{deptId} + </select> + + <delete id="deleteRoleDept" parameterType="Long"> + delete from sys_role_dept where role_id in + <foreach collection="array" item="roleId" open="(" separator="," close=")"> + #{roleId} + </foreach> + </delete> + + <insert id="batchRoleDept"> + insert into sys_role_dept(role_id, dept_id) values + <foreach item="item" index="index" collection="list" separator=","> + (#{item.roleId},#{item.deptId}) + </foreach> + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..f3dfeeb --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,234 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysRoleMapper"> + + <resultMap type="SysRole" id="SysRoleResult"> + <id property="roleId" column="role_id" /> + <result property="roleName" column="role_name" /> + <result property="roleKey" column="role_key" /> + <result property="roleSort" column="role_sort" /> + <result property="dataScope" column="data_scope" /> + <result property="menuCheckStrictly" column="menu_check_strictly" /> + <result property="deptCheckStrictly" column="dept_check_strictly" /> + <result property="status" column="status" /> + <result property="delFlag" column="del_flag" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + <result property="remark" column="remark" /> + <result property="removeDays" column="removeDays" /> + <result property="postType" column="postType" /> + </resultMap> + + <sql id="selectRoleVo"> + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark,r.postType,r.removeDays + 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 + </sql> + + <select id="selectRoleList" parameterType="SysRole" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + where r.del_flag = '0' + <if test="roleId != null and roleId != 0"> + AND r.role_id = #{roleId} + </if> + <if test="roleName != null and roleName != ''"> + AND r.role_name like concat('%', #{roleName}, '%') + </if> + <if test="status != null and status != ''"> + AND r.status = #{status} + </if> + <if test="roleKey != null and roleKey != ''"> + AND r.role_key like concat('%', #{roleKey}, '%') + </if> + order by r.role_sort + </select> + + <select id="selectRolePermissionByUserId" parameterType="Long" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + WHERE r.del_flag = '0' and ur.user_id = #{userId} + </select> + + <select id="selectRoleAll" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + </select> + + <select id="selectRoleListByUserId" parameterType="Long" resultType="Long"> + select r.role_id + 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 + where u.user_id = #{userId} + </select> + + <select id="selectRoleById" parameterType="Long" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + where r.role_id = #{roleId} + </select> + + <select id="selectRolesByUserName" parameterType="String" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + WHERE r.del_flag = '0' and u.user_name = #{userName} + </select> + + <select id="checkRoleNameUnique" parameterType="String" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + where r.role_name=#{roleName} and r.del_flag = '0' limit 1 + </select> + + <select id="checkRoleKeyUnique" parameterType="String" resultMap="SysRoleResult"> + <include refid="selectRoleVo"/> + where r.role_key=#{roleKey} and r.del_flag = '0' limit 1 + </select> + <select id="selectCountByRoleName" resultType="java.lang.Integer"> + select count(*) from sys_role + <where> + <if test="roleName != null and roleName != ''"> + AND role_name = #{roleName} + </if> + AND del_flag = 0 + </where> + </select> + <select id="selectPageList" resultType="com.ruoyi.common.core.domain.entity.SysRole"> + select a.role_id AS roleId, a.role_name AS roleName, a.role_key AS roleKey, a.role_sort AS roleSort, a.data_scope AS dataScope, + a.menu_check_strictly AS menuCheckStrictly, a.dept_check_strictly AS deptCheckStrictly,a.status AS status, a.del_flag AS delFlag, + a.create_time AS createTime,a.create_by AS createBy,a.postType AS postType,a.removeDays AS removeDays, + IFNULL(b.userCount,0) as userCount + from sys_role a + LEFT JOIN + (SELECT + r.role_id AS roleId, + COUNT(ur.user_id) AS userCount + FROM sys_role r + LEFT JOIN sys_user_role ur ON r.role_id = ur.role_id + where r.del_flag = 0 + GROUP BY r.role_id) b on a.role_id = b.roleId + <where> + <if test="query.roleName != null and query.roleName != ''"> + AND a.role_name LIKE concat('%',#{query.roleName},'%') + </if> + <if test="query.status != null"> + AND a.status = #{query.status} + </if> + AND a.del_flag = 0 + </where> + </select> + <select id="selectCount" resultType="java.lang.Integer"> + select count(*) from sys_role + <where> + <if test="status != null"> + AND status = #{status} + </if> + AND del_flag = 0 + </where> + </select> + <select id="selectListByDelFlag" resultType="com.ruoyi.common.core.domain.entity.SysRole"> + select role_id AS roleId, role_name AS roleName, role_key AS roleKey, role_sort AS roleSort, data_scope AS dataScope, + menu_check_strictly AS menuCheckStrictly, dept_check_strictly AS deptCheckStrictly,status AS status, del_flag AS delFlag, + create_time AS createTime,create_by AS createBy,postType AS postType,removeDays AS removeDays + from sys_role where del_flag = 0 + </select> + + <select id="selectRoleByUserId" resultType="com.ruoyi.common.core.domain.entity.SysRole"> + select distinct r.role_id AS roleId, r.role_name AS roleName, r.role_key AS roleKey, r.role_sort AS roleSort, r.data_scope AS dataScope, + r.menu_check_strictly AS menuCheckStrictly, r.dept_check_strictly AS deptCheckStrictly,r.status AS status, + r.del_flag AS delFlag, r.create_time AS createTime,r.create_by AS createBy,r.postType AS postType,r.removeDays AS removeDays + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + where ur.user_id = #{userId} + </select> + <select id="selectByUserId" resultType="java.lang.String"> + select t2.role_name from sys_user_role t1 + left join sys_role t2 on t1.role_id = t2.role_id + where t1.user_id = #{userId} + </select> + <select id="selectRoleByUserIds" resultType="com.ruoyi.common.core.domain.entity.SysRole"> + select + a.user_id as role_id, + b.nick_name as role_name + from sys_user_role a + left join sys_user b on a.user_id = b.user_id + where a.role_id in + <foreach item="item" index="index" collection="roleIds" open="(" separator="," close=")"> + #{item} + </foreach> + </select> + + <insert id="insertRole" parameterType="SysRole" useGeneratedKeys="true" keyProperty="roleId"> + insert into sys_role( + <if test="roleId != null and roleId != 0">role_id,</if> + <if test="roleName != null and roleName != ''">role_name,</if> + <if test="roleKey != null and roleKey != ''">role_key,</if> + <if test="roleSort != null">role_sort,</if> + <if test="dataScope != null and dataScope != ''">data_scope,</if> + <if test="menuCheckStrictly != null">menu_check_strictly,</if> + <if test="deptCheckStrictly != null">dept_check_strictly,</if> + <if test="status != null and status != ''">status,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + <if test="removeDays != null">removeDays,</if> + <if test="postType != null">postType,</if> + create_time + )values( + <if test="roleId != null and roleId != 0">#{roleId},</if> + <if test="roleName != null and roleName != ''">#{roleName},</if> + <if test="roleKey != null and roleKey != ''">#{roleKey},</if> + <if test="roleSort != null">#{roleSort},</if> + <if test="dataScope != null and dataScope != ''">#{dataScope},</if> + <if test="menuCheckStrictly != null">#{menuCheckStrictly},</if> + <if test="deptCheckStrictly != null">#{deptCheckStrictly},</if> + <if test="status != null and status != ''">#{status},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + <if test="removeDays != null">#{removeDays},</if> + <if test="postType != null">#{postType},</if> + sysdate() + ) + </insert> + + <update id="updateRole" parameterType="SysRole"> + update sys_role + <set> + <if test="roleName != null and roleName != ''">role_name = #{roleName},</if> + <if test="roleKey != null and roleKey != ''">role_key = #{roleKey},</if> + <if test="roleSort != null">role_sort = #{roleSort},</if> + <if test="dataScope != null and dataScope != ''">data_scope = #{dataScope},</if> + <if test="menuCheckStrictly != null">menu_check_strictly = #{menuCheckStrictly},</if> + <if test="deptCheckStrictly != null">dept_check_strictly = #{deptCheckStrictly},</if> + <if test="status != null and status != ''">status = #{status},</if> + <if test="remark != null">remark = #{remark},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + <if test="removeDays != null">removeDays = #{removeDays},</if> + <if test="postType != null">postType = #{postType},</if> + update_time = sysdate() + </set> + where role_id = #{roleId} + </update> + <update id="updateStatus" parameterType="SysRole"> + update sys_role + <set> + <if test="status != null">status = #{status},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + update_time = sysdate() + </set> + where role_id = #{roleId} + </update> + + <delete id="deleteRoleById" parameterType="Long"> + update sys_role set del_flag = '2' where role_id = #{roleId} + </delete> + + <delete id="deleteRoleByIds" parameterType="Long"> + update sys_role set del_flag = '2' where role_id in + <foreach collection="roleIds" item="roleId" open="(" separator="," close=")"> + #{roleId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..2853301 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysRoleMenuMapper"> + + <resultMap type="SysRoleMenu" id="SysRoleMenuResult"> + <result property="roleId" column="role_id" /> + <result property="menuId" column="menu_id" /> + </resultMap> + + <select id="checkMenuExistRole" resultType="Integer"> + select count(1) from sys_role_menu where menu_id = #{menuId} + </select> + + <delete id="deleteRoleMenuByRoleId" parameterType="Long"> + delete from sys_role_menu where role_id=#{roleId} + </delete> + + <delete id="deleteRoleMenu" parameterType="Long"> + delete from sys_role_menu where role_id in + <foreach collection="ids" item="roleId" open="(" separator="," close=")"> + #{roleId} + </foreach> + </delete> + + <insert id="batchRoleMenu"> + insert into sys_role_menu(role_id, menu_id) values + <foreach item="item" index="index" collection="list" separator=","> + (#{item.roleId},#{item.menuId}) + </foreach> + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..65ca3b0 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,359 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysUserMapper"> + + <resultMap type="SysUser" id="SysUserResult"> + <id property="userId" column="user_id" /> + <result property="deptId" column="dept_id" /> + <result property="userName" column="user_name" /> + <result property="nickName" column="nick_name" /> + <result property="email" column="email" /> + <result property="phonenumber" column="phonenumber" /> + <result property="sex" column="sex" /> + <result property="avatar" column="avatar" /> + <result property="password" column="password" /> + <result property="status" column="status" /> + <result property="delFlag" column="del_flag" /> + <result property="loginIp" column="login_ip" /> + <result property="loginDate" column="login_date" /> + <result property="createBy" column="create_by" /> + <result property="createTime" column="create_time" /> + <result property="updateBy" column="update_by" /> + <result property="updateTime" column="update_time" /> + <result property="remark" column="remark" /> + <result property="ifBlack" column="ifBlack" /> + <result property="districtId" column="districtId" /> + <association property="dept" javaType="SysDept" resultMap="deptResult" /> + <collection property="roles" javaType="java.util.List" resultMap="RoleResult" /> + </resultMap> + + <resultMap id="deptResult" type="SysDept"> + <id property="deptId" column="dept_id" /> + <result property="parentId" column="parent_id" /> + <result property="deptName" column="dept_name" /> + <result property="ancestors" column="ancestors" /> + <result property="orderNum" column="order_num" /> + <result property="leader" column="leader" /> + <result property="status" column="dept_status" /> + </resultMap> + + <resultMap id="RoleResult" type="SysRole"> + <id property="roleId" column="role_id" /> + <result property="roleName" column="role_name" /> + <result property="roleKey" column="role_key" /> + <result property="roleSort" column="role_sort" /> + <result property="dataScope" column="data_scope" /> + <result property="status" column="role_status" /> + </resultMap> + + <sql id="selectUserVo"> + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, + u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_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 + </sql> + + <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult"> + select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, + u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + where u.del_flag = '0' + <if test="userId != null and userId != 0"> + AND u.user_id = #{userId} + </if> + <if test="userName != null and userName != ''"> + AND u.user_name like concat('%', #{userName}, '%') + </if> + <if test="status != null and status != ''"> + AND u.status = #{status} + </if> + <if test="phonenumber != null and phonenumber != ''"> + AND u.phonenumber like concat('%', #{phonenumber}, '%') + </if> + <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> + AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') + </if> + <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> + AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') + </if> + <if test="deptId != null and deptId != 0"> + AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) )) + </if> + <!-- 数据范围过滤 --> + ${params.dataScope} + </select> + + <select id="selectAllocatedList" parameterType="SysUser" resultMap="SysUserResult"> + select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time + 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 + where u.del_flag = '0' and r.role_id = #{roleId} + <if test="userName != null and userName != ''"> + AND u.user_name like concat('%', #{userName}, '%') + </if> + <if test="phonenumber != null and phonenumber != ''"> + AND u.phonenumber like concat('%', #{phonenumber}, '%') + </if> + <!-- 数据范围过滤 --> + ${params.dataScope} + </select> + + <select id="selectUnallocatedList" parameterType="SysUser" resultMap="SysUserResult"> + select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time + 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 + where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL) + and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{roleId}) + <if test="userName != null and userName != ''"> + AND u.user_name like concat('%', #{userName}, '%') + </if> + <if test="phonenumber != null and phonenumber != ''"> + AND u.phonenumber like concat('%', #{phonenumber}, '%') + </if> + <!-- 数据范围过滤 --> + ${params.dataScope} + </select> + + <select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult"> + <include refid="selectUserVo"/> + where u.user_name = #{userName} and u.del_flag = '0' + </select> + + <select id="selectUserById" parameterType="Long" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select u.user_id AS userId, u.dept_id AS deptId, u.user_name AS userName, u.nick_name AS nickName, u.email AS email, u.avatar AS avatar, + u.phonenumber AS phonenumber, u.sex AS sex, u.status AS status, u.del_flag AS delFlag, u.login_ip AS loginIp, + u.login_date AS loginDate, u.create_by AS createBy, u.create_time AS createTime, u.remark AS remark,u.ifBlack AS ifBlack, + u.districtId AS districtId, + ur.role_id AS roleId,sr.role_name AS roleName,u.deptName as deptName + from sys_user u + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role sr on ur.role_id = sr.role_id + left join sys_dept sd on u.dept_id = sd.dept_id + where u.user_id = #{userId} and u.del_flag = 0 + </select> + + <select id="checkUserNameUnique" parameterType="String" resultMap="SysUserResult"> + select user_id, user_name from sys_user where user_name = #{userName} and del_flag = '0' limit 1 + </select> + + <select id="checkPhoneUnique" parameterType="String" resultMap="SysUserResult"> + select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} and del_flag = '0' limit 1 + </select> + + <select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult"> + select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1 + </select> + <select id="selectUserByIds" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select user_id AS userId, dept_id AS deptId, user_name AS userName, nick_name AS nickName, email AS email, avatar AS avatar, phonenumber AS phonenumber + from sys_user where user_id in + <foreach collection="userIds" separator="," item="userId" open="(" close=")"> + #{userId} + </foreach> + </select> + <select id="selectList" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select u.user_id AS userId, u.dept_id AS deptId, u.user_name AS userName, u.nick_name AS nickName, u.email AS email, u.avatar AS avatar, + u.phonenumber AS phonenumber, u.sex AS sex, u.status AS status, u.del_flag AS delFlag, u.login_ip AS loginIp, + u.login_date AS loginDate, u.create_by AS createBy, u.create_time AS createTime, u.remark AS remark,u.ifBlack AS ifBlack, u.districtId AS districtId + from sys_user u + WHERE u.del_flag = 0 + </select> + <select id="selectCount" resultType="java.lang.Integer"> + select count(*) from sys_user + <where> + <if test="status != null"> + AND status = #{status} + </if> + AND del_flag = 0 + </where> + </select> + <select id="selectListByNamePhone" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select u.user_id AS userId, u.dept_id AS deptId, u.user_name AS userName, u.nick_name AS nickName, u.email AS email, u.avatar AS avatar, + u.phonenumber AS phonenumber, u.sex AS sex, u.status AS status, u.del_flag AS delFlag, u.login_ip AS loginIp, + u.login_date AS loginDate, u.create_by AS createBy, u.create_time AS createTime, u.remark AS remark,u.ifBlack AS ifBlack, u.districtId AS districtId + from sys_user u + WHERE u.del_flag = 0 + <if test="name != null and name != ''"> + AND (u.nick_name LIKE concat('%',#{name},'%') + OR u.phonenumber LIKE concat('%',#{name},'%')) + </if> + </select> + <select id="selectUserByUserNameList" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select u.user_id AS userId, u.dept_id AS deptId, u.user_name AS userName, u.nick_name AS nickName, u.email AS email, u.avatar AS avatar, + u.phonenumber AS phonenumber + from sys_user u + WHERE u.del_flag = 0 + <if test="names != null and names.size()>0"> + AND u.user_name IN + <foreach collection="names" close=")" open="(" item="name" separator=","> + #{name} + </foreach> + </if> + </select> + <select id="userInfo" resultType="com.ruoyi.system.vo.UserInfoVo"> + select t1.*,t2.companyName,t2.companyType ,tq.qrcodeLink from sys_user t1 + left join t_company t2 on t2.id = t1.companyId + left join t_qrcode tq on t1.user_id = tq.otherId and tq.type=1 + where t1.user_id = #{id} + </select> + <select id="selectByPhone" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select u.user_id AS userId, u.dept_id AS deptId, u.user_name AS userName, u.nick_name AS nickName, u.email AS email, u.avatar AS avatar, + u.phonenumber AS phonenumber, u.sex AS sex, u.status AS status, u.del_flag AS delFlag, u.login_ip AS loginIp, + u.login_date AS loginDate, u.create_by AS createBy, u.create_time AS createTime, u.remark AS remark + from sys_user u where u.phonenumber = #{phonenumber} and u.status = 0 and u.del_flag = 0 + </select> + <select id="getUserInfoBy" resultType="com.ruoyi.system.vo.UserInfoVo"> + select t1.*,t2.companyName,t2.companyType from sys_user t1 + left join t_company t2 on t2.id = t1.companyId + where t1.singleNum = #{singleNum} + </select> + <select id="getUserRole" resultType="java.lang.Long"> + select role_id from sys_user_role where sys_user_role.user_id = #{userId} + </select> + <select id="selectAllList" resultType="com.ruoyi.common.core.domain.entity.SysUser"> + select * from sys_user + </select> + <select id="pageList" resultType="com.ruoyi.system.vo.SysUserVO"> + select u.user_id AS userId, u.dept_id AS deptId, u.user_name AS userName, u.nick_name AS nickName, u.email AS email, u.avatar AS avatar,u.disable_remark AS disableRemark, + u.phonenumber AS phonenumber, u.sex AS sex, u.status AS status, u.del_flag AS delFlag, u.login_ip AS loginIp,u.operating_time AS operatingTime,u.operating_person AS operatingPerson, + u.login_date AS loginDate, u.create_by AS createBy, u.create_time AS createTime, u.remark AS remark,u.ifBlack AS ifBlack, u.districtId AS districtId, + r.role_id AS roleId, r.role_name AS roleName, r.role_key AS roleKey, r.role_sort AS roleSort, r.data_scope AS dataScope, r.status as role_status,u.deptName as deptName + from sys_user u + 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 + WHERE u.del_flag = 0 + <if test="query.nickNameOrPhone != null and query.nickNameOrPhone != ''"> + AND (u.nick_name LIKE concat('%',#{query.nickNameOrPhone},'%') + OR u.phonenumber LIKE concat('%',#{query.nickNameOrPhone},'%')) + </if> + <if test="query.status != null and query.status != ''"> + AND u.status = #{query.status} + </if> + <if test="query.deptIds != null and query.deptIds.size()>0"> + AND u.user_id IN (select DISTINCT user_id from t_dept_to_user where dept_id IN + <foreach collection="query.deptIds" close=")" open="(" item="deptId" separator=","> + #{deptId} + </foreach>) + </if> + <if test="query.roleIds != null and query.roleIds.size()>0"> + AND r.role_id IN + <foreach collection="query.roleIds" close=")" open="(" item="roleId" separator=","> + #{roleId} + </foreach> + </if> + ORDER BY u.create_time DESC + </select> + <select id="selectIdByPhone" resultType="java.lang.Long"> + select user_id from sys_user where phonenumber = #{phonenumber} and status = 0 and del_flag = 0 + </select> + + <insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId"> + insert into sys_user( + <if test="userId != null and userId != 0">user_id,</if> + <if test="deptId != null and deptId != 0">dept_id,</if> + <if test="userName != null and userName != ''">user_name,</if> + <if test="deptName != null and deptName != ''">deptName,</if> + <if test="nickName != null and nickName != ''">nick_name,</if> + <if test="email != null and email != ''">email,</if> + <if test="avatar != null and avatar != ''">avatar,</if> + <if test="phonenumber != null and phonenumber != ''">phonenumber,</if> + <if test="sex != null and sex != ''">sex,</if> + <if test="password != null and password != ''">password,</if> + <if test="status != null and status != ''">status,</if> + <if test="createBy != null and createBy != ''">create_by,</if> + <if test="remark != null and remark != ''">remark,</if> + <if test="ifBlack != null">ifBlack,</if> + <if test="districtId != null">districtId,</if> + create_time + )values( + <if test="userId != null and userId != ''">#{userId},</if> + <if test="deptId != null and deptId != ''">#{deptId},</if> + <if test="userName != null and userName != ''">#{userName},</if> + <if test="deptName != null and deptName != ''">#{deptName},</if> + + <if test="nickName != null and nickName != ''">#{nickName},</if> + <if test="email != null and email != ''">#{email},</if> + <if test="avatar != null and avatar != ''">#{avatar},</if> + <if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if> + <if test="sex != null and sex != ''">#{sex},</if> + <if test="password != null and password != ''">#{password},</if> + <if test="status != null and status != ''">#{status},</if> + <if test="createBy != null and createBy != ''">#{createBy},</if> + <if test="remark != null and remark != ''">#{remark},</if> + <if test="ifBlack != null">#{ifBlack},</if> + <if test="districtId != null">#{districtId},</if> + <if test="businessDeptId != null">#{businessDeptId},</if> + sysdate() + ) + </insert> + + <update id="updateUser" parameterType="SysUser"> + update sys_user + <set> + <if test="deptId != null and deptId != 0">dept_id = #{deptId},</if> + <if test="userName != null and userName != ''">user_name = #{userName},</if> + <if test="nickName != null and nickName != ''">nick_name = #{nickName},</if> + <if test="deptName != null and deptName != ''">deptName = #{deptName},</if> + <if test="email != null ">email = #{email},</if> + <if test="phonenumber != null ">phonenumber = #{phonenumber},</if> + <if test="sex != null and sex != ''">sex = #{sex},</if> + <if test="avatar != null and avatar != ''">avatar = #{avatar},</if> + <if test="password != null and password != ''">password = #{password},</if> + <if test="status != null and status != ''">status = #{status},</if> + <if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if> + <if test="loginDate != null">login_date = #{loginDate},</if> + <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> + <if test="remark != null">remark = #{remark},</if> + <if test="ifBlack != null">ifBlack = #{ifBlack},</if> + <if test="districtId != null">districtId = #{districtId},</if> + <if test="disableRemark != null">disable_remark = #{disableRemark},</if> + <if test="operatingTime != null">operating_time = #{operatingTime},</if> + <if test="operatingPerson != null">operating_person = #{operatingPerson},</if> + update_time = sysdate() + </set> + where user_id = #{userId} + </update> + + <update id="updateUserStatus" parameterType="SysUser"> + update sys_user set status = #{status} where user_id = #{userId} + </update> + + <update id="updateUserAvatar" parameterType="SysUser"> + update sys_user set avatar = #{avatar} where user_name = #{userName} + </update> + + <update id="resetUserPwd" parameterType="SysUser"> + update sys_user set password = #{password} where user_name = #{userName} + </update> + <update id="updateUserIfBlack"> + update sys_user set ifBlack = 0,safetyPoints = 12,update_time = sysdate() + where user_id IN + <foreach collection="ids" separator="," item="userId" open="(" close=")"> + #{userId} + </foreach> + </update> + <update id="updatePassword"> + update sys_user set password = #{s} where user_id = #{id} + </update> + + <delete id="deleteUserById" parameterType="Long"> + update sys_user set del_flag = '2' where user_id = #{userId} + </delete> + + <delete id="deleteUserByIds" parameterType="Long"> + update sys_user set del_flag = '2' where user_id in + <foreach collection="ids" item="userId" open="(" separator="," close=")"> + #{userId} + </foreach> + </delete> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..2b90bc4 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysUserPostMapper"> + + <resultMap type="SysUserPost" id="SysUserPostResult"> + <result property="userId" column="user_id" /> + <result property="postId" column="post_id" /> + </resultMap> + + <delete id="deleteUserPostByUserId" parameterType="Long"> + delete from sys_user_post where user_id=#{userId} + </delete> + + <select id="countUserPostById" resultType="Integer"> + select count(1) from sys_user_post where post_id=#{postId} + </select> + + <delete id="deleteUserPost" parameterType="Long"> + delete from sys_user_post where user_id in + <foreach collection="array" item="userId" open="(" separator="," close=")"> + #{userId} + </foreach> + </delete> + + <insert id="batchUserPost"> + insert into sys_user_post(user_id, post_id) values + <foreach item="item" index="index" collection="list" separator=","> + (#{item.userId},#{item.postId}) + </foreach> + </insert> + +</mapper> \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..f7715e4 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper +PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" +"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="com.ruoyi.system.mapper.SysUserRoleMapper"> + + <resultMap type="SysUserRole" id="SysUserRoleResult"> + <result property="userId" column="user_id" /> + <result property="roleId" column="role_id" /> + </resultMap> + + <delete id="deleteUserRoleByUserId" parameterType="Long"> + delete from sys_user_role where user_id=#{userId} + </delete> + + <select id="countUserRoleByRoleId" resultType="Integer"> + select count(1) from sys_user_role where role_id=#{roleId} + </select> + + <delete id="deleteUserRole" parameterType="Long"> + delete from sys_user_role where user_id in + <foreach collection="ids" item="userId" open="(" separator="," close=")"> + #{userId} + </foreach> + </delete> + + <insert id="batchUserRole"> + insert into sys_user_role(user_id, role_id) values + <foreach item="item" index="index" collection="list" separator=","> + (#{item.userId},#{item.roleId}) + </foreach> + </insert> + <insert id="insertUserRole"> + insert into sys_user_role(user_id, role_id) values (#{userRole.userId},#{userRole.roleId}) + </insert> + + <delete id="deleteUserRoleInfo" parameterType="SysUserRole"> + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + </delete> + + <delete id="deleteUserRoleInfos"> + delete from sys_user_role where role_id=#{roleId} and user_id in + <foreach collection="userIds" item="userId" open="(" separator="," close=")"> + #{userId} + </foreach> + </delete> +</mapper> \ No newline at end of file diff --git a/ruoyi-ui/.editorconfig b/ruoyi-ui/.editorconfig new file mode 100644 index 0000000..7034f9b --- /dev/null +++ b/ruoyi-ui/.editorconfig @@ -0,0 +1,22 @@ +# 告诉EditorConfig插件,这是根文件,不用继续往上查找 +root = true + +# 匹配全部文件 +[*] +# 设置字符集 +charset = utf-8 +# 缩进风格,可选space、tab +indent_style = space +# 缩进的空格数 +indent_size = 2 +# 结尾换行符,可选lf、cr、crlf +end_of_line = lf +# 在文件结尾插入新行 +insert_final_newline = true +# 删除一行中的前后空格 +trim_trailing_whitespace = true + +# 匹配md结尾的文件 +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development new file mode 100644 index 0000000..302ecd1 --- /dev/null +++ b/ruoyi-ui/.env.development @@ -0,0 +1,11 @@ +# 页面标题 +VUE_APP_TITLE = 若依管理系统 + +# 开发环境配置 +ENV = 'development' + +# 若依管理系统/开发环境 +VUE_APP_BASE_API = '/dev-api' + +# 路由懒加载 +VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production new file mode 100644 index 0000000..b4893b0 --- /dev/null +++ b/ruoyi-ui/.env.production @@ -0,0 +1,8 @@ +# 页面标题 +VUE_APP_TITLE = 若依管理系统 + +# 生产环境配置 +ENV = 'production' + +# 若依管理系统/生产环境 +VUE_APP_BASE_API = '/prod-api' diff --git a/ruoyi-ui/.env.staging b/ruoyi-ui/.env.staging new file mode 100644 index 0000000..361859f --- /dev/null +++ b/ruoyi-ui/.env.staging @@ -0,0 +1,10 @@ +# 页面标题 +VUE_APP_TITLE = 若依管理系统 + +NODE_ENV = production + +# 测试环境配置 +ENV = 'staging' + +# 若依管理系统/测试环境 +VUE_APP_BASE_API = '/stage-api' diff --git a/ruoyi-ui/.eslintignore b/ruoyi-ui/.eslintignore new file mode 100644 index 0000000..89be6f6 --- /dev/null +++ b/ruoyi-ui/.eslintignore @@ -0,0 +1,10 @@ +# 忽略build目录下类型为js的文件的语法检查 +build/*.js +# 忽略src/assets目录下文件的语法检查 +src/assets +# 忽略public目录下文件的语法检查 +public +# 忽略当前目录下为js的文件的语法检查 +*.js +# 忽略当前目录下为vue的文件的语法检查 +*.vue \ No newline at end of file diff --git a/ruoyi-ui/.eslintrc.js b/ruoyi-ui/.eslintrc.js new file mode 100644 index 0000000..82bbdee --- /dev/null +++ b/ruoyi-ui/.eslintrc.js @@ -0,0 +1,199 @@ +// ESlint 检查配置 +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint', + sourceType: 'module' + }, + env: { + browser: true, + node: true, + es6: true, + }, + extends: ['plugin:vue/recommended', 'eslint:recommended'], + + // add your custom rules here + //it is base on https://github.com/vuejs/eslint-config-vue + rules: { + "vue/max-attributes-per-line": [2, { + "singleline": 10, + "multiline": { + "max": 1, + "allowFirstLine": false + } + }], + "vue/singleline-html-element-content-newline": "off", + "vue/multiline-html-element-content-newline":"off", + "vue/name-property-casing": ["error", "PascalCase"], + "vue/no-v-html": "off", + 'accessor-pairs': 2, + 'arrow-spacing': [2, { + 'before': true, + 'after': true + }], + 'block-spacing': [2, 'always'], + 'brace-style': [2, '1tbs', { + 'allowSingleLine': true + }], + 'camelcase': [0, { + 'properties': 'always' + }], + 'comma-dangle': [2, 'never'], + 'comma-spacing': [2, { + 'before': false, + 'after': true + }], + 'comma-style': [2, 'last'], + 'constructor-super': 2, + 'curly': [2, 'multi-line'], + 'dot-location': [2, 'property'], + 'eol-last': 2, + 'eqeqeq': ["error", "always", {"null": "ignore"}], + 'generator-star-spacing': [2, { + 'before': true, + 'after': true + }], + 'handle-callback-err': [2, '^(err|error)$'], + 'indent': [2, 2, { + 'SwitchCase': 1 + }], + 'jsx-quotes': [2, 'prefer-single'], + 'key-spacing': [2, { + 'beforeColon': false, + 'afterColon': true + }], + 'keyword-spacing': [2, { + 'before': true, + 'after': true + }], + 'new-cap': [2, { + 'newIsCap': true, + 'capIsNew': false + }], + 'new-parens': 2, + 'no-array-constructor': 2, + 'no-caller': 2, + 'no-console': 'off', + 'no-class-assign': 2, + 'no-cond-assign': 2, + 'no-const-assign': 2, + 'no-control-regex': 0, + 'no-delete-var': 2, + 'no-dupe-args': 2, + 'no-dupe-class-members': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty-character-class': 2, + 'no-empty-pattern': 2, + 'no-eval': 2, + 'no-ex-assign': 2, + 'no-extend-native': 2, + 'no-extra-bind': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-parens': [2, 'functions'], + 'no-fallthrough': 2, + 'no-floating-decimal': 2, + 'no-func-assign': 2, + 'no-implied-eval': 2, + 'no-inner-declarations': [2, 'functions'], + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-iterator': 2, + 'no-label-var': 2, + 'no-labels': [2, { + 'allowLoop': false, + 'allowSwitch': false + }], + 'no-lone-blocks': 2, + 'no-mixed-spaces-and-tabs': 2, + 'no-multi-spaces': 2, + 'no-multi-str': 2, + 'no-multiple-empty-lines': [2, { + 'max': 1 + }], + 'no-native-reassign': 2, + 'no-negated-in-lhs': 2, + 'no-new-object': 2, + 'no-new-require': 2, + 'no-new-symbol': 2, + 'no-new-wrappers': 2, + 'no-obj-calls': 2, + 'no-octal': 2, + 'no-octal-escape': 2, + 'no-path-concat': 2, + 'no-proto': 2, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-return-assign': [2, 'except-parens'], + 'no-self-assign': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-shadow-restricted-names': 2, + 'no-spaced-func': 2, + 'no-sparse-arrays': 2, + 'no-this-before-super': 2, + 'no-throw-literal': 2, + 'no-trailing-spaces': 2, + 'no-undef': 2, + 'no-undef-init': 2, + 'no-unexpected-multiline': 2, + 'no-unmodified-loop-condition': 2, + 'no-unneeded-ternary': [2, { + 'defaultAssignment': false + }], + 'no-unreachable': 2, + 'no-unsafe-finally': 2, + 'no-unused-vars': [2, { + 'vars': 'all', + 'args': 'none' + }], + 'no-useless-call': 2, + 'no-useless-computed-key': 2, + 'no-useless-constructor': 2, + 'no-useless-escape': 0, + 'no-whitespace-before-property': 2, + 'no-with': 2, + 'one-var': [2, { + 'initialized': 'never' + }], + 'operator-linebreak': [2, 'after', { + 'overrides': { + '?': 'before', + ':': 'before' + } + }], + 'padded-blocks': [2, 'never'], + 'quotes': [2, 'single', { + 'avoidEscape': true, + 'allowTemplateLiterals': true + }], + 'semi': [2, 'never'], + 'semi-spacing': [2, { + 'before': false, + 'after': true + }], + 'space-before-blocks': [2, 'always'], + 'space-before-function-paren': [2, 'never'], + 'space-in-parens': [2, 'never'], + 'space-infix-ops': 2, + 'space-unary-ops': [2, { + 'words': true, + 'nonwords': false + }], + 'spaced-comment': [2, 'always', { + 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] + }], + 'template-curly-spacing': [2, 'never'], + 'use-isnan': 2, + 'valid-typeof': 2, + 'wrap-iife': [2, 'any'], + 'yield-star-spacing': [2, 'both'], + 'yoda': [2, 'never'], + 'prefer-const': 2, + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, + 'object-curly-spacing': [2, 'always', { + objectsInObjects: false + }], + 'array-bracket-spacing': [2, 'never'] + } +} diff --git a/ruoyi-ui/.gitignore b/ruoyi-ui/.gitignore new file mode 100644 index 0000000..78a752d --- /dev/null +++ b/ruoyi-ui/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**/*.log + +tests/**/coverage/ +tests/e2e/reports +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock diff --git a/ruoyi-ui/README.md b/ruoyi-ui/README.md new file mode 100644 index 0000000..00c0ab8 --- /dev/null +++ b/ruoyi-ui/README.md @@ -0,0 +1,30 @@ +## 开发 + +```bash +# 克隆项目 +git clone https://gitee.com/y_project/RuoYi-Vue + +# 进入项目目录 +cd ruoyi-ui + +# 安装依赖 +npm install + +# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev +``` + +浏览器访问 http://localhost:80 + +## 发布 + +```bash +# 构建测试环境 +npm run build:stage + +# 构建生产环境 +npm run build:prod +``` \ No newline at end of file diff --git a/ruoyi-ui/babel.config.js b/ruoyi-ui/babel.config.js new file mode 100644 index 0000000..c8267b2 --- /dev/null +++ b/ruoyi-ui/babel.config.js @@ -0,0 +1,13 @@ +module.exports = { + presets: [ + // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app + '@vue/cli-plugin-babel/preset' + ], + 'env': { + 'development': { + // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). + // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. + 'plugins': ['dynamic-import-node'] + } + } +} \ No newline at end of file diff --git a/ruoyi-ui/bin/build.bat b/ruoyi-ui/bin/build.bat new file mode 100644 index 0000000..dda590d --- /dev/null +++ b/ruoyi-ui/bin/build.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [��Ϣ] ���Web���̣�����dist�ļ��� +echo. + +%~d0 +cd %~dp0 + +cd .. +npm run build:prod + +pause \ No newline at end of file diff --git a/ruoyi-ui/bin/package.bat b/ruoyi-ui/bin/package.bat new file mode 100644 index 0000000..0e5bc0f --- /dev/null +++ b/ruoyi-ui/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [��Ϣ] ��װWeb���̣�����node_modules�ļ��� +echo. + +%~d0 +cd %~dp0 + +cd .. +npm install --registry=https://registry.npmmirror.com + +pause \ No newline at end of file diff --git a/ruoyi-ui/bin/run-web.bat b/ruoyi-ui/bin/run-web.bat new file mode 100644 index 0000000..d30deae --- /dev/null +++ b/ruoyi-ui/bin/run-web.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [��Ϣ] ʹ�� Vue CLI �������� Web ���̡� +echo. + +%~d0 +cd %~dp0 + +cd .. +npm run dev + +pause \ No newline at end of file diff --git a/ruoyi-ui/build/index.js b/ruoyi-ui/build/index.js new file mode 100644 index 0000000..0c57de2 --- /dev/null +++ b/ruoyi-ui/build/index.js @@ -0,0 +1,35 @@ +const { run } = require('runjs') +const chalk = require('chalk') +const config = require('../vue.config.js') +const rawArgv = process.argv.slice(2) +const args = rawArgv.join(' ') + +if (process.env.npm_config_preview || rawArgv.includes('--preview')) { + const report = rawArgv.includes('--report') + + run(`vue-cli-service build ${args}`) + + const port = 9526 + const publicPath = config.publicPath + + var connect = require('connect') + var serveStatic = require('serve-static') + const app = connect() + + app.use( + publicPath, + serveStatic('./dist', { + index: ['index.html', '/'] + }) + ) + + app.listen(port, function () { + console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) + if (report) { + console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) + } + + }) +} else { + run(`vue-cli-service build ${args}`) +} diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json new file mode 100644 index 0000000..cecdaac --- /dev/null +++ b/ruoyi-ui/package.json @@ -0,0 +1,91 @@ +{ + "name": "ruoyi", + "version": "3.8.6", + "description": "若依管理系统", + "author": "若依", + "license": "MIT", + "scripts": { + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", + "lint": "eslint --ext .js,.vue src" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "src/**/*.{js,vue}": [ + "eslint --fix", + "git add" + ] + }, + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@riophae/vue-treeselect": "0.4.0", + "axios": "0.24.0", + "clipboard": "2.0.8", + "core-js": "3.25.3", + "echarts": "5.4.0", + "element-ui": "2.15.13", + "file-saver": "2.0.5", + "fuse.js": "6.4.3", + "highlight.js": "9.18.5", + "js-beautify": "1.13.0", + "js-cookie": "3.0.1", + "jsencrypt": "3.0.0-rc.1", + "nprogress": "0.2.0", + "quill": "1.3.7", + "react": "^18.2.0", + "screenfull": "5.0.2", + "sortablejs": "1.10.2", + "vue": "2.6.12", + "vue-count-to": "1.0.13", + "vue-cropper": "0.5.5", + "vue-meta": "2.4.0", + "vue-router": "3.4.9", + "vuedraggable": "2.24.3", + "vuex": "3.6.0" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "4.4.6", + "@vue/cli-plugin-eslint": "4.4.6", + "@vue/cli-service": "4.4.6", + "babel-eslint": "10.1.0", + "babel-plugin-dynamic-import-node": "2.3.3", + "chalk": "4.1.0", + "compression-webpack-plugin": "5.0.2", + "connect": "3.6.6", + "eslint": "7.15.0", + "eslint-plugin-vue": "7.2.0", + "lint-staged": "10.5.3", + "runjs": "4.4.2", + "sass": "1.32.13", + "sass-loader": "10.1.1", + "script-ext-html-webpack-plugin": "2.1.5", + "svg-sprite-loader": "5.1.1", + "vue-template-compiler": "2.6.12" + }, + "engines": { + "node": ">=8.9", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ] +} diff --git a/ruoyi-ui/public/favicon.ico b/ruoyi-ui/public/favicon.ico new file mode 100644 index 0000000..e263760 --- /dev/null +++ b/ruoyi-ui/public/favicon.ico Binary files differ diff --git a/ruoyi-ui/public/html/ie.html b/ruoyi-ui/public/html/ie.html new file mode 100644 index 0000000..052ffcd --- /dev/null +++ b/ruoyi-ui/public/html/ie.html @@ -0,0 +1,46 @@ + +<!DOCTYPE html> +<html lang="zh-CN"> +<head> + <meta charset="UTF-8" /> + <title>请升级您的浏览器</title> + <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" > + <meta name="renderer" content="webkit"> + <base target="_blank" /> + <style type="text/css"> + html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0} + a{text-decoration:none;color:#0072c6;}a:hover{text-decoration:none;color:#004d8c;} + body{width:960px;margin:0 auto;padding:10px;font-size:14px;line-height:24px;color:#454545;font-family:'Microsoft YaHei UI','Microsoft YaHei',DengXian,SimSun,'Segoe UI',Tahoma,Helvetica,sans-serif;overflow-y:scroll} + h1{font-size:40px;line-height:80px;font-weight:100;margin-bottom:10px;} + h2{font-size:20px;line-height:25px;font-weight:100;margin:10px 0;} + em{color:red} + p{margin-bottom:10px;} + hr{margin:20px 0;border:0;border-top:1px solid #dadada} + span{display:block;font-size:12px;line-height:12px;} + .clean{clear:both;} + .browser{padding:10px 10px;} + .browser li{width:auto;padding:0 80px;margin-top:30px;height:34px;line-height:22px;float:left;list-style:none;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAADMCAYAAAAWCXEwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAC7ESURBVHja5Lx5dFRV1rBfgHwYRQQVtB26ZWhtabtfeUGxGxFbUGZF8RMHGkVbRkekVYiKisicVhE0gEwBokgDAhEMMSSQkAECwcxkrlRSqVTqJqnxzs/vj5t7qUyAvr9e37fWV2vtleSm6p6n9t5nn733OVU2RaUaEP5PiqJSbeMXPBTA5/Xhzk9Vnd9vo3HFx21E2LYJX9IRgh6npvyCe9uaqS4K4C3IpXHFx9S99CTuJ8Z0KLVjRlA7ZgTuJ8ZgXxmJL+kIlwAkXBQk6HFq9pWRVA8fSvXwodYgdS892a6EA1UNvouqwXdR99KTeAtyfz2IL+kI1cOHYh9wqwVwKWJqpXbMCOv19gG3Imzb1JF2OgZxfr/NukH4jcNVfyEAE8IU+4BbKet1PfaVke3BtA/i/H6b8aIBt7a4mWmaC0nr55vmqRp8F5V33Mm5LhHtwbQF8SUdsSDCb1I1+K42g1xIWgOYYh9wK+e6RCBs29QxSIWus37aJM51iWjx4so77mwD1d5AHQ1eecedlN9yuyVlva6nrNf14Q7cEmRn4W7u3T2E9ME3UX7L7W1uZg5Weced1s3sA2613ql5LXzQjuRclwjcT4wxTXQeRHC7GLdnHPeensiCVwa3e0PznZk3EbZtwluQa0kofz8NcVNxr++Ce30XnNuv61Bcu7viXt8Fvyu7JYipjfGHxzD+8Bh2j+7fAiZcC+Y0zPDIbCyD6DyV6DyVeDcIQR2C39J4oieNJ3oSOnkVcnZ35Ozu6MVdDHF0N6S4C43OqJYg/0ydzb27hzDx0FjuPT2R+asfa6OVsl7X40s6QoWus/CQk6fWZPHChhxe3lbMCxtyrN9TyxSQSwidvMoC0XK6tRGybPjSRmOuNUKVo4Zxe8YxIu4+Jh4ay/jDY7j39MQWWjnXJYLGFR9Toes8tSaLiavTrIHDxfxfapkCwW8hy9YuhCmhk1fR1FRnaCS1NM4yy8RDYy2tjIkZRXq/HtYsCnqc2sJDTkYsTrU00J6YkEJQR7M/eEGY0MmrcOenqjZA2JmyzTJLuJiOe65LBHUvPUmGR2bE4lQmrk7jqTVZHcrE1WkMWpRIdJ4KnpUXBCHLRl3e16EWIOEaMU00/vAY9na/gsYVH/NdgYe+8w9bMBeSQYsSWXjICcFvL2ga+dhlFwcJ10rjio/ZklprgbSWiavTWvzdd/5hXt5W/OtATC201sq9u4eQ+PVijmSW0nf+YQYtSmTQosR2gUYsTmXQokT6zj9saeRCpmkJ0hxD2gOZeGgsI+Lu45+ps7FXlFmDmDDtSd/5h+k7/zCpZQpa9cwOQciyIR+77LyzFhXlMyZmFOP2jLP8orVWRsTdR2ppHFtSa+k6ZZM1WHvSdcomwyxySceayO4OWTY88TdirygzUkWf18eL2//RQiutYcwYE/Q4tagDOUQ8uo6uUzbRZ3qMJV2nbCLi0XU8tSbrolNXzu6OfOyylgEN4NOkaO5acw/j9ozr0ET37h5imehIZimPL91rAfSZHsOQBfuISS7E7vaTETeX0MmrOoQInbwK+dhlNKWsahni0zPSuGvNPW1M1BrI1NrOwt0WkCn2ijJSS+MYt2ccuQk3oxd36RCi8URPY+HLT1VbgGiSzPsx71laCddMe2Yygf6ZOtuScXvG0XfJn/n8YL+LQnjibyQ34WZ8Xl/bfKSoKL+FVi4EYwKZcu/uIQzaPoExMaPQcrq1ADFX33AI1+6u1OV9HVI6ShU/TYqm75I/dwjTHtDEQ2MZt2ccg7ZPaGGScIDWEBlxc42UoSMQ00StYdoDCgcbtH0Cbx+8p40ZTIBwiFM7RmB3+y+exZvT2YRpDdR6ZoVrw1xRWwN44m/Euf06A6Ki7NLrmnDNmH7TEdSg7RP4/GA/yLK1GdwEKNzSk1M7RlDlqPl1JefOlG2MXTGmXaAxMaMsB/XE34h4tH+7ANlrB7T2iV8OAlDlqOH9mPcsIBPKlF3R16Ad7GwlxoVberYAKCrKv1ghfmkg5sPldLIzZVsLqLErxpC9doAlp3aMICNurlGyVpRdSAu/HqS1Q58rd1JUlI87P1UtKsrHXlGG3e1HCOoov+x2wiX3RxT+o49L1IgutXxVUCfDIxNfLraQDI+M3e3/NdCXbhohqBNfLrIsVzZqmoT6dmXG0SBLTrmJLxd/CVRLECXcDGFaSC1TmHE0yKg4B0P2uxiy38WoOAePHaptAfHYoVqG7HcxcGc5o+IcfFfgsbQUPoYoSa213BbE78oGucTSwpJTbobFFjNgbQHdvi6g8/Z6Om+vZ8h+VxsQE7T/97UMWFvA+Og0UvIryfDIZBQ4CeXvt8a5IAhAY/RImlJWUaHrPHaolhuXFXHN+8e58qNcbomq5P6t3xG973WePLzPgnnsUG0LiP7f1zJwZzk3LisyctfSOFxOJ4lfLzYToQubxu/KpmpWBFWzInguOokrP8ql7/zDRMxLpFfUabasHwlZNnITbmbgznI6b6+3Bu7/fa2lrW5fF9Ar6jQD1hYwLLaYx5fupdi+EiGok748koa4qa010xKkKWUV2UM7kd6vB7tH9yfpnUFkLzQiZOGWnmgHO9N4oie9ok5bA4YPbkqvqNNc8/5xIuYl8tSaLOLLRXambENXF+PxNJD0ziAanVHhYaEliH1lJD/1iqD0qSsIzu2M/N550TZ3QjvYmS3rR1qDtwdhgpgwnabGMj46zRjQsxJdXYw7P1X1pY0GuaRjkMKxPah5qxuV8y6nct7l1LzVDfdyo6miHexM+ou9mblwKfdv/Y77t37HNe8fbwMQDhIxL5FOU2PZklqLJjUYdU7wWxBuN+ricBAF0KQG6pcNovZpw0fCQao/MEBcu7tSOLYHjnu7EZzbmeDczqyfNokrP8ptMXi4XDnzAJ0n72TIgn1oUoMB4VlpgIjj24I0payi9KkrqHj+Ssth2wM5c38f8p68D2nbHKRtc3h86d42A/eZHsOVMw9Y0nXKJmxDvyS1NA70z8Gz0qh5hNvbzpr6ZYMofzyiBUwLkOVdjfR/eVcao0dSl/d1aHx0GhHzEi0TXDnzAJ2mxtJpaixdp2yypM/0GLrcs5D3Y94ztNDsK7qjuxmzDBBz2rYGqZoVQc1b3dr4yfppk+g8eWeLd91aAxGPrqPbyKV0G7mUiEfXMWdz+nmQ0Jsgn1AbT/SkMXrkeZC6vK9DpU9d0S5I5bzLqf6gq6UV7WBn5q9+zDJBuEQ8us4SE6LLPQvpcs9CjmSW4ndlo1XPNBxWLiE34WbSX+wNapEBEsrfT/njERSO7WGBmDA1b3Wj9KkrSO/Xg1WjBjJl/CT+8sQ8a0BT/eGDhwN0uWchXe94ia07YkE+oSLc3gxyQt2yfiSrRg0E+YRqgRSO7UHh2B4UT7ragqmcdznFk67mp14ROO7txpTxk7AN/bLFgN1GLsU29EvrejiACdG59xQjKgu3GzVP9UwIvcmCVwYb102NmBHVBDFNVDUrgjP39yF98E0E5xox5Dcj5lsDhwOYQObg4dK59xR2RV8D4njEo/0NIEd3dkVfgy9t9HkfMTWSO6pXG63kjupF8aSrqXj+SoJzO1M573KmjJ/Eb0bM5y9PzGPBK4Mp3GKUEFvWj+Q3I+a3AOjcewp/eWKesUQ0T1mz2att7oSU9+F5EE2SqXvpSbKHdrIGNmHCoapmRVgh33LezZ3QNncyloGDnVnwyuA2IFvWj0Q+dplREzu6Wy0r9/KubVvg9pWRpPfrwZn7+1haMSHCxdSM/J4RWWufjiC9Xw/m9PgtN9w0uo1JbrhpNI0njAXTrAIbT/TEvb4LjdEj2641vqQjpPfrQfrgm1qYKHxKlz51BbmjerFj4G2WtAYwtWDKglcGG2ZoXrldu43AWDUrAmnbnLaRVZMayHvyPn7qZThoa38pfeoKap+OIDi3M6tGDeSGm0a3GTT82g03jeaGm0bj3H4d8rHLrN0I93LDpDsG3kb68si2a425hfZTrwjSB9/UBiZcM+YM6ghoyvhJpL/Ym+yFhknc67tYQVF+z3gjc3r8Fuf32zpOFTMeHXpRGDNfMYF2j+7PqlEDWTVqIOkv9rZ8SNvcCff6LlTOu9yK1Okv9mZOj9+S8ehQNKmBDhs17vxU9adeES1gwoHKH49oFyhcwhfKynmXWzOu4vkryR7aieyhnQjl7+84QzNNJGzbxN7uV1gw7WmntYZaLw2mmNdrn44ge2gnztzfx9od7zBnDa9t0pdHtgsTDhRustaaCndwEyLj0aG481PVS9r3FSUJj6eBrConMZHvnodpntrh2gkHCgcLl/TBN7G3+xXGLMlIo0LXjU7ixeoaUZIQ3C7OlTtJya8kJvJddgy8DctvWgGFaylcHPd2Y2/3K5jT47esGjWQrTtiyapy4nI6jUrvUmpfUytFRfmkZ6SxdUcs66dNYsfA2ywNtQBrJeb/dgy8jZjId/kx4YgF4fP6Ln1L3uyhhWvnSGYpOw6lEBP5LuunTWLDAw+x4YGHrAi74YGHWD9tEuunTSIm8l227ohtAyBK0i8/pNDagTVJxuf1YXf7OVfuJKvKMF16RhrpGWkcySwlJb+SrCqn1awRgjqaJP9nO0b/Zxo1v+ahS0ZqKJ9QCX5rJMyhN42aRj6h/udB5BKjiAp+i64uNrJ2M0Vs3rUiy4aU92G42X49iCYZDZjUMoX4ctFIcILfGgVU6E0LwEyCxKP98aWNxpc2GvFof+RjlyHlfdjxWnOxh93tJya5kIWHnDx2qJbnopP4NCmaYvtKC0LL6WYkQps70RA3laaUVbjzU1V7RRn2ijK8BbkWUJsM7VIAog7k8MyuPKtD1AJA/9zQQpYN9/oubFk/kpkLl7J4a0KbtrdZa/vSRrfMWS8GcSSzlGd25TH5VIjptTpR9T5SS+OMsrHZD3RHd7SDnTm1YwSzY2KsTtL46DSei07iSGZpm/tKeR8a5gnf0+vI8zfE5zAstpjptTrvifBJeeZ5LTQDkGXDtbsr0fte59mjDmaWaUyv1ZlZpvH3XJlRcQ6Grj5OTHJhy/t7VhrpwMVAog7kMCrOwcs+nZWaccak2L7S0oLpC6d2jGDJiUyWN8E6FVZqsLwJ5ruwYO5O9jFoUSIb4nPOT+/gtxf3kZjkQobFFreAaHRGGZoQbm+hhWd25fHsUQevHilgbo7bAmoNM2S/i6Grj3Mks9Tolcgn1Hb39MzHuXInw9edZrJd4z3xPISuLrYgCrf0ZOuOWKLzVFLLFDIKmlfr5EJmHMxhfoWvDczkUyELxl5RduFUUZNkIvdm8+BpkZd9eocQPyYc6XDnocpRQ+TebObmuFmptdTK5FMhBqwt4K1vMi4cWTMKnIyKczDZrvFJeWaHEBdrbVc5aphxMIflTR1rJaPA2TFI1IEc7k72tZwdYRCLtyZc6h4MMcmF7WrlwRSRAWsLiNyb3T6Iz+vjmV15jIpztIHwxN/I7JgY4svFS47CHk9DG62Y5hm4s5zx0Wntb0CnlikMiy3m06ToFpFSO9iZnSnbeGZXHkcyS8kocF6SHMksZc7m9AuaJyW/si3IltRaZsfEGNM09KZVs2bEzWV5EyzLlXn1SEG7MuNgTruy5JS73dlzd7IvPMi1BIlJLmRnyjbLJFawar7ZHi5NdrSS9jRyd7KPXlGnzQDXyjSlcYY2mk1SuKUnS05kslI7f9M9/HKgdaoh74nn/cR02NV7M9t2A9A/t/qf2uZOvB/zHvNdxk3Mm0bV+36VzK8wxHTWVutPmEbkE6q1hjQ3/yefCvGeeB7k1SPGlLsUeeubDOtnezJnczpvfZPBuXJnGEjzAqSri9FyulG4pSf3b/3OCvErNQNmxsEczpU70ST5kuWXJc9yiZXemQ3du5N9TK/VedmnW1qZm+M+v3r+gpTS42nA42nA5XRa4vE0hFd8zSDBb63cInvtAAYtSuTuZB+T7ZoFYy7tz+zK6+igQZtHRoGTyL3ZLab4M7vyGB+dxpAF+1i8NaEliLmWyNndsa+MZPi60/T/vpaJhTKT7ZqllZWaoZW3vsnA42m4IMS5cifPRScxN8fNeyK87NOZXqszsdDITa55/3i4dgVb0OPUTG2IR/vjzk9Vt6Qau5R3J/uYWCi3MJEJM2dzOkcyS80Q3WKrPia50IIIX2cmnwrxYIpIr6jTPBed1Mo0apFgpv0NcVMR3C5ESWLO5nS6fV3Ag6fFdmHmV/iYcTCHyL3ZRB3IsSRybzbP7MpjfoWvXYj+39cyZME+c7aEgTQ36smy0RA31dostrv9DF193IIJ9xcTxgSam+O2xAQwg9fMMo2JhTIPnjYgBi1KbC+RPq8REyR8iT9X7rRgWptpvssYLBwqHGB6rc7fc2ULYsh+F4MWJbLjUErH09c8ytcaxNTMCxtyGLC2oIUDT6/VO5TJdkMLJsTAneUMWpTYNotvE0eaj3rKxy6zun2t69mdKdt4fOley4lN35ls11pIOIC51D8XnWQu9xcGUQCteibyscuM5n31TKNqD5fm1H9DfA7PRScxdPVxhsUWMyy22Dq4MGS/i2GxxQxfd9oC2HEopb1WVcdtCU2Sqcv7OmTWpGbRLOV9SCh/P0GPUwvPvDIKnMQkFxK5N5s5m9N5LjqJ56KTeOubDFbvzSQlv7LN1P5FxzZ8Xp918v8SWk5WsWStLbr0a5oLHRdY/+GjPP8vtq7+0yCiJOHz+hDcLlxOJ2bzxeV0Irhdlk/9x0B8Xh9VjhoEt6s5rZTaFU1qQHC7qHLU/PpZ05EGqhw1uJxO0CVESSIlv5KoAznM2ZxufTJgzuZ0og7kkJJfaR1mcjmdVDlqflkc6ahSs1eUWdMzJrmQQYsSrYMJNy4raiHmYQWzD2IC2SvKLpa/dAzi8/qsc6cZBU6GLNjHlTMPcEtUJVMSdd45qRGdp7KxDOvDPu+c1JhxNMgtUZVcOfMAQxbss0K7vaLsQqbq+GCtCbEhPodOU2O58qNcZhwNsrMK4t0Xlp1VMONokCs/yqXT1FgrE7sATPvbJK0hblxWxDsnNWugvc7zcqFry3JlbomqbANzSdskpk9kFDjpOmWTpQnzne6sMgbbWWWYY8kpN0tOuYnOU1v8z9TcOyc1blxWRNcpmwwz6dLFjxr7vD5rY+eO13YSMS+Rh/co1iAby4wBluXKLDnl5rsCD1lVxk7FdwUelpxysyxXbvHcjWUwYb9CxLxE7nhtp7X10spELUHMMiHqQA6dJ+9k8KYaJh1u6ZRLTrnZklrb+hS3lURtSa1lySm39fyNZTAlUWfwpho6T95p1rqtS5LzICapJsmWNkbEBpiSqLMs1/gY3DsntfAuT4tDlkrYtci92bxzUmNjmaG9KYk6I2IDbbTStsBqjhma1EBKfiVdp2xiwNoCHt6jMOmwxjsnNev46KWUkaIksfCQk2W5Mu+c1Jh0WGPCfoUBawvoOmWT1d4Miy3nQczIuXpvJp2mxjJ4Uw0T9hsg09KM6fhcdBIxyYWXJM9FJzHjaJBpaTDpsAEzeFMNnabGGhVec+RtA1LlqAFd4vGley0Q8wZTEnWmpWGdWX3sUC3PHnW0K+b/n0qoZ1oaTEszfCQc5PGle0GXwv0k7PxI87S9EMjMMo35rvMdILPDbErrzlA4iOmw4SBh0/iXgUxLg8mnQvw9V2Zmmdau/D1XtpoxpiYe3qPw8B6FW6IqreOCvwpkWhqMinMwaFEi46PTfrFMXG38HLr6OHe8ttPykXZNYzrr4q0JdJoay4C1BS2cdfCmGuZsTrd6Hv/T5ozZJ7no9L1xWZE1fU0bD193unXx3GESFZNcyIb4nDazaUN8Dh6PkTy1O307CmgT9itM2K9YWnkuOumi26wTV6dZR43NXOXKj3LpPHknEY+us0DaDWiWnwCr92bSdcomBm+q4eE9ShsThTXh2jRn5mxOZ/CmmjYzZkRsgE5TY40Q33bhu/iiF66VcJjh604TuTfbUnnk3myGrzttQZgzZtJhzQrvfabHWGNccNELnz2tfSUcJjxADVhbwIC1BdYsMyOp+fyH9yhWGnAks/TS0gDTV4qK8q2NxU5TY7klqrIFTDhQ6+gZ/hwzdoSbpKgo/9LPj5hnR8yUwEwVw810MRkRG7BSRXPpLyrKv/RUsT2YI5mlLZLnEbEBK1q2lhGxASt5vuO1nZY5ioryL5TJX7icENwuioryjV1rr4+oAzkMWbDvouXEkAX7iDqQg8/rQ5MaLgZxaQWWJslWSWkWWBkFzl9UYP2PvgjFPNrj8/osM/2YcIQfE46QnpFmfL7K7SLocWpBj1Mz6+D0jLQWzzPb3b/6aI8SVnCbvXTTVOZxno6kqCjfKlPNUH4pIP9XPGz/N319UFnrf2iKLGi6LmggqCBoIOi6JuiqIqCrgqIrgqyrgoYu6JpiiK4LKgigCpquCCEdQdVVAU0VdP2iMGW29tplmtbcQNQ1QEXXNDQdQGsWHZBbvdQsKkTQfaiaBJrc/PyLPpQ2zqqbL9U10GV0TUbTZUCyQAoaJPaVinx5RmbVKZnVWRpf56r8WKlQFww2Q4bf8VdMXwsEtfkdGb97xSAb8yRG7df4zYYQ3deEsK2WsK1UsK1U6LIqxJWfKQzcEODVw0GS7KbG1F8Pout6C7WuL5Dpv1PBtlLEFgWXfyHTY61Ery91rvkiwLWfB7h6jcxV/5LoskLF9gl0+tjLI7FesuuxzKnrHeqneQdL143Bjacj6wqg4ZFUph8JYvusCdsXIldvhGvXi/T+SuS6dQrXrZO4fp3Ib76UuH5NiD6fi1z/mcgNnwa5epWMbbHG1StEvsoSjbeoq2i60h6MYNN1XTAhNF1vdlBoVFSG7/Nh+1Ti2o1Brl8v03uDyDVfN3DDVz5u+FKh15cKvdbp9FoHvT5X6PW5wjVr4LrPda6NkugTJdL1EwXbIpkVx5sdGaXZ8S9gGgNIJ6ipPHgghO3TED23h+ixTafXZpmb1ofos0ml+9dw1VcaV3wapMvKIF1WSVz+qULPzxV6faZw9Wc613yq0Xt1iN9Ehei+WMG2QObz03JHDtxsGk07P2XRmZ/hx7ZG5rqtMjdubqTHFonrNov8doPMZRvA9pmPqz8X+MNWhb/tkrg/VuGWaJXLPmmk85Imen6m0+sz6BMlcsNqP9etVujysU63jwIcrwy1N6UFm6Zrgma4KKBxrE7lyq999PnaT58dcMNWjV5bFa7d6sP2lcj/+szP6/FNHK2SqQtpSKqIKItUN2psyJH52yYXtkV+uq9UuP5fMj1XqVy9WuWGFSE6LQgxbHMQv6kVXW92B12wKZouSEjGNNMVJvwgYdugcGOsym+2q/TZqnD9dh3bVz5u3h4guVJtnpJa808zkJlBMMS7SQG6vB/gimUKvVdK9Fmu0nu5zLXLZGzvaWzLDhggmoysqwYIKoKqG+rKqVO5douP62JUfvutxg2xCn1iZTpv0rgpRuF0XQAIgRJElSUURUWWZWRZRpFlgrIKeIEg7yaC7X2FXkslei+XDVkmY1sQ4pFNDaA3hwcdNF0XbGjNZwNQWXZaxrZV5XexMjftFLnpW4ne34rYNvjZUywBQUJqEEkMoEk6oqIgySqipCCKEt6Qis8fRNEaAB+TtijYInV6Lwtx7VKRPstkIj5S6PGBRGFtwFCgApquCDYFTQANXZeZkiARsVPnlu9kfhcr0/cbiYivA4w94DM0oet4VQVJUQiJGiFRIiTKBEMSAX+QhoBIvU/C1SQCfpIKGrl8kZerFitcu0Tkuk9ErlsiYXtDYuMpYyobE0gVbIouC6DiDsgMiwtx406Z/rs0+u6WGPCNSI8tIZbnSoCCEvITkBRkWSMUkAgEJbz+EE2+IA3eAPUNjTR6fNTWSni9PuoFN/d8KtBpkcg1n3jp82GQ3h/6sc33seAHb/P6pYOmCTY0VQCNEkHhrgMhfrdL5k/fafT/XqT/boU+sRI/2r0AhESFYFDCF1TwBSWa/CE8TQHcjQFcDX6cdQGq63w43PWU1AoEmup4emMjtvl+enzop/d7Aa57N4Btvo/Z37jCHBzBpuqaAHDOHWDo/iD99in8+XuZO/er/H6fxsB/h0irDgGqoYGAhOAL0eALUd/oo87TRK2nCUddI3anQKXTTUl1DUVVNXga6nh2mwvb6066L3Bz3btOekU2YXtd5MVNDmuVVtEFm6brAmiUu4OMPODnjv0idx+UGHpQ4q6DEnf928+h0iCg0egN0OgXqW8MUCd4cXm81LgbqHIJlDs9lFd5KK90U1hWQ3GlgLOqlrs/rsQ2q45rFjq57q0yukc6sL3iYc62akBDR0fRNcGmq5oAQYSAyuQEibsPhnjgkMYD8T4ePOTnv/ZrfJrtBTWE4A3ibvRTJ3hx1jdRXddApbOeMoebEruL3Ao3p8vqOVVSR1JuDZkFtbywvgDb0zl0eqmanm+Wct2bFdhmlvP2Po/hH6qIrmiCTdNUAVVElTVeyfTz10My435UGHNE5JGfJIYf1ZiV4kFo8uILBKirD+LwBHC43Dhq6ymurqfAUU9ORS05RSU0NHmQVRW/JCMqOho6354U6DEri04z8+nxWim2fxSx8ZgLEAlJCqoiCzZZUwVZVECDjUVNDD8s8sRRlSmJOs8mwbPHZJ466iO2yI8aDNJU56a8tpGqaicOZx2FVfWcLa8lq7CMBn8IHfAGZQKSik/SQAoBOjEZtdiezqTTS/l0fzmPrFIBNB9CUCcoSYJN0TTBKymgS5TXBXn8pwCTj8lMT1WZmarx+nGR2Rk680+GOFleh9/bgMtZR3V1HYWVLvJKajiTV0pVjRsV8IVEgrLaLApeERSCAAx5/xS2+48zZvlZAmKIQFMTHq+PppAi2DRdFQIyyKIfRImoMz6ePO7lpUyJl08r/PN0iMjTEh9kS6zNEUgp92GvaaDAXstZh4DLG0JoChAMyviDMn5Jxi/K+EISIVnFr0h4JWPZ33a8mNteSCI6vhpZbMDhaqChyYfHHxRsmhYURBECoRDoMvkukVfTFN7IlHk7W+aDXIlVOSHW5ob4qhi2F4v8WNLIiSov5wLgkVVERSUYMqa2LyTjF1UCkkpQ1vGLImJAxCsai2SdKFJQ6aG0ooqK+gBuVxOCTxBsuq4IkqQSFCVCkgyqzg8lXt5J9/H+WViVJ7G+KMSOEoVdJSp77DJxdRrH3Rq5goLDJyMERRqCIt6QbPiHqBAQFSRJJSCrhGSZJklDUs/nIefsNRRXe3DWefE0NjUf21BURFEiGDRWVH9I5Nu8Rt7Pk/lXocbWIpFvKzT2VSr8YJdIcEqk1Svke2TsPhV3SMYTEmkISngDCr6QTFBSCUkqQUnFL2kEJUNLflFF1aGuyUepow6HuxG34DdyVkVRkCQFUVLxBWR0ScEfFPmuuIG1hTIxpSr/rpA46FBIqJHJdGmcqVPJa1Co9MrUBiTcQQlPQKYhoNAUUvCJCn5JJSApBCTZEr8oEVJU/IpKiaOOmnov9Q1+QyOqqiErGqKiIYk6/mAATQ4QalRItPvZU+EnvkrmxxqJRJdIVp1KTr1GQaNChVei2idTE9BwBRTqAzKeoEyjKNMkKvglhaCkNAMZogAeX4DS6npcDQE8jYHmM0aajqLqyLJOSNbwSTJev0woEKCxyU9OdZCEkgAJ1UGSBYWsBo3cRihq0qj0KVT5ZBwBjdqQRn1IRhBVGiWVRlklqOiIikZQ1hAV4ytjJE2n0ummqt6LU/AjNAYEm64jaBqoqo6iaEiKhiirBESVhkAQr9eH0ChSUu3nVGkdGY4mUmt8ZLoC5DWoFDUplHpVKnw6VT6ZWn+IuqCEJ6TQEFINzUgSflXFJ8nUe304XALVdQ3UNwaob/TT5A0ZILoO4TCyrBKSZHxBGcEfxNPgpdETwO32U+ZoIKesnrPlHrLtbn6urCfPXk+B3U2R3cO5qgbOVTVQUilwrkKgtEqguLKe4sp6yhwNlNg9VLkEhKYgjd4QTX6RYFA+X2Dpuo6maaiqiqqqKIqGKKn4QwrekERjIIC70YenMUBjk0S9EKK23our3ovb48Xj8SI0BfD4ROq9IdyNQeoa/Lg8AZxuPzV1PuobRASfguAN0egP4Q1KBEMykqwKNkAxMnpDNM1oSxhQGrKiI6oqTapIkyTiDYUIiDLBkEwoICOGjHghKxqKqqCoEooqEVJFgkqIkBIiKIsEpBB+MYA/FMAXkgiICiHRmK2KoilWo6bZRIKu61bjRdd1QdEQVBVBkzRBlVRBFhVBVTRBUXRBknVB1hAUECQQNF0XUHVB13RB0XRBVDRBUjRBUTVBUlRBlBRBlGQhJGuCJOuCouiCpuqCqqpl/7Eemqor5HnS2Ja/hPezpvCP1PuYlfo3vvo5EnfA0baH9qs+CKZpBIIh7DUuyuw1lNprqHDU4mnwoqoamq5xyn2YVTkv8cKJO3n+TH+eTB7Ao/H9eSr+TnbmrfyfgdiddZzKKaK0yklhuYN6oWVfvabay+6Tu3gzaSJPpPZm9E9XMmnvH1n60wKSanZypuEg35WuZlrCMLb9vPSXgzicdWTkFLX7vya5Dq/spk62s8v1AW+cu53ns29kSd6z/Fi9mZ/L8tpqVFfZeHYxBe7MSwdJy85v8Xd1oJwDFRtZlTeTD88+wcKsMSzMGsv8rL8wNbMnc7LuJN6xg6AcsF6TW1xBkzfQct9P8pDrSkfT1QuDKKrKz8UV1t+V3kKi89/m1YyhvHlyMPOz/ouFZ4fwYe59fJAzjLfO3s66wuep8p7jbF0iUTkzOe76/rzZ6jxUVteGtch06gL2C4PIikJFtcv6e3/ZeuamDOHNU//NivwxfFY8jnXlE/iyYiKflz/Eh4WD2Gv/CL/YQIJjI2+dvJvXTt7FtJS+LPt5OvVBY383KEoUlFaGzSz5wqb5ubC0WSsyG3PfZUbKnXzw8wOsKX6EdWUT+NI+nq8cY1nrGMnikjuJd0Xhld1sr3iTt37+IyuLHmZN0WMszxnPzLSBvJnxMMWNPxv7vUITLrdw8VlzMswnNud+xD+O3cGy3LF8ce5R1pZN4IuKsXzlGM0X1SP4uPJ2jgpraJAcfFb+CJHnbuOz8pF8UT6OL0om8nnRJFblPcrLaXfxxolROHzGd2idq7xIHBEavTQFQwAcLNvMP5Lu5JOcsawpmsRnJROIKnuYtVWjWVP9Vz6q7McRz0pUTSa2Zh6LSgeytOJPfGa/j3UVY1lTMoFPz01kdcEjLM95hNmp/8UHmU+j6MYnlrJyz3UMknHW0IbDW8rLyfexIGs4nxU8zqqi8Xx07gGiKkfyheN+ltnvJEFYGdYOFWlUqjniWcGK8iFElQ1jTek4Pi2awOqCR1iZ9wgfnx3Hs4l9+aHc+BqH2voGRFFqC+JpaEKSjOR2Y84iZqX8majcx1ieN57Xc+/hvXPD+aziAZaX30VGY0yH0/1s00E+KR7KquL7+ezceFbnT2BFzkSW5Uzg7VP38UbKQ3hCdc1aKWoLktHsG06/nbnJ9/H+6VGsyJnIC9l38kreMNaUPsKSkkHsdy26aABMcK3lw4L/5l9FY1mdP56lOeP55Ox4Psh+mOeT7+BAyUZj17O8qiWIKMkUlNoBOFQaw4zkQSw+M5bZp+7in7mPsKnkFVade4DPSsfTJNVeFCSk+lhbPIVl+Q+wMnccS8+OY/GZsXxwZjTTj9/OkqwXACi3O/H5A+dBKhy1lFQac33t2bf5R/KdvJnxFxadnkSyYzuf5j3BssIR/Kt4DBvLp/NF2dOsqXiSNRVPsKbyCeNnxZN8XjaFz4ufJrr4Bf5V8Agr8h5iWc5YPs4ey4enR/P+6YeYnfZn3kh9CAUfqgz2Gtd5kLOFpZTYjUMHH516jmlJA3jjxHCO2XexteBtFpwZyqqC0awo+huLCv7Eu4W38V7x73mvtD/vl/Xl/bJ+vFfye94tuo2F+X/g3dw/szT/b6zIHcMnZ0fz0ZmHWXT6ISKzRvJq5mBeSh5MSeNZyzyyrBggWTlFlNsNssiMKYz9oQe7i/9FmmM/r6bezZKfx7Is5yGW5f+NFYUjWHXuflaXDmN12V+JKhtGVNkwVpX9lZXFw1lRNILl+Q/ySc6DfHRmFIuyRhF5ciRvZ/6NNzPvZ3baIJ5N+AM/1xsfXcg9V47XH2wLMidpFE/9eAcVQg7Lsp7j9fShfHTmIT4+M4rIrKG8ljGAeSf78eaZfszP7sc/z/bln9n9mH+mH29m9eO1jP7MPfF7ZibfxvSE3zP1UD+eiruVxw/cxIT9fRj+764Mje3M6bqjAOQVl+MPhgyQvHPllFQapnkhfgRf5y7haNV3PJvwe945+QDvnnyAf2bcQ0zR22S7fySzbj+Z7n2cdO/jZP1eTtbvI9O9j8y6fWS49pHm3Edq9T6OV+0luXIPRyt2k1C+i/jybzhYupUfSrfjV40wX1zhQNN0A8RR66bEbjjr5p+Xc9IRz9snJvJ88h94O/N+3s64j1dS7mJLXuT/v0e/vT6qa93nnVXXdXLOlRtJi6qSWLmL8Yd682rGvcxLG8qbJ4byRuoQXj56L+UNuRcdoDHk5kDJNvaXbuZA2Rb2l21hX9nX7C3byNaCKJKr4pqnbw3+QLBlQDttxn4dPsh4hseP3sjcjP/m5dRBvJYymNdTBjMtvh8rT865KMja0wsZvqsr4/f3ZNyBnjx88CpGxV3BiAM2bt5iY8PPKwz/KKlsG1lDooTgCRJAYPKR/jyb2pcZaQOZdfyPzDn+J145/l/MSfojU364lW05yzuE2F30FU/80JcZSQN5+fifmH38Tmam3MGM1Dt4LOE6pv90DyHFCGLZ+SXtL3pn88rJCR5hbPy1TEq6jqnJv2XGsduZdfwPzD52By8n/5FZSX9g8sGbeDflGU7VHMUTqKMhVM/Z2hMsSZ/JY3G38I/E25l77I/MOv4HZhy/nRkptzE1+Rbu+beNhMrvjLEKSi+cj0T+8AaPZfTi2eQ/8Gj89fz96C3MSB7AjOTfMzPpNmYn3c7MowN4/IdrmXKoPy8l3MtLP/2Fpw7fxiMHr+HFxH7MTrqNmUm/56XkAbyY3I/pyb/jr/tsRJ542hqnOGydaRdkxv6J/DXBxvflX/Fd0Rru2W3jmYTrmZnUnxlJ/ZhxtB+zjg5g1tH+vJBwM1Pjr+fZ+Ot5PuFmZiX2Y9ZR43kvJfXlpeR+PJ90M3/da2Nm4gME5MZ2c5F2QV5OeYA/7rZxrOYgANE/f8S933ViTFxXZiX1ZfbRvsxK7MusxFuZnXgrs8JkZuKtzEi8lZlHf8espL48Gd+Lu3fbeDVpLA1BY+kvc7T7ZTktQUQlyLQjg/nzv20cyo+zrsdX7OKR/bcybLeNp368hpd+uok5ib9lbuKtzfI75ib+jtmJv2PGT7fwfMJveOj7zty/O4JPs+YjKsYUdTc04Wloav/YRusLz/04lAeTIsgsPENewfnc0is1EH32Qx47MICH913F+O//F+O/t/H4wW7877gIHtnfhXHfd2Hs91cyZl9v3k19lgLPaev15TV1NDR6Oz4/0vrC26ceYVhcL45X/GB4d2Eljf7Q+cJI9pHqiGPVqVeZd+wRZicOZ0bCvbyS9DAfpD3PnnNfUuO3ny9NVI2T+eVI8oVPGrUB2ZsfzX1HehJTtMK6FgyJZOYW0+gXf1EIz8wro9LhvKTn2lrugkMoFOS5n/7C0APXYK8tb3GepMrh5HB8Cmknz5JbXEpBSQVlFbVU2N0UlVWRW1RK1s95/JCQzMkzPyPLMpqm4ff7CQQChEIhJElCURQ0TcPsVOm6fn6tCT+oUOkq4bGE27n/qzv4KeMIwVCQQCBAbV0ttXW1VFRWkJ19lrS0DJKSj5F4NInk5OOcPHmK/Px8amtrcbvd1NTU4HQ6cbvdNDU1WTCyLKOqaguYDmvfgNzE4bIYdpWv4UT5EezuMkQl9B877PT/DQC7cLwx8LR3hQAAAABJRU5ErkJggg==) no-repeat;padding-left:40px} + .browser .browser-firefox{background-position:0 -34px} + .browser .browser-ie{background-position:0 -68px;margin-left:0px} + .browser .browser-360{background-position:0 -170px;margin-left: -27px} + </style> +</head> +<body style="margin-top:50px"> +<h1>请升级您的浏览器,以便我们更好的为您提供服务!</h1> +<p>您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。</p> +<hr> +<h2>请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束</h2> +<p>自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 <a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support">微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明</a> 。</p> +<hr> +<h2>您可以选择更先进的浏览器</h2> +<p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p> +<ul class="browser"> + <li class="browser-chrome"><a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1"> 谷歌浏览器<span>Google Chrome</span></a></li> + <li class="browser-firefox"><a href="https://www.mozilla.org/zh-CN/firefox/new/"> 火狐浏览器<span>Mozilla Firefox</span></a></li> + <li class="browser-ie"><a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie"> IE 11 浏览器<span>Internet Explorer</span></a></li> + <li class="browser-360"><a href="http://se.360.cn/"> 360安全浏览器<span>360 Chrome</span></a></li> + <div class="clean"></div> +</ul> +<hr> +</body> +</html> \ No newline at end of file diff --git a/ruoyi-ui/public/index.html b/ruoyi-ui/public/index.html new file mode 100644 index 0000000..925455c --- /dev/null +++ b/ruoyi-ui/public/index.html @@ -0,0 +1,208 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + <meta name="renderer" content="webkit"> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> + <link rel="icon" href="<%= BASE_URL %>favicon.ico"> + <title><%= webpackConfig.name %></title> + <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> + <style> + html, + body, + #app { + height: 100%; + margin: 0px; + padding: 0px; + } + .chromeframe { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; + } + + #loader-wrapper { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 999999; + } + + #loader { + display: block; + position: relative; + left: 50%; + top: 50%; + width: 150px; + height: 150px; + margin: -75px 0 0 -75px; + border-radius: 50%; + border: 3px solid transparent; + border-top-color: #FFF; + -webkit-animation: spin 2s linear infinite; + -ms-animation: spin 2s linear infinite; + -moz-animation: spin 2s linear infinite; + -o-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; + z-index: 1001; + } + + #loader:before { + content: ""; + position: absolute; + top: 5px; + left: 5px; + right: 5px; + bottom: 5px; + border-radius: 50%; + border: 3px solid transparent; + border-top-color: #FFF; + -webkit-animation: spin 3s linear infinite; + -moz-animation: spin 3s linear infinite; + -o-animation: spin 3s linear infinite; + -ms-animation: spin 3s linear infinite; + animation: spin 3s linear infinite; + } + + #loader:after { + content: ""; + position: absolute; + top: 15px; + left: 15px; + right: 15px; + bottom: 15px; + border-radius: 50%; + border: 3px solid transparent; + border-top-color: #FFF; + -moz-animation: spin 1.5s linear infinite; + -o-animation: spin 1.5s linear infinite; + -ms-animation: spin 1.5s linear infinite; + -webkit-animation: spin 1.5s linear infinite; + animation: spin 1.5s linear infinite; + } + + + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + + #loader-wrapper .loader-section { + position: fixed; + top: 0; + width: 51%; + height: 100%; + background: #7171C6; + z-index: 1000; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + #loader-wrapper .loader-section.section-left { + left: 0; + } + + #loader-wrapper .loader-section.section-right { + right: 0; + } + + + .loaded #loader-wrapper .loader-section.section-left { + -webkit-transform: translateX(-100%); + -ms-transform: translateX(-100%); + transform: translateX(-100%); + -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); + transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); + } + + .loaded #loader-wrapper .loader-section.section-right { + -webkit-transform: translateX(100%); + -ms-transform: translateX(100%); + transform: translateX(100%); + -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); + transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); + } + + .loaded #loader { + opacity: 0; + -webkit-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; + } + + .loaded #loader-wrapper { + visibility: hidden; + -webkit-transform: translateY(-100%); + -ms-transform: translateY(-100%); + transform: translateY(-100%); + -webkit-transition: all 0.3s 1s ease-out; + transition: all 0.3s 1s ease-out; + } + + .no-js #loader-wrapper { + display: none; + } + + .no-js h1 { + color: #222222; + } + + #loader-wrapper .load_title { + font-family: 'Open Sans'; + color: #FFF; + font-size: 19px; + width: 100%; + text-align: center; + z-index: 9999999999999; + position: absolute; + top: 60%; + opacity: 1; + line-height: 30px; + } + + #loader-wrapper .load_title span { + font-weight: normal; + font-style: italic; + font-size: 13px; + color: #FFF; + opacity: 0.5; + } + </style> + </head> + <body> + <div id="app"> + <div id="loader-wrapper"> + <div id="loader"></div> + <div class="loader-section section-left"></div> + <div class="loader-section section-right"></div> + <div class="load_title">正在加载系统资源,请耐心等待</div> + </div> + </div> + </body> +</html> diff --git a/ruoyi-ui/public/robots.txt b/ruoyi-ui/public/robots.txt new file mode 100644 index 0000000..77470cb --- /dev/null +++ b/ruoyi-ui/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/ruoyi-ui/src/App.vue b/ruoyi-ui/src/App.vue new file mode 100644 index 0000000..29de49f --- /dev/null +++ b/ruoyi-ui/src/App.vue @@ -0,0 +1,28 @@ +<template> + <div id="app"> + <router-view /> + <theme-picker /> + </div> +</template> + +<script> +import ThemePicker from "@/components/ThemePicker"; + +export default { + name: "App", + components: { ThemePicker }, + metaInfo() { + return { + title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title, + titleTemplate: title => { + return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE + } + } + } +}; +</script> +<style scoped> +#app .theme-picker { + display: none; +} +</style> diff --git a/ruoyi-ui/src/api/login.js b/ruoyi-ui/src/api/login.js new file mode 100644 index 0000000..649f59c --- /dev/null +++ b/ruoyi-ui/src/api/login.js @@ -0,0 +1,59 @@ +import request from '@/utils/request' + +// 登录方法 +export function login(username, password, code, uuid) { + const data = { + username, + password, + code, + uuid + } + return request({ + url: '/login', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +// 注册方法 +export function register(data) { + return request({ + url: '/register', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +// 获取用户详细信息 +export function getInfo() { + return request({ + url: '/getInfo', + method: 'get' + }) +} + +// 退出方法 +export function logout() { + return request({ + url: '/logout', + method: 'post' + }) +} + +// 获取验证码 +export function getCodeImg() { + return request({ + url: '/captchaImage', + headers: { + isToken: false + }, + method: 'get', + timeout: 20000 + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/menu.js b/ruoyi-ui/src/api/menu.js new file mode 100644 index 0000000..faef101 --- /dev/null +++ b/ruoyi-ui/src/api/menu.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取路由 +export const getRouters = () => { + return request({ + url: '/getRouters', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/cache.js b/ruoyi-ui/src/api/monitor/cache.js new file mode 100644 index 0000000..72c5f6a --- /dev/null +++ b/ruoyi-ui/src/api/monitor/cache.js @@ -0,0 +1,57 @@ +import request from '@/utils/request' + +// 查询缓存详细 +export function getCache() { + return request({ + url: '/monitor/cache', + method: 'get' + }) +} + +// 查询缓存名称列表 +export function listCacheName() { + return request({ + url: '/monitor/cache/getNames', + method: 'get' + }) +} + +// 查询缓存键名列表 +export function listCacheKey(cacheName) { + return request({ + url: '/monitor/cache/getKeys/' + cacheName, + method: 'get' + }) +} + +// 查询缓存内容 +export function getCacheValue(cacheName, cacheKey) { + return request({ + url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, + method: 'get' + }) +} + +// 清理指定名称缓存 +export function clearCacheName(cacheName) { + return request({ + url: '/monitor/cache/clearCacheName/' + cacheName, + method: 'delete' + }) +} + +// 清理指定键名缓存 +export function clearCacheKey(cacheKey) { + return request({ + url: '/monitor/cache/clearCacheKey/' + cacheKey, + method: 'delete' + }) +} + +// 清理全部缓存 +export function clearCacheAll() { + return request({ + url: '/monitor/cache/clearCacheAll', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/job.js b/ruoyi-ui/src/api/monitor/job.js new file mode 100644 index 0000000..3815569 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/job.js @@ -0,0 +1,71 @@ +import request from '@/utils/request' + +// 查询定时任务调度列表 +export function listJob(query) { + return request({ + url: '/monitor/job/list', + method: 'get', + params: query + }) +} + +// 查询定时任务调度详细 +export function getJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'get' + }) +} + +// 新增定时任务调度 +export function addJob(data) { + return request({ + url: '/monitor/job', + method: 'post', + data: data + }) +} + +// 修改定时任务调度 +export function updateJob(data) { + return request({ + url: '/monitor/job', + method: 'put', + data: data + }) +} + +// 删除定时任务调度 +export function delJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'delete' + }) +} + +// 任务状态修改 +export function changeJobStatus(jobId, status) { + const data = { + jobId, + status + } + return request({ + url: '/monitor/job/changeStatus', + method: 'put', + data: data + }) +} + + +// 定时任务立即执行一次 +export function runJob(jobId, jobGroup) { + const data = { + jobId, + jobGroup + } + return request({ + url: '/monitor/job/run', + method: 'put', + data: data + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/jobLog.js b/ruoyi-ui/src/api/monitor/jobLog.js new file mode 100644 index 0000000..6e0be61 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/jobLog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询调度日志列表 +export function listJobLog(query) { + return request({ + url: '/monitor/jobLog/list', + method: 'get', + params: query + }) +} + +// 删除调度日志 +export function delJobLog(jobLogId) { + return request({ + url: '/monitor/jobLog/' + jobLogId, + method: 'delete' + }) +} + +// 清空调度日志 +export function cleanJobLog() { + return request({ + url: '/monitor/jobLog/clean', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/logininfor.js b/ruoyi-ui/src/api/monitor/logininfor.js new file mode 100644 index 0000000..4d112b7 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/logininfor.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +// 查询登录日志列表 +export function list(query) { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params: query + }) +} + +// 删除登录日志 +export function delLogininfor(infoId) { + return request({ + url: '/monitor/logininfor/' + infoId, + method: 'delete' + }) +} + +// 解锁用户登录状态 +export function unlockLogininfor(userName) { + return request({ + url: '/monitor/logininfor/unlock/' + userName, + method: 'get' + }) +} + +// 清空登录日志 +export function cleanLogininfor() { + return request({ + url: '/monitor/logininfor/clean', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/online.js b/ruoyi-ui/src/api/monitor/online.js new file mode 100644 index 0000000..bd22137 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/online.js @@ -0,0 +1,18 @@ +import request from '@/utils/request' + +// 查询在线用户列表 +export function list(query) { + return request({ + url: '/monitor/online/list', + method: 'get', + params: query + }) +} + +// 强退用户 +export function forceLogout(tokenId) { + return request({ + url: '/monitor/online/' + tokenId, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/operlog.js b/ruoyi-ui/src/api/monitor/operlog.js new file mode 100644 index 0000000..a04bca8 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/operlog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询操作日志列表 +export function list(query) { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params: query + }) +} + +// 删除操作日志 +export function delOperlog(operId) { + return request({ + url: '/monitor/operlog/' + operId, + method: 'delete' + }) +} + +// 清空操作日志 +export function cleanOperlog() { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/monitor/server.js b/ruoyi-ui/src/api/monitor/server.js new file mode 100644 index 0000000..e1f9ca2 --- /dev/null +++ b/ruoyi-ui/src/api/monitor/server.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取服务信息 +export function getServer() { + return request({ + url: '/monitor/server', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/config.js b/ruoyi-ui/src/api/system/config.js new file mode 100644 index 0000000..a404d82 --- /dev/null +++ b/ruoyi-ui/src/api/system/config.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询参数列表 +export function listConfig(query) { + return request({ + url: '/system/config/list', + method: 'get', + params: query + }) +} + +// 查询参数详细 +export function getConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'get' + }) +} + +// 根据参数键名查询参数值 +export function getConfigKey(configKey) { + return request({ + url: '/system/config/configKey/' + configKey, + method: 'get' + }) +} + +// 新增参数配置 +export function addConfig(data) { + return request({ + url: '/system/config', + method: 'post', + data: data + }) +} + +// 修改参数配置 +export function updateConfig(data) { + return request({ + url: '/system/config', + method: 'put', + data: data + }) +} + +// 删除参数配置 +export function delConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'delete' + }) +} + +// 刷新参数缓存 +export function refreshCache() { + return request({ + url: '/system/config/refreshCache', + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/dept.js b/ruoyi-ui/src/api/system/dept.js new file mode 100644 index 0000000..fc943cd --- /dev/null +++ b/ruoyi-ui/src/api/system/dept.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询部门列表 +export function listDept(query) { + return request({ + url: '/system/dept/list', + method: 'get', + params: query + }) +} + +// 查询部门列表(排除节点) +export function listDeptExcludeChild(deptId) { + return request({ + url: '/system/dept/list/exclude/' + deptId, + method: 'get' + }) +} + +// 查询部门详细 +export function getDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'get' + }) +} + +// 新增部门 +export function addDept(data) { + return request({ + url: '/system/dept', + method: 'post', + data: data + }) +} + +// 修改部门 +export function updateDept(data) { + return request({ + url: '/system/dept', + method: 'put', + data: data + }) +} + +// 删除部门 +export function delDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/dict/data.js b/ruoyi-ui/src/api/system/dict/data.js new file mode 100644 index 0000000..6c9eb79 --- /dev/null +++ b/ruoyi-ui/src/api/system/dict/data.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询字典数据列表 +export function listData(query) { + return request({ + url: '/system/dict/data/list', + method: 'get', + params: query + }) +} + +// 查询字典数据详细 +export function getData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'get' + }) +} + +// 根据字典类型查询字典数据信息 +export function getDicts(dictType) { + return request({ + url: '/system/dict/data/type/' + dictType, + method: 'get' + }) +} + +// 新增字典数据 +export function addData(data) { + return request({ + url: '/system/dict/data', + method: 'post', + data: data + }) +} + +// 修改字典数据 +export function updateData(data) { + return request({ + url: '/system/dict/data', + method: 'put', + data: data + }) +} + +// 删除字典数据 +export function delData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/dict/type.js b/ruoyi-ui/src/api/system/dict/type.js new file mode 100644 index 0000000..a7a6e01 --- /dev/null +++ b/ruoyi-ui/src/api/system/dict/type.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询字典类型列表 +export function listType(query) { + return request({ + url: '/system/dict/type/list', + method: 'get', + params: query + }) +} + +// 查询字典类型详细 +export function getType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'get' + }) +} + +// 新增字典类型 +export function addType(data) { + return request({ + url: '/system/dict/type', + method: 'post', + data: data + }) +} + +// 修改字典类型 +export function updateType(data) { + return request({ + url: '/system/dict/type', + method: 'put', + data: data + }) +} + +// 删除字典类型 +export function delType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'delete' + }) +} + +// 刷新字典缓存 +export function refreshCache() { + return request({ + url: '/system/dict/type/refreshCache', + method: 'delete' + }) +} + +// 获取字典选择框列表 +export function optionselect() { + return request({ + url: '/system/dict/type/optionselect', + method: 'get' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/menu.js b/ruoyi-ui/src/api/system/menu.js new file mode 100644 index 0000000..f6415c6 --- /dev/null +++ b/ruoyi-ui/src/api/system/menu.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询菜单列表 +export function listMenu(query) { + return request({ + url: '/system/menu/list', + method: 'get', + params: query + }) +} + +// 查询菜单详细 +export function getMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'get' + }) +} + +// 查询菜单下拉树结构 +export function treeselect() { + return request({ + url: '/system/menu/treeselect', + method: 'get' + }) +} + +// 根据角色ID查询菜单下拉树结构 +export function roleMenuTreeselect(roleId) { + return request({ + url: '/system/menu/roleMenuTreeselect/' + roleId, + method: 'get' + }) +} + +// 新增菜单 +export function addMenu(data) { + return request({ + url: '/system/menu', + method: 'post', + data: data + }) +} + +// 修改菜单 +export function updateMenu(data) { + return request({ + url: '/system/menu', + method: 'put', + data: data + }) +} + +// 删除菜单 +export function delMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/notice.js b/ruoyi-ui/src/api/system/notice.js new file mode 100644 index 0000000..c274ea5 --- /dev/null +++ b/ruoyi-ui/src/api/system/notice.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询公告列表 +export function listNotice(query) { + return request({ + url: '/system/notice/list', + method: 'get', + params: query + }) +} + +// 查询公告详细 +export function getNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'get' + }) +} + +// 新增公告 +export function addNotice(data) { + return request({ + url: '/system/notice', + method: 'post', + data: data + }) +} + +// 修改公告 +export function updateNotice(data) { + return request({ + url: '/system/notice', + method: 'put', + data: data + }) +} + +// 删除公告 +export function delNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/post.js b/ruoyi-ui/src/api/system/post.js new file mode 100644 index 0000000..1a8e9ca --- /dev/null +++ b/ruoyi-ui/src/api/system/post.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询岗位列表 +export function listPost(query) { + return request({ + url: '/system/post/list', + method: 'get', + params: query + }) +} + +// 查询岗位详细 +export function getPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'get' + }) +} + +// 新增岗位 +export function addPost(data) { + return request({ + url: '/system/post', + method: 'post', + data: data + }) +} + +// 修改岗位 +export function updatePost(data) { + return request({ + url: '/system/post', + method: 'put', + data: data + }) +} + +// 删除岗位 +export function delPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/role.js b/ruoyi-ui/src/api/system/role.js new file mode 100644 index 0000000..f13e6f4 --- /dev/null +++ b/ruoyi-ui/src/api/system/role.js @@ -0,0 +1,119 @@ +import request from '@/utils/request' + +// 查询角色列表 +export function listRole(query) { + return request({ + url: '/system/role/list', + method: 'get', + params: query + }) +} + +// 查询角色详细 +export function getRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'get' + }) +} + +// 新增角色 +export function addRole(data) { + return request({ + url: '/system/role', + method: 'post', + data: data + }) +} + +// 修改角色 +export function updateRole(data) { + return request({ + url: '/system/role', + method: 'put', + data: data + }) +} + +// 角色数据权限 +export function dataScope(data) { + return request({ + url: '/system/role/dataScope', + method: 'put', + data: data + }) +} + +// 角色状态修改 +export function changeRoleStatus(roleId, status) { + const data = { + roleId, + status + } + return request({ + url: '/system/role/changeStatus', + method: 'put', + data: data + }) +} + +// 删除角色 +export function delRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'delete' + }) +} + +// 查询角色已授权用户列表 +export function allocatedUserList(query) { + return request({ + url: '/system/role/authUser/allocatedList', + method: 'get', + params: query + }) +} + +// 查询角色未授权用户列表 +export function unallocatedUserList(query) { + return request({ + url: '/system/role/authUser/unallocatedList', + method: 'get', + params: query + }) +} + +// 取消用户授权角色 +export function authUserCancel(data) { + return request({ + url: '/system/role/authUser/cancel', + method: 'put', + data: data + }) +} + +// 批量取消用户授权角色 +export function authUserCancelAll(data) { + return request({ + url: '/system/role/authUser/cancelAll', + method: 'put', + params: data + }) +} + +// 授权用户选择 +export function authUserSelectAll(data) { + return request({ + url: '/system/role/authUser/selectAll', + method: 'put', + params: data + }) +} + +// 根据角色ID查询部门树结构 +export function deptTreeSelect(roleId) { + return request({ + url: '/system/role/deptTree/' + roleId, + method: 'get' + }) +} diff --git a/ruoyi-ui/src/api/system/user.js b/ruoyi-ui/src/api/system/user.js new file mode 100644 index 0000000..f2f76ef --- /dev/null +++ b/ruoyi-ui/src/api/system/user.js @@ -0,0 +1,135 @@ +import request from '@/utils/request' +import { parseStrEmpty } from "@/utils/ruoyi"; + +// 查询用户列表 +export function listUser(query) { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }) +} + +// 查询用户详细 +export function getUser(userId) { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }) +} + +// 新增用户 +export function addUser(data) { + return request({ + url: '/system/user', + method: 'post', + data: data + }) +} + +// 修改用户 +export function updateUser(data) { + return request({ + url: '/system/user', + method: 'put', + data: data + }) +} + +// 删除用户 +export function delUser(userId) { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }) +} + +// 用户密码重置 +export function resetUserPwd(userId, password) { + const data = { + userId, + password + } + return request({ + url: '/system/user/resetPwd', + method: 'put', + data: data + }) +} + +// 用户状态修改 +export function changeUserStatus(userId, status) { + const data = { + userId, + status + } + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }) +} + +// 查询用户个人信息 +export function getUserProfile() { + return request({ + url: '/system/user/profile', + method: 'get' + }) +} + +// 修改用户个人信息 +export function updateUserProfile(data) { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }) +} + +// 用户密码重置 +export function updateUserPwd(oldPassword, newPassword) { + const data = { + oldPassword, + newPassword + } + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + params: data + }) +} + +// 用户头像上传 +export function uploadAvatar(data) { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + data: data + }) +} + +// 查询授权角色 +export function getAuthRole(userId) { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }) +} + +// 保存授权角色 +export function updateAuthRole(data) { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }) +} + +// 查询部门下拉树结构 +export function deptTreeSelect() { + return request({ + url: '/system/user/deptTree', + method: 'get' + }) +} diff --git a/ruoyi-ui/src/api/tool/gen.js b/ruoyi-ui/src/api/tool/gen.js new file mode 100644 index 0000000..4506927 --- /dev/null +++ b/ruoyi-ui/src/api/tool/gen.js @@ -0,0 +1,76 @@ +import request from '@/utils/request' + +// 查询生成表数据 +export function listTable(query) { + return request({ + url: '/tool/gen/list', + method: 'get', + params: query + }) +} +// 查询db数据库列表 +export function listDbTable(query) { + return request({ + url: '/tool/gen/db/list', + method: 'get', + params: query + }) +} + +// 查询表详细信息 +export function getGenTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'get' + }) +} + +// 修改代码生成信息 +export function updateGenTable(data) { + return request({ + url: '/tool/gen', + method: 'put', + data: data + }) +} + +// 导入表 +export function importTable(data) { + return request({ + url: '/tool/gen/importTable', + method: 'post', + params: data + }) +} + +// 预览生成代码 +export function previewTable(tableId) { + return request({ + url: '/tool/gen/preview/' + tableId, + method: 'get' + }) +} + +// 删除表数据 +export function delTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'delete' + }) +} + +// 生成代码(自定义路径) +export function genCode(tableName) { + return request({ + url: '/tool/gen/genCode/' + tableName, + method: 'get' + }) +} + +// 同步数据库 +export function synchDb(tableName) { + return request({ + url: '/tool/gen/synchDb/' + tableName, + method: 'get' + }) +} diff --git a/ruoyi-ui/src/api/type/type.js b/ruoyi-ui/src/api/type/type.js new file mode 100644 index 0000000..b9eeeaa --- /dev/null +++ b/ruoyi-ui/src/api/type/type.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询培训类别列表 +export function listType(query) { + return request({ + url: '/type/list', + method: 'get', + params: query + }) +} + +// 查询培训类别详细 +export function getType(id) { + return request({ + url: '/type/' + id, + method: 'get' + }) +} + +// 新增培训类别 +export function addType(data) { + return request({ + url: '/type', + method: 'post', + data: data + }) +} + +// 修改培训类别 +export function updateType(data) { + return request({ + url: '/type', + method: 'put', + data: data + }) +} + +// 删除培训类别 +export function delType(id) { + return request({ + url: '/type/' + id, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/assets/401_images/401.gif b/ruoyi-ui/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 --- /dev/null +++ b/ruoyi-ui/src/assets/401_images/401.gif Binary files differ diff --git a/ruoyi-ui/src/assets/404_images/404.png b/ruoyi-ui/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 --- /dev/null +++ b/ruoyi-ui/src/assets/404_images/404.png Binary files differ diff --git a/ruoyi-ui/src/assets/404_images/404_cloud.png b/ruoyi-ui/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 --- /dev/null +++ b/ruoyi-ui/src/assets/404_images/404_cloud.png Binary files differ diff --git a/ruoyi-ui/src/assets/icons/index.js b/ruoyi-ui/src/assets/icons/index.js new file mode 100644 index 0000000..2c6b309 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/index.js @@ -0,0 +1,9 @@ +import Vue from 'vue' +import SvgIcon from '@/components/SvgIcon'// svg component + +// register globally +Vue.component('svg-icon', SvgIcon) + +const req = require.context('./svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys().map(requireContext) +requireAll(req) diff --git a/ruoyi-ui/src/assets/icons/svg/404.svg b/ruoyi-ui/src/assets/icons/svg/404.svg new file mode 100644 index 0000000..6df5019 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/404.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/bug.svg b/ruoyi-ui/src/assets/icons/svg/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/bug.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/build.svg b/ruoyi-ui/src/assets/icons/svg/build.svg new file mode 100644 index 0000000..97c4688 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/build.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899741379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2054" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 591.424V368.96c0-0.288 0.16-0.512 0.16-0.768S960 367.68 960 367.424V192a32 32 0 0 0-32-32H96a32 32 0 0 0-32 32v175.424c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768v222.464c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768V864a32 32 0 0 0 32 32h832a32 32 0 0 0 32-32v-271.04c0-0.288 0.16-0.512 0.16-0.768S960 591.68 960 591.424z m-560-31.232v-160H608v160h-208z m208 64V832h-208v-207.808H608z m-480-224h208v160H128v-160z m544 0h224v160h-224v-160zM896 224v112.192H128V224h768zM128 624.192h208V832H128v-207.808zM672 832v-207.808h224V832h-224z" p-id="2055"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/button.svg b/ruoyi-ui/src/assets/icons/svg/button.svg new file mode 100644 index 0000000..904fddc --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/button.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588670460195" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1314" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M230.4 307.712c13.824 0 25.088-11.264 25.088-25.088 0-100.352 81.92-182.272 182.272-182.272s182.272 81.408 182.272 182.272c0 13.824 11.264 25.088 25.088 25.088s25.088-11.264 24.576-25.088c0-127.488-103.936-231.936-231.936-231.936S205.824 154.624 205.824 282.624c-0.512 14.336 10.752 25.088 24.576 25.088z m564.736 234.496c-11.264 0-21.504 2.048-31.232 6.144 0-44.544-40.448-81.92-88.064-81.92-14.848 0-28.16 3.584-39.936 10.24-13.824-28.16-44.544-48.128-78.848-48.128-12.288 0-24.576 2.56-35.328 7.68V284.16c0-45.568-37.888-81.92-84.48-81.92s-84.48 36.864-84.48 81.92v348.672l-69.12-112.64c-18.432-28.16-58.368-36.864-91.136-19.968-26.624 14.336-46.592 47.104-30.208 88.064 3.072 8.192 76.8 205.312 171.52 311.296 0 0 28.16 24.576 43.008 58.88 4.096 9.728 13.312 15.36 22.528 15.36 3.072 0 6.656-0.512 9.728-2.048 12.288-5.12 18.432-19.968 12.8-32.256-19.456-44.544-53.76-74.752-53.76-74.752C281.6 768 209.408 573.44 208.384 570.88c-5.12-12.8-2.56-20.992 7.168-26.112 9.216-4.608 21.504-4.608 26.112 2.56l113.152 184.32c4.096 8.704 12.8 14.336 22.528 14.336 13.824 0 25.088-10.752 25.088-25.088V284.16c0-17.92 15.36-32.256 34.816-32.256s34.816 14.336 34.816 32.256v284.16c0 13.824 10.24 25.088 24.576 25.088 13.824 0 25.088-11.264 25.088-25.088v-57.344c0-17.92 15.36-32.768 34.816-32.768 19.968 0 37.376 15.36 37.376 32.768v95.232c0 7.168 3.072 13.312 7.68 17.92 4.608 4.608 10.752 7.168 17.92 7.168 13.824 0 24.576-11.264 24.576-25.088V547.84c0-18.432 13.824-32.256 32.256-32.256 20.48 0 38.912 15.36 38.912 32.256v95.232c0 13.824 11.264 25.088 25.088 25.088s24.576-11.264 25.088-25.088v-18.944c0-18.944 12.8-32.256 30.72-32.256 18.432 0 22.528 18.944 22.528 31.744 0 1.024-11.776 99.84-50.688 173.056-30.72 58.368-45.056 112.128-51.2 146.944-2.56 13.312 6.656 26.112 19.968 28.672 1.536 0 3.072 0.512 4.608 0.512 11.776 0 22.016-8.192 24.064-20.48 5.632-31.232 18.432-79.36 46.08-132.608 43.52-81.92 55.808-186.88 56.32-193.536-0.512-50.688-29.696-83.968-72.704-83.968z"></path></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/cascader.svg b/ruoyi-ui/src/assets/icons/svg/cascader.svg new file mode 100644 index 0000000..e256024 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/cascader.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/chart.svg b/ruoyi-ui/src/assets/icons/svg/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/chart.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/checkbox.svg b/ruoyi-ui/src/assets/icons/svg/checkbox.svg new file mode 100644 index 0000000..013fd3a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/checkbox.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575982282951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="902" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M828.40625 90.125H195.59375C137.375 90.125 90.125 137.375 90.125 195.59375v632.8125c0 58.21875 47.25 105.46875 105.46875 105.46875h632.8125c58.21875 0 105.46875-47.25 105.46875-105.46875V195.59375c0-58.21875-47.25-105.46875-105.46875-105.46875z m52.734375 738.28125c0 29.16-23.57015625 52.734375-52.734375 52.734375H195.59375c-29.109375 0-52.734375-23.574375-52.734375-52.734375V195.59375c0-29.109375 23.625-52.734375 52.734375-52.734375h632.8125c29.16 0 52.734375 23.625 52.734375 52.734375v632.8125z" p-id="903"></path><path d="M421.52890625 709.55984375a36.28125 36.28125 0 0 1-27.55265625-12.66890625L205.17453125 476.613125a36.28546875 36.28546875 0 0 1 55.10109375-47.22890625l164.986875 192.4846875 342.16171875-298.48078125a36.2896875 36.2896875 0 0 1 47.70984375 54.68765625L445.3859375 700.6203125a36.3234375 36.3234375 0 0 1-23.85703125 8.93953125z" p-id="904"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/clipboard.svg b/ruoyi-ui/src/assets/icons/svg/clipboard.svg new file mode 100644 index 0000000..90923ff --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/clipboard.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/code.svg b/ruoyi-ui/src/assets/icons/svg/code.svg new file mode 100644 index 0000000..5f9c5ab --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/code.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1546567861908" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2422" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M318.577778 819.2L17.066667 512l301.511111-307.2 45.511111 45.511111L96.711111 512l267.377778 261.688889zM705.422222 819.2l-45.511111-45.511111L927.288889 512l-267.377778-261.688889 45.511111-45.511111L1006.933333 512zM540.785778 221.866667l55.751111 11.150222L483.157333 802.133333l-55.751111-11.093333z" p-id="2423"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/color.svg b/ruoyi-ui/src/assets/icons/svg/color.svg new file mode 100644 index 0000000..44a81aa --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/color.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577252187056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M747.59340925 691.12859384c11.51396329 0.25305413 22.43746719-0.21087818 40.74171707-1.51832482 29.35428085-2.10878421 35.84933734-2.36183835 46.47761114-0.8856895 24.71495444 3.37405491 41.12129828 21.76265671 32.47528161 47.95376084-85.57447632 258.19957947-442.00123984 249.76444099-628.67084683 50.73735554-153.47733892-159.33976008-153.09775772-414.41833795 0.92786545-573.42069196 159.71934128-162.67163983 424.03439521-166.59397897 565.78689185 0.63263534 80.38686649 94.81095318 108.34934958 169.16669549 89.11723508 230.57450162-15.01454608 47.99593598-50.61082928 77.68762207-119.77896259 114.63352789-4.89237973 2.65706845-29.35428085 15.52065436-35.84933652 19.02123633-46.94154346 25.30541465-63.51659033 41.20565021-62.20914449 58.45550757 2.95229856 39.13904114 24.16667102 52.7196135 70.98168823 53.81618115z m44.41100207 50.10472101c-19.82257471 1.43397372-32.05352527 1.940082-45.63409763 1.6448519-70.34905207-1.60267593-115.98314969-30.91478165-121.38163769-101.64341492-3.45840683-46.05585397 24.7571304-73.13264758 89.24376132-107.96976837 6.7902866-3.66928501 31.37871396-16.57504688 36.06021551-19.06341229 57.69634516-30.83042972 85.15271997-53.73183005 94.76877722-84.47790866 12.77923398-40.78389304-9.10994898-98.94417051-79.24812286-181.6507002-121.17075953-142.97559219-350.14258521-139.60153647-489.2380134 2.06660824-134.49827774 138.84237405-134.79350784 362.12048163-0.42175717 501.637667 158.53842169 168.99799328 451.9968783 181.18676788 534.57688175-11.80919339-4.68150156 0.2952301-10.71262573 0.67481131-18.72600705 1.26527069z" p-id="2509"></path><path d="M346.03865637 637.18588562a78.82636652 78.82636652 0 0 0 78.32025825-79.29029883c0-43.69401562-35.005823-79.29029883-78.32025825-79.29029882a78.82636652 78.82636652 0 0 0-78.36243338 79.29029882c0 43.69401562 35.005823 79.29029883 78.36243338 79.29029883z m0-51.7495729a27.07679361 27.07679361 0 0 1-26.5706845-27.54072593c0-15.30977536 11.97789643-27.54072593 26.5706845-27.54072592 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072592a27.07679361 27.07679361 0 0 1-26.57068533 27.54072593zM475.7289063 807.11174353a78.82636652 78.82636652 0 0 0 78.3624334-79.29029882c0-43.69401562-34.96364785-79.29029883-78.32025825-79.29029883a78.82636652 78.82636652 0 0 0-78.32025742 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029882z m0-51.74957208a27.07679361 27.07679361 0 0 1-26.57068532-27.54072674c0-15.30977536 12.06224753-27.54072593 26.57068532-27.54072593 14.59278892 0 26.57068533 12.23095057 26.57068453 27.54072593a27.07679361 27.07679361 0 0 1-26.57068453 27.54072674zM601.24376214 377.21492718a78.82636652 78.82636652 0 0 0 78.32025742-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025742-79.29029882a78.82636652 78.82636652 0 0 0-78.32025823 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025824 79.29029883z m1e-8-51.74957208a27.07679361 27.07679361 0 0 1-26.57068534-27.54072675c0-15.30977536 11.97789643-27.54072593 26.57068534-27.54072591 14.55061295 0 26.57068533 12.23095057 26.57068451 27.54072592a27.07679361 27.07679361 0 0 1-26.57068451 27.54072674zM378.80916809 433.85687983a78.82636652 78.82636652 0 0 0 78.32025824-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025824-79.29029802a78.82636652 78.82636652 0 0 0-78.32025742 79.29029802c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029883z m0-51.74957209a27.07679361 27.07679361 0 0 1-26.57068451-27.54072674c0-15.30977536 11.97789643-27.54072593 26.57068451-27.54072593 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072593a27.07679361 27.07679361 0 0 1-26.57068533 27.54072674z" p-id="2510"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/component.svg b/ruoyi-ui/src/assets/icons/svg/component.svg new file mode 100644 index 0000000..29c3458 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/component.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575804206892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3145" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M826.56 470.016c-32.896 0-64.384 12.288-89.984 35.52l0-104.96c0-62.208-50.496-112.832-112.64-113.088L623.936 287.04 519.552 287.104C541.824 262.72 554.56 230.72 554.56 197.12c0-73.536-59.904-133.44-133.504-133.44-73.472 0-133.376 59.904-133.376 133.44 0 32.896 12.224 64.256 35.52 89.984L175.232 287.104l0 0.576C113.728 288.704 64 338.88 64 400.576l0.32 0 0.32 116.48C60.864 544.896 70.592 577.728 100.8 588.48c12.736 4.608 37.632 7.488 60.864-25.28 12.992-18.368 34.24-29.248 56.64-29.248 38.336 0 69.504 31.104 69.504 69.312 0 38.4-31.168 69.504-69.504 69.504-22.656 0-44.032-11.264-57.344-30.4C138.688 610.112 112.576 615.36 102.464 619.136c-29.824 10.752-39.104 43.776-38.144 67.392l0 160.384L64 846.912C64 909.248 114.752 960 177.216 960l446.272 0c62.4 0 113.152-50.752 113.152-113.152l0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.536 0 133.44-59.904 133.44-133.504C960 529.92 900.096 470.016 826.56 470.016zM826.56 672.896c-22.72 0-44.032-11.264-57.344-30.4-22.272-32.384-48.448-27.136-58.56-23.36-29.824 10.752-39.04 43.776-38.08 67.392l0 160.384c0 27.136-22.016 49.152-49.152 49.152L177.216 896.064C150.08 896 128 873.984 128 846.848l0.32 0 0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.6 0 133.504-59.904 133.504-133.504 0-73.472-59.904-133.376-133.504-133.376-32.896 0-64.32 12.288-89.984 35.52l0-104.96L128 400.512c0-27.072 22.08-49.152 49.216-49.152L177.216 351.04 334.656 350.72c3.776 0.512 7.616 0.832 11.52 0.832 24.896 0 50.752-10.816 60.032-37.056 4.544-12.736 7.424-37.568-25.344-60.736C362.624 240.768 351.68 219.52 351.68 197.12c0-38.272 31.104-69.44 69.376-69.44 38.336 0 69.504 31.168 69.504 69.44 0 22.72-11.264 44.032-30.528 57.472C427.968 276.736 433.088 302.784 436.8 313.024c10.752 29.888 43.072 39.232 67.392 38.08l119.232 0 0 0.384c27.136 0 49.152 22.08 49.152 49.152l0.256 116.48c-3.776 27.84 6.016 60.736 36.224 71.488 12.736 4.608 37.632 7.488 60.8-25.28 13.056-18.368 34.24-29.248 56.704-29.248C864.832 534.016 896 565.12 896 603.392 896 641.728 864.832 672.896 826.56 672.896z" p-id="3146"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/dashboard.svg b/ruoyi-ui/src/assets/icons/svg/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/dashboard.svg @@ -0,0 +1 @@ +<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/date-range.svg b/ruoyi-ui/src/assets/icons/svg/date-range.svg new file mode 100644 index 0000000..fda571e --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/date-range.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774833889" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.466667 192.853333h-100.693334V119.466667c0-10.24-6.826667-17.066667-17.066666-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H303.786667V119.466667c0-10.24-6.826667-17.066667-17.066667-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H168.96c-46.08 0-85.333333 37.546667-85.333333 85.333334V836.266667c0 46.08 37.546667 85.333333 85.333333 85.333333H887.466667c46.08 0 85.333333-37.546667 85.333333-85.333333V278.186667c0-47.786667-37.546667-85.333333-85.333333-85.333334z m-718.506667 34.133334h100.693333v66.56c0 10.24 6.826667 17.066667 17.066667 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56h450.56v66.56c0 10.24 6.826667 17.066667 17.066666 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56H887.466667c27.306667 0 51.2 22.186667 51.2 51.2v88.746666H117.76v-88.746666c0-29.013333 22.186667-51.2 51.2-51.2zM887.466667 887.466667H168.96c-27.306667 0-51.2-22.186667-51.2-51.2V401.066667H938.666667V836.266667c0 27.306667-22.186667 51.2-51.2 51.2z" p-id="1377"></path><path d="M858.453333 493.226667H327.68c-10.24 0-17.066667 6.826667-17.066667 17.066666v114.346667h-116.053333c-10.24 0-17.066667 6.826667-17.066667 17.066667v133.12c0 10.24 6.826667 17.066667 17.066667 17.066666H460.8c10.24 0 17.066667-6.826667 17.066667-17.066666v-114.346667h380.586666c10.24 0 17.066667-6.826667 17.066667-17.066667v-133.12c0-10.24-6.826667-17.066667-17.066667-17.066666z m-413.013333 34.133333v97.28h-98.986667v-97.28h98.986667z m-230.4 131.413333h98.986667v98.986667h-98.986667v-98.986667z m131.413333 97.28v-97.28h98.986667v97.28h-98.986667z m133.12-228.693333h97.28v98.986667h-97.28v-98.986667z m131.413334 0h98.986666v98.986667h-98.986666v-98.986667z m230.4 97.28h-98.986667v-98.986667h98.986667v98.986667z" p-id="1378"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/date.svg b/ruoyi-ui/src/assets/icons/svg/date.svg new file mode 100644 index 0000000..52dc73e --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/date.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577186573535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1068" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M479.85714249 608.42857168h64.28571502c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285751s-12.85714249-32.14285751-32.14285664-32.14285664h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285662s12.85714249 32.14285751 32.14285664 32.14285753z m-2e-8 122.14285665h64.28571504c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285665s-12.85714249-32.14285751-32.14285664-32.14285751h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285751s12.85714249 32.14285751 32.14285664 32.14285664z m353.57142921-559.28571416h-128.57142921v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285664-32.14285753s-32.14285751 12.85714249-32.14285751 32.14285753v32.14285664h-257.14285665v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285752-32.14285753s-32.14285751 12.85714249-32.14285664 32.14285753v32.14285664h-128.57142919c-70.71428585 0-128.57142832 57.85714249-128.57142832 122.14285751v501.42857081c0 70.71428585 57.85714249 128.57142832 128.57142832 122.14285751h642.85714335c70.71428585 0 128.57142832-57.85714249 128.57142833-122.14285751v-501.42857081c0-70.71428585-57.85714249-122.14285753-128.57142833-122.14285751z m64.28571415 623.57142832c0 32.14285751-32.14285751 64.28571415-64.28571416 64.28571504h-642.85714335c-32.14285751 0-64.28571415-25.71428583-64.28571417-64.28571504v-372.85714249h771.42857168v372.85714249z m0-437.14285664h-771.42857168v-64.28571417c0-32.14285751 32.14285751-64.28571415 64.28571417-64.28571415h128.57142919v32.14285664c0 19.28571417 12.85714249 32.14285751 32.14285664 32.14285751s32.14285751-12.85714249 32.14285753-32.14285751v-32.14285664h257.14285665v32.14285664c0 19.28571417 12.85714249 32.14285751 32.1428575 32.14285751s32.14285751-12.85714249 32.14285664-32.14285751v-32.14285664h128.57142921c32.14285751 0 64.28571415 25.71428583 64.28571415 64.28571415v64.28571417z m-610.71428583 372.85714247h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285664s-12.85714249-32.14285751-32.14285753-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285751s12.85714249 32.14285751 32.14285751 32.14285665z m385.71428583-122.14285664h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285751s-12.85714249-32.14285751-32.14285751-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285664s12.85714249 32.14285751 32.14285753 32.14285751z m-385.71428583 0h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285751s-12.85714249-32.14285751-32.14285753-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285664s12.85714249 32.14285751 32.14285751 32.14285751z m385.71428583 122.14285665h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285665s-12.85714249-32.14285751-32.14285751-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285751s12.85714249 32.14285751 32.14285753 32.14285665z" p-id="1069"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/dict.svg b/ruoyi-ui/src/assets/icons/svg/dict.svg new file mode 100644 index 0000000..4849377 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/dict.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035680909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3601" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1002.0848 744.672l-33.568 10.368c0.96 7.264 2.144 14.304 2.144 21.76 0 7.328-1.184 14.432-2.368 21.568l33.792 10.56c7.936 2.24 14.496 7.616 18.336 14.752 3.84 7.328 4.672 15.808 1.952 23.552-5.376 16-23.168 24.672-39.936 19.68l-34.176-10.624c-7.136 12.8-15.776 24.672-26.208 35.2l20.8 27.488a28.96 28.96 0 0 1 5.824 22.816 29.696 29.696 0 0 1-12.704 19.616 32.544 32.544 0 0 1-44.416-6.752l-20.8-27.552c-13.696 6.56-28.192 11.2-43.008 13.888v33.632c0 16.736-14.112 30.432-31.648 30.432-17.6 0-31.872-13.696-31.872-30.432v-33.632a167.616 167.616 0 0 1-42.88-13.888l-20.928 27.552c-10.72 13.76-30.08 16.64-44.288 6.752a29.632 29.632 0 0 1-12.704-19.616 29.28 29.28 0 0 1 5.696-22.816l20.896-27.808a166.72 166.72 0 0 1-27.008-34.688l-33.376 10.432c-16.8 5.184-34.56-3.552-39.936-19.616a29.824 29.824 0 0 1 20.224-38.24l33.472-10.432c-0.8-7.264-2.016-14.304-2.016-21.824 0-7.36 1.184-14.496 2.304-21.632l-33.792-10.368c-16.672-5.376-25.632-22.496-20.224-38.432 5.376-16 23.136-24.672 39.936-19.68l34.016 10.752c7.328-12.672 15.84-24.8 26.336-35.328l-20.8-27.552a29.44 29.44 0 0 1 6.944-42.432 32.704 32.704 0 0 1 44.384 6.752l20.832 27.616c13.696-6.432 28.224-11.2 43.104-13.952v-33.568c0-16.736 14.048-30.432 31.648-30.432 17.536 0 31.808 13.568 31.808 30.432v33.504c15.072 2.688 29.344 7.808 42.848 14.016l20.992-27.616a32.48 32.48 0 0 1 44.224-6.752 29.568 29.568 0 0 1 7.136 42.432l-21.024 27.808c10.432 10.432 19.872 21.888 27.04 34.752l33.376-10.432c16.768-5.12 34.56 3.68 39.936 19.68 5.536 15.936-3.712 33.056-20.32 38.304z m-206.016-74.432c-61.344 0-111.136 47.808-111.136 106.56 0 58.88 49.792 106.496 111.136 106.496 61.312 0 111.104-47.616 111.104-106.496 0-58.752-49.792-106.56-111.104-106.56z" p-id="3602"></path><path d="M802.7888 57.152h-76.448c0-22.08-21.024-38.24-42.848-38.24H39.3968a39.68 39.68 0 0 0-39.36 40.032v795.616s41.888 120.192 110.752 120.192H673.2848a227.488 227.488 0 0 1-107.04-97.44H117.6368s-40.608-13.696-40.608-41.248l470.304-0.256 1.664 3.36a227.68 227.68 0 0 1-12.64-73.632c0-60.576 24-118.624 66.88-161.44a228.352 228.352 0 0 1 123.552-63.392l-3.2 0.288 2.144-424.672h38.208l0.576 421.024c27.04 0 52.672 4.8 76.64 13.344V101.536c0.032 0-6.304-44.384-38.368-44.384zM149.7648 514.336H72.3888v-77.408H149.7648v77.408z m0-144.32H72.3888v-77.44H149.7648v77.44z m0-137.248H72.3888v-77.44H149.7648v77.44z m501.856 281.568H206.0848v-77.408h445.536v77.408z m0-144.32H206.0848v-77.44h445.536v77.44z m0-137.248H206.0848v-77.44h445.536v77.44z" p-id="3603"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/documentation.svg b/ruoyi-ui/src/assets/icons/svg/documentation.svg new file mode 100644 index 0000000..7043122 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/documentation.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/download.svg b/ruoyi-ui/src/assets/icons/svg/download.svg new file mode 100644 index 0000000..c896951 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/download.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569915748289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3062" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M768.35456 416a256 256 0 1 0-512 0 192 192 0 1 0 0 384v64a256 256 0 0 1-58.88-505.216 320.128 320.128 0 0 1 629.76 0A256.128 256.128 0 0 1 768.35456 864v-64a192 192 0 0 0 0-384z m-512 384h64v64H256.35456v-64z m448 0h64v64h-64v-64z" fill="#333333" p-id="3063"></path><path d="M539.04256 845.248V512.192a32.448 32.448 0 0 0-32-32.192c-17.664 0-32 14.912-32 32.192v333.056l-36.096-36.096a32.192 32.192 0 0 0-45.056 0.192 31.616 31.616 0 0 0-0.192 45.056l90.88 90.944a31.36 31.36 0 0 0 22.528 9.088 30.08 30.08 0 0 0 22.4-9.088l90.88-90.88a32.192 32.192 0 0 0-0.192-45.12 31.616 31.616 0 0 0-45.056-0.192l-36.096 36.096z" fill="#333333" p-id="3064"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/drag.svg b/ruoyi-ui/src/assets/icons/svg/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/drag.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/druid.svg b/ruoyi-ui/src/assets/icons/svg/druid.svg new file mode 100644 index 0000000..a2b4b4e --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/druid.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036347051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5853" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M832 128H192a64.19 64.19 0 0 0-64 64v640a64.19 64.19 0 0 0 64 64h640a64.19 64.19 0 0 0 64-64V192a64.19 64.19 0 0 0-64-64z m0 703.89l-0.11 0.11H192.11l-0.11-0.11V768h640zM832 544H720L605.6 696.54 442.18 435.07 333.25 544H192v-64h114.75l147.07-147.07L610.4 583.46 688 480h144z m0-288H192v-63.89l0.11-0.11h639.78l0.11 0.11z" p-id="5854"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/edit.svg b/ruoyi-ui/src/assets/icons/svg/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/edit.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/education.svg b/ruoyi-ui/src/assets/icons/svg/education.svg new file mode 100644 index 0000000..7bfb01d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/education.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/email.svg b/ruoyi-ui/src/assets/icons/svg/email.svg new file mode 100644 index 0000000..74d25e2 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/email.svg @@ -0,0 +1 @@ +<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/example.svg b/ruoyi-ui/src/assets/icons/svg/example.svg new file mode 100644 index 0000000..46f42b5 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/example.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/excel.svg b/ruoyi-ui/src/assets/icons/svg/excel.svg new file mode 100644 index 0000000..74d97b8 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/excel.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg b/ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/eye-open.svg b/ruoyi-ui/src/assets/icons/svg/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/eye-open.svg @@ -0,0 +1 @@ +<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/eye.svg b/ruoyi-ui/src/assets/icons/svg/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/eye.svg @@ -0,0 +1 @@ +<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/form.svg b/ruoyi-ui/src/assets/icons/svg/form.svg new file mode 100644 index 0000000..dcbaa18 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/form.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/fullscreen.svg b/ruoyi-ui/src/assets/icons/svg/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/fullscreen.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/github.svg b/ruoyi-ui/src/assets/icons/svg/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/github.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1581238998885" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4187" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M511.542857 14.057143C228.914286 13.942857 0 242.742857 0 525.142857 0 748.457143 143.2 938.285714 342.628571 1008c26.857143 6.742857 22.742857-12.342857 22.742858-25.371429v-88.571428c-155.085714 18.171429-161.371429-84.457143-171.771429-101.6C172.571429 756.571429 122.857143 747.428571 137.714286 730.285714c35.314286-18.171429 71.314286 4.571429 113.028571 66.171429 30.171429 44.685714 89.028571 37.142857 118.857143 29.714286 6.514286-26.857143 20.457143-50.857143 39.657143-69.485715-160.685714-28.8-227.657143-126.857143-227.657143-243.428571 0-56.571429 18.628571-108.571429 55.2-150.514286-23.314286-69.142857 2.171429-128.342857 5.6-137.142857 66.4-5.942857 135.428571 47.542857 140.8 51.771429 37.714286-10.171429 80.8-15.542857 129.028571-15.542858 48.457143 0 91.657143 5.6 129.714286 15.885715 12.914286-9.828571 76.914286-55.771429 138.628572-50.171429 3.314286 8.8 28.228571 66.628571 6.285714 134.857143 37.028571 42.057143 55.885714 94.514286 55.885714 151.2 0 116.8-67.428571 214.971429-228.571428 243.314286a145.714286 145.714286 0 0 1 43.542857 104v128.571428c0.914286 10.285714 0 20.457143 17.142857 20.457143 202.4-68.228571 348.114286-259.428571 348.114286-484.685714 0-282.514286-229.028571-511.2-511.428572-511.2z" p-id="4188"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/guide.svg b/ruoyi-ui/src/assets/icons/svg/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/guide.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.482 70.131l36.204 16.18 69.932-65.485-61.38 70.594 46.435 18.735c1.119.425 2.397-.17 2.797-1.363v-.085L127.998.047 1.322 65.874c-1.12.597-1.519 1.959-1.04 3.151.32.511.72.937 1.2 1.107zm44.676 57.821L64.22 107.26l-18.062-7.834v28.527z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/icon.svg b/ruoyi-ui/src/assets/icons/svg/icon.svg new file mode 100644 index 0000000..82be8ee --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/icon.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 0 1 4.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 0 1 2.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/input.svg b/ruoyi-ui/src/assets/icons/svg/input.svg new file mode 100644 index 0000000..ab91381 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/input.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802859706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="3103"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="3104"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/international.svg b/ruoyi-ui/src/assets/icons/svg/international.svg new file mode 100644 index 0000000..e9b56ee --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/international.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M83.287 103.01c-1.57-3.84-6.778-10.414-15.447-19.548-2.327-2.444-2.182-4.306-1.338-9.862v-.64c.553-3.81 1.513-6.05 14.313-8.087 6.516-1.018 8.203 1.57 10.589 5.178l.785 1.193a12.625 12.625 0 0 0 6.43 5.207c1.134.524 2.53 1.164 4.421 2.24 4.596 2.53 4.596 5.41 4.596 11.753v.727a26.91 26.91 0 0 1-5.178 17.454 59.055 59.055 0 0 1-19.025 11.026c3.49-6.546.814-14.313 0-16.553l-.146-.087zM64 5.12a58.502 58.502 0 0 1 25.484 5.818 54.313 54.313 0 0 0-12.859 10.327c-.93 1.28-1.716 2.473-2.472 3.579-2.444 3.694-3.637 5.352-5.818 5.614a25.105 25.105 0 0 1-4.219 0c-4.276-.29-10.094-.64-11.956 4.422-1.193 3.23-1.396 11.956 2.444 16.495.66 1.077.778 2.4.32 3.578a7.01 7.01 0 0 1-2.066 3.229 18.938 18.938 0 0 1-2.909-2.91 18.91 18.91 0 0 0-8.32-6.603c-1.25-.349-2.647-.64-3.985-.93-3.782-.786-8.03-1.688-9.019-3.812a14.895 14.895 0 0 1-.727-5.818 21.935 21.935 0 0 0-1.396-9.25 8.873 8.873 0 0 0-5.557-4.946A58.705 58.705 0 0 1 64 5.12zM0 64c0 35.346 28.654 64 64 64 35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64C28.654 0 0 28.654 0 64z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/job.svg b/ruoyi-ui/src/assets/icons/svg/job.svg new file mode 100644 index 0000000..2a93a25 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/job.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036191400" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5472" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M934.912 1016.832H192c-14.336 0-25.6-11.264-25.6-25.6v-189.44c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v163.84h691.712V64H217.6v148.48c0 14.336-11.264 25.6-25.6 25.6s-25.6-11.264-25.6-25.6v-174.08c0-14.336 11.264-25.6 25.6-25.6h742.912c14.336 0 25.6 11.264 25.6 25.6v952.832c0 14.336-11.264 25.6-25.6 25.6z" p-id="5473"></path><path d="M232.96 371.2h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 540.16h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 698.88h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM574.464 762.88c-134.144 0-243.2-109.056-243.2-243.2S440.32 276.48 574.464 276.48s243.2 109.056 243.2 243.2-109.056 243.2-243.2 243.2z m0-435.2c-105.984 0-192 86.016-192 192S468.48 711.68 574.464 711.68s192-86.016 192-192S680.448 327.68 574.464 327.68z" p-id="5474"></path><path d="M663.04 545.28h-87.04c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h87.04c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6z" p-id="5475"></path><path d="M576 545.28c-14.336 0-25.6-11.264-25.6-25.6v-87.04c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v87.04c0 14.336-11.264 25.6-25.6 25.6z" p-id="5476"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/language.svg b/ruoyi-ui/src/assets/icons/svg/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/language.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.742 36.8c2.398 7.2 5.595 12.8 11.19 18.4 4.795-4.8 7.992-11.2 10.39-18.4h-21.58zm-52.748 40h20.78l-10.39-28-10.39 28z"/><path d="M111.916 0H16.009C7.218 0 .025 7.2.025 16v96c0 8.8 7.193 16 15.984 16h95.907c8.791 0 15.984-7.2 15.984-16V16c0-8.8-6.394-16-15.984-16zM72.754 103.2c-1.598 1.6-3.197 1.6-4.795 1.6-.8 0-2.398 0-3.197-.8-.8-.8-1.599 0-1.599-.8s-.799-1.6-1.598-3.2c-.8-1.6-.8-2.4-1.599-4l-3.196-8.8H28.797L25.6 96c-1.598 3.2-2.398 5.6-3.197 7.2-.8 1.6-2.398 1.6-4.795 1.6-1.599 0-3.197-.8-4.796-1.6-1.598-1.6-2.397-2.4-2.397-4 0-.8 0-1.6.799-3.2.8-1.6.8-2.4 1.598-4l17.583-44.8c.8-1.6.8-3.2 1.599-4.8.799-1.6 1.598-3.2 2.397-4 .8-.8 1.599-2.4 3.197-3.2 1.599-.8 3.197-.8 4.796-.8 1.598 0 3.196 0 4.795.8 1.598.8 2.398 1.6 3.197 3.2.799.8 1.598 2.4 2.397 4 .8 1.6 1.599 3.2 2.398 5.6l17.583 44c1.598 3.2 2.398 5.6 2.398 7.2-.8.8-1.599 2.4-2.398 4zM116.711 72c-8.791-3.2-15.185-7.2-20.78-12-5.594 5.6-12.787 9.6-21.579 12l-2.397-4c8.791-2.4 15.984-5.6 21.579-11.2C87.939 51.2 83.144 44 81.545 36h-7.992v-3.2h21.58c-1.6-2.4-3.198-5.6-4.796-8l2.397-.8c1.599 2.4 3.997 5.6 5.595 8.8h19.98v4h-7.992c-2.397 8-6.393 15.2-11.189 20 5.595 4.8 11.988 8.8 20.78 11.2l-3.197 4z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/link.svg b/ruoyi-ui/src/assets/icons/svg/link.svg new file mode 100644 index 0000000..48197ba --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/link.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/list.svg b/ruoyi-ui/src/assets/icons/svg/list.svg new file mode 100644 index 0000000..20259ed --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/list.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.585 12.087c0 6.616 3.974 11.98 8.877 11.98 4.902 0 8.877-5.364 8.877-11.98 0-6.616-3.975-11.98-8.877-11.98-4.903 0-8.877 5.364-8.877 11.98zM125.86.107H35.613c-1.268 0-2.114 1.426-2.114 2.852v18.255c0 1.712 1.057 2.853 2.114 2.853h90.247c1.268 0 2.114-1.426 2.114-2.853V2.96c0-1.711-1.057-2.852-2.114-2.852zM.106 62.86c0 6.615 3.974 11.979 8.876 11.979 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zM124.17 50.88H33.921c-1.268 0-2.114 1.425-2.114 2.851v18.256c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852V53.73c0-1.426-.846-2.852-2.114-2.852zM.106 115.913c0 6.616 3.974 11.98 8.876 11.98 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zm124.064-11.98H33.921c-1.268 0-2.114 1.426-2.114 2.853v18.255c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852v-18.255c0-1.427-.846-2.853-2.114-2.853z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/lock.svg b/ruoyi-ui/src/assets/icons/svg/lock.svg new file mode 100644 index 0000000..74fee54 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/lock.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/log.svg b/ruoyi-ui/src/assets/icons/svg/log.svg new file mode 100644 index 0000000..d879d33 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/log.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035943711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4805" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M208.736 566.336H64.384v59.328h144.352v-59.328z m0-336.096H165.44V74.592c0-7.968 4.896-14.848 10.464-14.848h502.016V0.448H175.936c-38.72 1.248-69.248 34.368-68.192 74.144v155.648H64.384V289.6h144.352V230.24z m0 168.096H64.384v59.328h144.352v-59.328z m714.656 76.576h-57.76v474.496c0 7.936-4.896 14.848-10.464 14.848H175.936c-5.568 0-10.464-6.912-10.464-14.848v-155.68h43.296v-59.296H64.384v59.296h43.328v155.68c-1.024 39.776 29.472 72.896 68.192 74.144h679.232c38.72-1.184 69.248-34.368 68.256-74.144V474.912z m14.944-290.336l-83.072-85.312a71.264 71.264 0 0 0-52.544-21.728 71.52 71.52 0 0 0-51.616 23.872L386.528 507.264a30.496 30.496 0 0 0-6.176 10.72L308.16 740.512a30.016 30.016 0 0 0 6.976 30.24c7.712 7.968 19.2 10.752 29.568 7.2l216.544-74.112a28.736 28.736 0 0 0 12.128-7.936L940.448 287.456a75.552 75.552 0 0 0-2.112-102.88z m-557.12 518.272l39.104-120.64 78.336 80.416-117.44 40.224z m170.048-70.016l-103.552-106.016 200.16-222.4 103.52 106.304-200.128 222.112zM897.952 247.072l-0.256 0.224-107.136 119.168-103.52-106.528 106.432-118.624a14.144 14.144 0 0 1 10.304-4.736 13.44 13.44 0 0 1 10.464 4.288l83.264 85.696c5.472 5.6 5.664 14.72 0.448 20.512z" p-id="4806"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/logininfor.svg b/ruoyi-ui/src/assets/icons/svg/logininfor.svg new file mode 100644 index 0000000..267f844 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/logininfor.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036016814" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5261" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 128h-85.333333a42.666667 42.666667 0 0 0 0 85.333333h42.666666v640H170.666667V213.333333h42.666666a42.666667 42.666667 0 0 0 0-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666667v725.333333a42.666667 42.666667 0 0 0 42.666667 42.666667h768a42.666667 42.666667 0 0 0 42.666667-42.666667V170.666667a42.666667 42.666667 0 0 0-42.666667-42.666667z" p-id="5262"></path><path d="M341.333333 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666666 42.666667zM512 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333334 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM682.666667 298.666667a42.666667 42.666667 0 0 0 42.666666-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM341.333333 768a42.666667 42.666667 0 0 0 42.666667-42.666667 128 128 0 0 1 256 0 42.666667 42.666667 0 0 0 85.333333 0 213.333333 213.333333 0 0 0-107.52-184.32A128 128 0 0 0 640 469.333333a128 128 0 0 0-256 0 128 128 0 0 0 22.186667 71.68A213.333333 213.333333 0 0 0 298.666667 725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667z m128-298.666667a42.666667 42.666667 0 1 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667z" p-id="5263"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/message.svg b/ruoyi-ui/src/assets/icons/svg/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/message.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/money.svg b/ruoyi-ui/src/assets/icons/svg/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/money.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/monitor.svg b/ruoyi-ui/src/assets/icons/svg/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/monitor.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; } +</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/nested.svg b/ruoyi-ui/src/assets/icons/svg/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/nested.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/number.svg b/ruoyi-ui/src/assets/icons/svg/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/number.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802851180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2867" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M279.272727 791.272727h512a46.545455 46.545455 0 0 1 0 93.090909H279.272727a46.545455 46.545455 0 0 1 0-93.090909z m33.838546-617.984V651.636364H193.722182V395.170909c0-37.003636-0.884364-59.298909-2.653091-66.746182a24.948364 24.948364 0 0 0-14.615273-16.989091c-8.005818-3.863273-25.786182-5.771636-53.341091-5.771636h-11.822545v-55.854545c57.716364-12.381091 101.562182-37.888 131.490909-76.520728h70.283636z m303.709091 396.8V651.636364H354.164364v-68.235637c77.777455-127.255273 124.043636-206.010182 138.705454-236.218182 14.661818-30.254545 22.016-53.853091 22.016-70.74909 0-13.032727-2.234182-22.714182-6.656-29.137455-4.421818-6.376727-11.170909-9.588364-20.247273-9.588364a22.248727 22.248727 0 0 0-20.200727 10.612364c-4.468364 7.121455-6.656 21.178182-6.656 42.263273v45.521454H354.164364v-17.454545c0-26.763636 1.396364-47.941818 4.142545-63.348364 2.746182-15.499636 9.541818-30.72 20.386909-45.661091 10.798545-14.987636 24.901818-26.298182 42.216727-33.978182 17.361455-7.68 38.167273-11.543273 62.37091-11.543272 47.476364 0 83.316364 11.776 107.706181 35.328 24.296727 23.552 36.445091 53.341091 36.445091 89.367272 0 27.368727-6.842182 56.32-20.48 86.853819-13.730909 30.533818-54.039273 95.325091-121.018182 194.420363h130.885819z m270.615272-189.393454c18.152727 6.097455 31.650909 16.104727 40.494546 29.975272 8.843636 13.917091 13.312 46.452364 13.312 97.652364 0 38.027636-4.328727 67.490909-13.032727 88.529455-8.657455 20.945455-23.598545 36.910545-44.869819 47.848727-21.271273 10.938182-48.593455 16.384-81.873454 16.384-37.794909 0-67.490909-6.330182-89.088-19.083636-21.550545-12.660364-35.746909-28.253091-42.542546-46.638546-6.795636-18.432-10.193455-50.362182-10.193454-95.883636v-37.841455h119.389091v77.730909c0 20.666182 1.210182 33.838545 3.723636 39.424 2.420364 5.585455 7.912727 8.424727 16.337455 8.424728 9.309091 0 15.36-3.537455 18.338909-10.612364 2.932364-7.121455 4.421818-25.6 4.421818-55.575273v-33.047273c0-18.338909-2.048-31.744-6.190546-40.215272a30.72 30.72 0 0 0-18.338909-16.709818c-8.052364-2.653091-23.738182-4.189091-46.964363-4.561455V357.050182c28.392727 0 45.893818-1.070545 52.596363-3.258182a22.946909 22.946909 0 0 0 14.475637-14.149818c2.932364-7.307636 4.421818-18.711273 4.421818-34.257455v-26.624c0-16.756364-1.722182-27.741091-5.12-33.047272-3.490909-5.352727-8.843636-8.005818-16.151273-8.005819-8.285091 0-13.963636 2.792727-16.989091 8.378182-3.025455 5.632-4.561455 17.640727-4.561454 35.933091v39.284364h-119.389091v-40.773818c0-45.661091 10.472727-76.567273 31.325091-92.625455 20.898909-16.058182 54.085818-24.064 99.607272-24.064 56.878545 0 95.511273 11.170909 115.805091 33.373091 20.293818 22.248727 30.394182 53.201455 30.394182 92.765091 0 26.810182-3.630545 46.173091-10.891636 58.088727-7.307636 11.915636-20.107636 22.807273-38.446546 32.628364z" p-id="2868"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/online.svg b/ruoyi-ui/src/assets/icons/svg/online.svg new file mode 100644 index 0000000..330a202 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899557259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="535" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286zM263.943926 754.580874c-92.603071-61.111846-145.713686-149.623739-145.713686-242.840794 0-93.195565 53.094242-181.682899 145.667637-242.774279 11.805884-7.79043 15.061021-23.677259 7.269567-35.483142-7.79043-11.805884-23.677259-15.062044-35.483142-7.269567C128.487861 296.954249 67.006602 401.024489 67.006602 511.74008c0 110.73708 61.496609 214.830857 168.721703 285.593504 4.343935 2.867304 9.240455 4.238534 14.08274 4.238534 8.317433 0 16.476253-4.046153 21.400403-11.507078C279.003923 778.258133 275.748786 762.372328 263.943926 754.580874zM788.660552 226.213092c-11.80486-7.791453-27.692712-4.536316-35.483142 7.269567-7.79043 11.805884-4.536316 27.692712 7.269567 35.483142 92.575442 61.092403 145.670707 149.579737 145.670707 242.774279 0 93.216032-53.111638 181.727924-145.715733 242.840794-11.805884 7.79043-15.059997 23.678282-7.269567 35.484166 4.925173 7.461949 13.081946 11.507078 21.400403 11.507078 4.841262 0 9.739828-1.37123 14.083763-4.238534 107.22714-70.761624 168.724773-174.857447 168.724773-285.593504C957.341323 401.025513 895.860063 296.955272 788.660552 226.213092zM790.090111 633.67213c23.865547-37.459147 36.480866-79.617315 36.480866-121.914654 0-42.298362-12.615319-84.45653-36.480866-121.917724-22.598694-35.470863-55.496965-67.115584-95.139847-91.515297-12.047384-7.413853-27.821649-3.659343-35.236526 8.387018-7.414877 12.045337-3.659343 27.821649 8.385994 35.236526 68.156286 41.949414 107.247606 103.842043 107.247606 169.809477 0 65.964364-39.090297 127.85597-107.247606 169.804361-12.045337 7.414877-15.800871 23.190165-8.385994 35.237549 4.838192 7.861038 13.236466 12.190647 21.835308 12.190647 4.579295 0 9.215896-1.227967 13.400195-3.804652C734.591099 700.786691 767.490394 669.142993 790.090111 633.67213zM567.129086 518.274914c24.12342-17.150612 39.887452-45.305859 39.887452-77.07133 0-52.128241-42.452881-94.538143-94.634334-94.538143-52.18043 0-94.633311 42.408879-94.633311 94.538143 0 31.695886 15.696494 59.797921 39.730886 76.958766-49.875944 21.128203-84.917018 70.234621-84.917018 127.301338 0 2.366907 0.061398 4.762467 0.182149 7.119141l1.249457 24.296359 276.373515 0 1.238201-24.308639c0.119727-2.358721 0.181125-4.750187 0.181125-7.106862C651.786185 588.497255 616.865861 539.465538 567.129086 518.274914zM512.381182 397.889079c23.937179 0 43.411719 19.430538 43.411719 43.314505 0 23.882943-19.47454 43.313481-43.411719 43.313481-23.936155 0-43.409672-19.430538-43.409672-43.313481C468.971509 417.320641 488.445026 397.889079 512.381182 397.889079zM426.08884 625.656573c9.119705-38.542828 44.254923-67.337641 86.085634-67.337641s76.966952 28.794813 86.085634 67.337641L426.08884 625.656573z" p-id="536"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/password.svg b/ruoyi-ui/src/assets/icons/svg/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/password.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802846045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2750" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M868.593046 403.832442c-30.081109-28.844955-70.037123-44.753273-112.624057-44.753273L265.949606 359.079168c-42.554188 0-82.510202 15.908318-112.469538 44.690852-30.236652 28.782533-46.857191 67.222007-46.857191 108.198258l0 294.079782c0 40.977273 16.619516 79.414701 46.702672 108.136859 29.959336 28.844955 70.069869 44.814672 112.624057 44.814672l490.019383 0c42.585911 0 82.696444-15.969717 112.624057-44.814672 30.082132-28.844955 46.579875-67.222007 46.579875-108.136859L915.172921 511.968278C915.171897 471.053426 898.675178 432.677397 868.593046 403.832442zM841.821309 806.049083c0 22.098297-8.882298 42.772152-25.099654 58.306964-16.154935 15.661701-37.81935 24.203238-60.752666 24.203238L265.949606 888.559285c-22.934339 0-44.567032-8.54256-60.877509-24.264637-16.186657-15.474436-25.067932-36.148291-25.067932-58.246589L180.004165 511.968278c0-22.035876 8.881274-42.772152 25.192775-58.307987 16.186657-15.536858 37.81935-24.139793 60.753689-24.139793l490.019383 0c22.933315 0 44.597731 8.602935 60.752666 24.139793 16.21838 15.535835 25.099654 36.272112 25.099654 58.307987L841.822332 806.049083zM510.974136 135.440715c114.914216 0 208.318536 89.75214 208.318536 200.055338l73.350588 0c0-149.113109-126.366036-270.496667-281.669124-270.496667-155.333788 0-281.699824 121.383558-281.699824 270.496667l73.350588 0C302.623877 225.193879 396.059919 135.440715 510.974136 135.440715zM474.299865 747.244792l73.350588 0L547.650453 629.576859l-73.350588 0L474.299865 747.244792z" p-id="2751"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/pdf.svg b/ruoyi-ui/src/assets/icons/svg/pdf.svg new file mode 100644 index 0000000..957aa0c --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/pdf.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M869.073 277.307H657.111V65.344l211.962 211.963zm-238.232 26.27V65.344l-476.498-.054v416.957h714.73v-178.67H630.841zm-335.836 360.57c-5.07-3.064-10.944-5.133-17.61-6.201-6.67-1.064-13.603-1.6-20.81-1.6h-48.821v85.641h48.822c7.206 0 14.14-.532 20.81-1.6 6.665-1.065 12.54-3.133 17.609-6.202 5.064-3.063 9.134-7.406 12.208-13.007 3.065-5.602 4.6-12.937 4.6-22.011 0-9.07-1.535-16.408-4.6-22.01-3.074-5.603-7.144-9.94-12.208-13.01zM35.82 541.805v416.904h952.358V541.805H35.821zm331.421 191.179c-3.6 11.071-9.343 20.879-17.209 29.413-7.874 8.542-18.078 15.408-30.617 20.61-12.544 5.206-27.747 7.807-45.621 7.807h-66.036v102.45h-62.831V607.517h128.867c17.874 0 33.077 2.6 45.62 7.802 12.541 5.207 22.745 12.076 30.618 20.615 7.866 8.538 13.604 18.277 17.21 29.212 3.6 10.943 5.401 22.278 5.401 34.018 0 11.477-1.8 22.752-5.402 33.819zM644.9 806.417c-5.343 17.61-13.408 32.818-24.212 45.627-10.807 12.803-24.283 22.879-40.423 30.213-16.146 7.343-35.155 11.007-57.03 11.007h-123.26V607.518h123.26c18.41 0 35.552 2.941 51.428 8.808 15.873 5.869 29.618 14.671 41.22 26.412 11.608 11.744 20.674 26.411 27.217 44.02 6.535 17.61 9.803 38.288 9.803 62.035 0 20.81-2.67 40.02-8.003 57.624zm245.362-146.07h-138.07v66.03h119.66v48.829h-119.66v118.058h-62.83V607.518h200.9v52.829h-.001zm-318.2 25.611c-6.402-8.266-14.877-14.604-25.412-19.01-10.544-4.402-23.551-6.602-39.019-6.602h-44.825v180.088h56.029c9.07 0 17.872-1.463 26.415-4.401 8.535-2.932 16.14-7.802 22.812-14.609 6.665-6.8 12.007-15.667 16.007-26.61 4.003-10.94 6.003-24.275 6.003-40.021 0-14.408-1.4-27.416-4.202-39.019-2.8-11.607-7.406-21.542-13.808-29.816zm0 0"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/people.svg b/ruoyi-ui/src/assets/icons/svg/people.svg new file mode 100644 index 0000000..2bd54ae --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/people.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M104.185 95.254c8.161 7.574 13.145 17.441 13.145 28.28 0 1.508-.098 2.998-.285 4.466h-10.784c.238-1.465.403-2.948.403-4.465 0-8.983-4.36-17.115-11.419-23.216C86 104.66 75.355 107.162 64 107.162c-11.344 0-21.98-2.495-31.22-6.83-7.064 6.099-11.444 14.218-11.444 23.203 0 1.517.165 3 .403 4.465H10.955a35.444 35.444 0 0 1-.285-4.465c0-10.838 4.974-20.713 13.127-28.291C9.294 85.42.003 70.417.003 53.58.003 23.99 28.656.001 64 .001s63.997 23.988 63.997 53.58c0 16.842-9.299 31.85-23.812 41.673zM64 36.867c-29.454 0-53.33-10.077-53.33 15.342 0 25.418 23.876 46.023 53.33 46.023 29.454 0 53.33-20.605 53.33-46.023 0-25.419-23.876-15.342-53.33-15.342zm24.888 25.644c-3.927 0-7.111-2.665-7.111-5.953 0-3.288 3.184-5.954 7.11-5.954 3.928 0 7.111 2.666 7.111 5.954s-3.183 5.953-7.11 5.953zm-3.556 16.372c0 4.11-9.55 7.442-21.332 7.442-11.781 0-21.332-3.332-21.332-7.442 0-1.06.656-2.064 1.8-2.976 3.295 2.626 10.79 4.465 19.532 4.465 8.743 0 16.237-1.84 19.531-4.465 1.145.912 1.801 1.916 1.801 2.976zm-46.22-16.372c-3.927 0-7.11-2.665-7.11-5.953 0-3.288 3.183-5.954 7.11-5.954 3.927 0 7.111 2.666 7.111 5.954s-3.184 5.953-7.11 5.953z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/peoples.svg b/ruoyi-ui/src/assets/icons/svg/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/peoples.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/phone.svg b/ruoyi-ui/src/assets/icons/svg/phone.svg new file mode 100644 index 0000000..ab8e8c4 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/phone.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1567417214476" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M761.503029 2.90619 242.121921 2.90619c-32.405037 0-58.932204 26.060539-58.932204 58.527998l0 902.302287c0 32.156374 26.217105 58.216913 58.932204 58.216913l519.381108 0c32.344662 0 58.591443-26.060539 58.591443-58.216913L820.094472 61.123103C820.094472 28.966729 793.847691 2.90619 761.503029 2.90619M452.878996 61.123103l98.147344 0c6.780427 0 12.31549 5.536087 12.31549 12.253068 0 6.748704-5.535063 12.253068-12.31549 12.253068l-98.147344 0c-6.779404 0-12.345166-5.504364-12.345166-12.253068C440.532807 66.659189 446.099592 61.123103 452.878996 61.123103M501.641583 980.593398c-29.636994 0-53.987588-23.946388-53.987588-53.677527 0-29.356608 24.039509-53.614082 53.987588-53.614082 29.91738 0 53.987588 23.883967 53.987588 53.614082C555.629171 956.647009 531.559986 980.593398 501.641583 980.593398M766.35657 803.142893c0 16.23373-13.186324 29.107945-29.233811 29.107945l-470.618521 0c-16.35755 0-29.325909-13.186324-29.325909-29.107945L237.178329 163.500794c0-16.232706 13.279445-29.138644 29.325909-29.138644l470.246037 0c16.420995 0 29.357632 13.1853 29.357632 29.138644l0 639.642099L766.35657 803.142893zM766.35657 803.142893" p-id="2267"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/post.svg b/ruoyi-ui/src/assets/icons/svg/post.svg new file mode 100644 index 0000000..2922c61 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/post.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035724641" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3998" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M136.4 434.3h77.7c21.5 0 38.9-17.4 38.9-38.9s-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9zM252.9 628.6c0-21.5-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9H214c21.5-0.1 38.9-17.5 38.9-38.9z" p-id="3999"></path><path d="M874.7 97.5H227c-28.6 0-51.8 23.2-51.8 51.8v194.3h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v129.5h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v194.3c0 28.6 23.2 51.8 51.8 51.8h647.7c28.6 0 51.8-23.2 51.8-51.8V149.3c0-28.6-23.2-51.8-51.8-51.8z m-311.3 723c-15.6 0-146.7-71.6-146.7-91 0-19.4 102-368.6 102-368.6l-83.6-104s-12.3-23.1 24.6-23.1h208.9c36.9 0 18.4 23.1 18.4 23.1l-79 104s102 351.3 102 368.6c0.1 17.3-131 91-146.6 91z m169.2-253.6l-27.9 40.2-74.5-240 103.4 171.7c4.6 7.9 4.2 20.6-1 28.1z" p-id="4000"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/qq.svg b/ruoyi-ui/src/assets/icons/svg/qq.svg new file mode 100644 index 0000000..ee13d4e --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/qq.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M18.448 57.545l-.244-.744-.198-.968-.132-.53v-2.181l.236-.859.24-.908.317-.953.428-1.06.561-1.103.794-1.104v-.773l.077-.724.123-.984.34-1.106.313-1.194.25-.548.289-.511.371-.569.405-.423v-2.73l.234-1.407.236-1.633.42-1.955.577-2.035.43-1.118.426-1.217.468-1.135.559-1.216.57-1.332.655-1.247.737-1.331.929-1.33.43-.762.457-.624.995-1.406 1.025-1.403 1.163-1.444 1.246-1.405 1.352-1.384 1.41-1.423 1.708-1.536 1.083-.934 1.322-1.008 1.34-.89 1.448-.855 1.392-.76 1.57-.63 1.667-.775 1.657-.532 1.653-.552 1.787-.548 1.785-.417 1.876-.347L59.128.68l1.879-.245 1.876-.252 2.002-.106h5.912l1.97.243 1.981.231 2.019.207 1.874.441 1.979.413 1.857.475 2.035.53 1.862.646 1.782.738 1.904.78 1.736.853 1.689.95 1.655 1.044 1.425.971.662.548.693.401 1.323 1.1 1.115 1.064 1.112 1.1 1.083 1.214.894 1.178 1.064 1.217.74 1.306.752 1.162.798 1.352.661 1.175 1.113 2.489.546 1.286.428 1.192.428 1.294.384 1.217.267 1.047.347 1.231.607 2.198.388 1.924.253 1.861.217 1.497.342 2.28.077.362.274.41.737 1.18.473.8.42.832.534.892.472 1.07.307 1.093.334 1.2.252 1.232.115.605.106.746v.648l-.106.643v.8l-.192.774-.35 1.5-.403.76-.299.852v.213l.142.264.4.623 1.746 2.53 1.377 1.9.66 1.267.889 1.389.774 1.52.893 1.627.894 1.828 1.006 2.069.567 1.268.518 1.239.447 1.307.44 1.175.336 1.235.342 1.16.432 2.261.343 2.31.235 2.05v2.891l-.158 1.025-.226 1.768-.308 1.59-.48 1.44-.18.588-.336.707-.28.493-.375.607-.33.383-.42.494-.375.4-.401.34-.48.207-.432.207-.355.114h-.543l-.346-.114-.66-.32-.302-.212-.317-.223-.347-.304-.35-.342-.579-.63-.684-.89-.539-.917-.538-.734-.526-.855-.741-1.517-.833-1.579-.098-.055h-.138l-.338.247-.196.415-.326.516-.567 1.533-.856 2.182-1.096 2.626-.824 1.308-.864 1.366-1.027 1.536-1.09 1.503-.557.68-.676.743-1.555 1.497.136.135.21.214.777.446 3.235 1.524 1.41.779 1.347.756 1.332.953 1.187.982.574.443.432.511.445.593.367.643.198.533.242.64.105.554.115.647-.115.433v.44l-.105.454-.242.415-.092.325-.22.394-.587.784-.543.627-.42.47-.35.348-.893.638-1.01.556-1.077.532-1.155.511-1.287.495-.693.207-.608.167-1.496.342-1.545.325-1.552.323-1.689.27-1.74.072-1.785.21h-5.539l-1.998-.114-1.86-.168-2.005-.27-1.99-.209-2.095-.286-2.03-.495-1.981-.374-1.968-.552-2.019-.707-1.98-.585-1.044-.342-.927-.323-.586-.223-.582-.12h-1.647l-1.904-.131-.962-.096-1.24-.135-.795.705-1.085.665-1.471.701-1.628.875-.99.475-1.033.376-2.281.914-1.24.305-1.3.343-1.803.344-1.13.086-1.193.1-1.246.135-1.45.053h-5.926l-3.346-.053-3.25-.321-1.644-.23-1.589-.23-1.546-.227-1.547-.305-1.442-.456-1.434-.325-1.294-.51-1.223-.474-1.142-.533-.99-.583-.984-.71-.336-.343-.44-.415-.334-.362-.3-.417-.278-.415-.215-.42-.311-.89-.109-.46-.138-.51v-.473l.138-.533v-.53l.109-.53v-1.069l.052-.564.259-.647.215-.646.39-.779.286-.3.236-.348.615-.738.49-.38.464-.266.428-.338.676-.21.543-.324.676-.341.77-.227.775-.231.897-.192.85-.11 1.008-.13 1.093-.081.284-.092h.063l.137-.115v-.13l-.2-.266-.58-.27-1.45-1.231-.975-.761-1.127-.967-1.136-1.082-1.181-1.382-1.36-1.558-.508-.843-.672-.87-.58-1.007-.522-1.1-.704-1.047-.459-1.194-.547-1.192-.546-1.33-.397-1.273-.378-1.575-.112-.057h-.115l-.059-.113h-.14l-.23.113-.114.057-.158.264-.057.321-.119.286-.206.477-.664 1.157-.345.701-.546.612-.58.736-.641.816-.677.724-.795.701-.734.658-.814.524-.89.546-.855.325-1.008.247-.99.095h-.233l-.228-.095-.18-.384-.29-.188-.38-.912-.237-.493-.255-.707-.21-.734-.113-.724-.313-1.648-.12-.972v-3.185l.12-2.379.196-1.214.23-1.252.21-1.347.374-1.254.42-1.443.431-1.407.578-1.448.545-1.38.754-1.4.699-1.52.855-1.425 1.006-1.538 1.023-1.382 1.069-1.538.891-1.071 1.142-1.227 1.202-1.237.56-.59.678-.662.985-.836 1.012-.853 1.647-1.446 1.242-.889z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/question.svg b/ruoyi-ui/src/assets/icons/svg/question.svg new file mode 100644 index 0000000..cf75bd4 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/question.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1581238842264" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1409" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 0C229.233778 0 0 229.233778 0 512s229.233778 512 512 512 512-229.233778 512-512A512 512 0 0 0 512 0z m0 938.666667C276.366222 938.666667 85.333333 747.633778 85.333333 512 85.333333 276.366222 276.366222 85.333333 512 85.333333c235.633778 0 426.666667 191.032889 426.666667 426.666667a426.666667 426.666667 0 0 1-426.666667 426.666667z m0-717.653334a170.666667 170.666667 0 0 0-170.666667 170.666667 42.666667 42.666667 0 0 0 85.333334 0 85.333333 85.333333 0 1 1 85.333333 85.333333 42.666667 42.666667 0 0 0-42.666667 42.666667v111.36a42.666667 42.666667 0 0 0 85.333334 0v-74.24A170.666667 170.666667 0 0 0 512 221.013333z m-42.666667 542.293334a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 0 0-85.333334 0z" p-id="1410"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/radio.svg b/ruoyi-ui/src/assets/icons/svg/radio.svg new file mode 100644 index 0000000..0cde345 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/radio.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575966775973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="879" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M507.39346659 71.84873358c241.53533667 0 437.39770766 195.85422109 437.39770767 437.37442191 0 241.53766571-195.86237099 437.38955776-437.39770767 437.38955776-241.50040803 0-437.34997219-195.85189205-437.34997219-437.38955776C70.0434944 267.70295467 265.89189347 71.84873358 507.39346659 71.84873358L507.39346659 71.84873358zM507.39346659 282.81899805c-125.00686734 0-226.37039389 101.38914133-226.37039388 226.41813048 0 125.01268821 101.36352768 226.39717262 226.37039388 226.39717262 125.04295993 0 226.42395136-101.38448441 226.42395136-226.39717262C733.81625401 384.20813938 632.43642653 282.81899805 507.39346659 282.81899805L507.39346659 282.81899805zM507.39346659 120.78172615c-214.46664192 0-388.42047261 173.95150279-388.4204726 388.44026539 0 214.51204949 173.95499463 388.46122325 388.4204726 388.46122325 214.52369237 0 388.46005817-173.94800981 388.46005818-388.46122325C895.85236082 294.73322894 721.91715897 120.78172615 507.39346659 120.78172615z" p-id="880"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/rate.svg b/ruoyi-ui/src/assets/icons/svg/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/rate.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577246781606" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1098" xmlns:xlink="http://www.w3.org/1999/xlink" width="84.5595703125" height="81"><defs><style type="text/css"></style></defs><path d="M633.72929961 378.02038203l9.49872568 18.68789795 20.78025469 2.79745225 206.61592412 27.33248408a11.46496817 11.46496817 0 0 1 6.6095543 19.47324902l-147.2675168 147.35350284-14.89299345 14.89299345 3.8006376 20.68280244 37.84585956 204.89044571a11.46496817 11.46496817 0 0 1-16.4808914 12.2961788L554.68980898 751.84713388l-18.68789794-9.49299345-18.48726123 9.99171915-183.23885392 99.34968163a11.46496817 11.46496817 0 0 1-16.78471347-11.8662416l32.5433127-205.79617881 3.29617793-20.78598692-15.19108243-14.49172002-151.03375839-143.48407587a11.46496817 11.46496817 0 0 1 6.09936328-19.63949062l205.79617881-32.63503185 20.78598691-3.2961788L428.87898125 380.72038203 518.59235674 192.64331182a11.46496817 11.46496817 0 0 1 20.56815264-0.26369385l94.56879023 185.63503183zM496.64840732 85.52038203l-121.75796162 254.98089229L95.76433145 384.76178369A34.3949045 34.3949045 0 0 0 77.46050938 443.66879023l204.87324901 194.66369385-44.16879023 279.1146498a34.3949045 34.3949045 0 0 0 50.36560489 35.61592325l248.4-134.67898038 251.84522285 128.27579591a34.3949045 34.3949045 0 0 0 49.43694287-36.89426777l-51.30573223-277.85350284 199.73120977-199.90891758a34.3949045 34.3949045 0 0 0-19.82866201-58.40827998l-280.11783428-37.03184736L558.32993633 84.71210205a34.3949045 34.3949045 0 0 0-61.68152901 0.80254775z" p-id="1099"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/redis-list.svg b/ruoyi-ui/src/assets/icons/svg/redis-list.svg new file mode 100644 index 0000000..98a15b2 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/redis-list.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1656035183065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3395" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); } +</style></defs><path d="M958.88 730.06H65.12c-18.28 0-33.12-14.82-33.12-33.12V68.91c0-18.29 14.83-33.12 33.12-33.12h893.77c18.28 0 33.12 14.82 33.12 33.12v628.03c-0.01 18.3-14.84 33.12-33.13 33.12zM98.23 663.83h827.53v-561.8H98.23v561.8z" p-id="3396"></path><path d="M512 954.55c-18.28 0-33.12-14.82-33.12-33.12V733.92c0-18.29 14.83-33.12 33.12-33.12s33.12 14.82 33.12 33.12v187.51c0 18.3-14.84 33.12-33.12 33.12z" p-id="3397"></path><path d="M762.01 988.21H261.99c-18.28 0-33.12-14.82-33.12-33.12 0-18.29 14.83-33.12 33.12-33.12h500.03c18.28 0 33.12 14.82 33.12 33.12-0.01 18.29-14.84 33.12-33.13 33.12zM514.74 578.55c-21.63 0-43.31-3.87-64.21-11.65-45.95-17.13-82.49-51.13-102.86-95.74-5.07-11.08-0.19-24.19 10.89-29.26 11.08-5.09 24.19-0.18 29.26 10.91 15.5 33.88 43.25 59.7 78.14 72.71 34.93 12.99 72.79 11.64 106.66-3.85 33.22-15.17 58.8-42.26 72.03-76.3 4.42-11.37 17.21-17.01 28.57-12.58 11.36 4.42 16.99 17.22 12.57 28.58-17.42 44.82-51.1 80.5-94.82 100.47-24.34 11.12-50.25 16.71-76.23 16.71z" p-id="3398"></path><path d="M325.27 528.78c-1.66 0-3.34-0.18-5.02-0.57-11.88-2.77-19.28-14.63-16.49-26.51l18.84-81c1.34-5.82 5-10.84 10.13-13.92 5.09-3.09 11.3-3.96 17.03-2.41l80.51 21.43c11.79 3.14 18.8 15.23 15.67 27.02-3.15 11.79-15.42 18.75-27.02 15.65l-58.49-15.57-13.69 58.81c-2.37 10.2-11.45 17.07-21.47 17.07zM360.8 351.01c-2.65 0-5.37-0.49-8-1.51-11.36-4.41-16.99-17.21-12.59-28.57 17.4-44.79 51.06-80.47 94.8-100.48 92.15-42.06 201.25-1.39 243.31 90.68 5.07 11.08 0.19 24.19-10.89 29.26-11.13 5.07-24.19 0.17-29.26-10.91-31.97-69.91-114.9-100.82-184.79-68.86-33.22 15.19-58.8 42.28-71.99 76.29-3.41 8.74-11.75 14.1-20.59 14.1z" p-id="3399"></path><path d="M684.68 376.74c-1.47 0-2.95-0.15-4.42-0.44l-81.61-16.68c-11.94-2.45-19.64-14.11-17.21-26.06 2.44-11.96 14.1-19.64 26.04-17.22l59.29 12.12 10.23-59.5c2.05-12 13.52-20.19 25.48-18.01 12.03 2.06 20.09 13.48 18.02 25.5l-14.08 81.96a22.089 22.089 0 0 1-9.29 14.49c-3.7 2.51-8.03 3.84-12.45 3.84z" p-id="3400"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/redis.svg b/ruoyi-ui/src/assets/icons/svg/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/redis.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605865043777" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="856" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1023.786667 611.84c-0.426667 9.770667-13.354667 20.693333-39.893334 34.56-54.613333 28.458667-337.749333 144.896-397.994666 176.298667-60.288 31.402667-93.738667 31.104-141.354667 8.32-47.616-22.741333-348.842667-144.469333-403.114667-170.368-27.093333-12.970667-40.917333-23.893333-41.386666-34.218667v103.509333c0 10.325333 14.250667 21.290667 41.386666 34.261334 54.272 25.941333 355.541333 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.362667 60.245333-31.402667 343.338667-147.797333 397.994666-176.298667 27.776-14.464 40.106667-25.728 40.106667-35.925333v-102.058667l-0.213333-0.085333z m0-168.746667c-0.512 9.770667-13.397333 20.650667-39.893334 34.517334-54.613333 28.458667-337.749333 144.896-397.994666 176.298666-60.288 31.402667-93.738667 31.104-141.354667 8.362667-47.616-22.741333-348.842667-144.469333-403.114667-170.410667-27.093333-12.928-40.917333-23.893333-41.386666-34.176v103.509334c0 10.325333 14.250667 21.248 41.386666 34.218666 54.272 25.941333 355.498667 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.32 60.245333-31.402667 343.338667-147.84 397.994666-176.298666 27.776-14.506667 40.106667-25.770667 40.106667-35.968v-102.058667l-0.256-0.042667z m0-175.018666c0.469333-10.410667-13.141333-19.541333-40.533334-29.610667-53.248-19.498667-334.634667-131.498667-388.522666-151.253333-53.888-19.712-75.818667-18.901333-139.093334 3.84C392.234667 113.706667 92.629333 231.253333 39.338667 252.074667c-26.666667 10.496-39.68 20.181333-39.253334 30.506666V386.133333c0 10.325333 14.250667 21.248 41.386667 34.218667 54.272 25.941333 355.498667 147.669333 403.114667 170.410667 47.616 22.741333 81.066667 23.04 141.354666-8.362667 60.245333-31.402667 343.338667-147.84 397.994667-176.298667 27.776-14.506667 40.106667-25.770667 40.106667-35.968V268.074667h-0.341334zM366.677333 366.08l237.269334-36.437333-71.68 105.088-165.546667-68.650667z m524.8-94.634667l-140.330666 55.466667-15.232 5.973333-140.245334-55.466666 155.392-61.44 140.373334 55.466666z m-411.989333-101.674666l-22.954667-42.325334 71.594667 27.989334 67.498667-22.101334-18.261334 43.733334 68.778667 25.770666-88.704 9.216-19.882667 47.786667-32.085333-53.290667-102.4-9.216 76.416-27.562666z m-176.768 59.733333c70.058667 0 126.805333 21.973333 126.805333 49.109333s-56.746667 49.152-126.805333 49.152-126.848-22.058667-126.848-49.152c0-27.136 56.789333-49.152 126.848-49.152z" p-id="857"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/row.svg b/ruoyi-ui/src/assets/icons/svg/row.svg new file mode 100644 index 0000000..0780992 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/row.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579339929870" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M152 854.856875h325.7146875V237.715625H134.856875v600q0 6.99375 5.0746875 12.0684375T152 854.856875z m737.143125-17.1421875v-600H546.284375v617.1421875H872q6.99375 0 12.0684375-5.07375t5.0746875-12.0684375z m68.5715625-651.429375V837.715625q0 35.3821875-25.16625 60.5484375T872 923.4284375H152q-35.383125 0-60.5484375-25.1653125T66.284375 837.7146875V186.284375q0-35.3821875 25.16625-60.5484375T152 100.5715625h720q35.383125 0 60.5484375 25.1653125t25.16625 60.5484375z" p-id="1183"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/search.svg b/ruoyi-ui/src/assets/icons/svg/search.svg new file mode 100644 index 0000000..84233dd --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/search.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/select.svg b/ruoyi-ui/src/assets/icons/svg/select.svg new file mode 100644 index 0000000..d628382 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/select.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575803481213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="804" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62 511.97954521C62 263.86590869 263.90681826 62 511.97954521 62s449.97954521 201.825 449.97954521 449.97954521c0 248.19545479-201.90681826 449.97954521-449.97954521 449.97954521C263.90681826 962 62 760.175 62 511.97954521M901.98636348 511.97954521c0-215.24318174-175.00909131-390.41590869-390.00681827-390.41590869-215.03863652 0-389.96590869 175.17272695-389.96590868 390.41590869 0 215.28409131 175.00909131 390.45681826 389.96590868 390.45681826C727.01818174 902.47727305 901.98636348 727.30454521 901.98636348 511.97954521M264.17272695 430.28409131c0-5.76818174 2.12727305-11.51590869 6.64772696-15.87272696 8.71363652-8.75454521 22.88863652-8.75454521 31.725 0l209.4340913 208.22727305L721.45454521 414.53409131c8.75454521-8.71363652 22.97045479-8.71363652 31.90909132 0 8.71363652 8.75454521 8.71363652 22.88863652 0 31.60227304L511.97954521 685.74090869 270.71818174 446.01363653C266.27954521 441.77954521 264.17272695 436.05227305 264.17272695 430.28409131" p-id="805"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/server.svg b/ruoyi-ui/src/assets/icons/svg/server.svg new file mode 100644 index 0000000..eb287e3 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/server.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547360688278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M890 120H134a70 70 0 0 0-70 70v500a70 70 0 0 0 70 70h756a70 70 0 0 0 70-70V190a70 70 0 0 0-70-70z m-10 520a40 40 0 0 1-40 40H712V448a40 40 0 0 0-80 0v232h-80V368a40 40 0 0 0-80 0v312h-80V512a40 40 0 0 0-80 0v168H184a40 40 0 0 1-40-40V240a40 40 0 0 1 40-40h656a40 40 0 0 1 40 40zM696 824H328a40 40 0 0 0 0 80h368a40 40 0 0 0 0-80z" p-id="6718"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/shopping.svg b/ruoyi-ui/src/assets/icons/svg/shopping.svg new file mode 100644 index 0000000..87513e7 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/shopping.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/size.svg b/ruoyi-ui/src/assets/icons/svg/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/size.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/skill.svg b/ruoyi-ui/src/assets/icons/svg/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/skill.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M31.652 93.206h33.401c1.44 2.418 3.077 4.663 4.93 6.692h-38.33v-6.692zm0-10.586h28.914a44.8 44.8 0 0 1-1.264-6.688h-27.65v6.688zm0-17.27H59.39c.288-2.286.714-4.532 1.34-6.687H31.65v6.687h.003zm53.913 44.84v5.85c0 2.798-2.095 5.075-4.667 5.075h-70.07c-2.576 0-4.663-2.277-4.663-5.075V31.26l23.22-20.96v22.25H17.16v6.688h18.39V6.688h45.348c2.576 0 4.667 2.277 4.667 5.066v20.009c1.987-.675 4.053-1.128 6.17-1.445v-18.56C91.738 5.28 86.874 0 80.902 0H31.15L0 28.118v87.917c0 6.48 4.859 11.759 10.832 11.759h70.07c5.974 0 10.837-5.27 10.837-11.759v-4.41c-2.117-.312-4.183-.765-6.17-1.435h-.004zM23.279 58.667h-7.96v6.688h7.96v-6.688zm-7.956 41.23h7.96v-6.691h-7.96v6.692zm7.956-23.96h-7.96v6.687h7.96v-6.688zm89.718-15.042l-4.896-4.07-12.447 17.613-11.19-9.305-3.762 5.311 16.091 13.38 16.204-22.929zM128 70.978c0-18.632-13.97-33.782-31.147-33.782-17.168 0-31.135 15.155-31.135 33.782 0 18.628 13.97 33.783 31.135 33.783 17.172 0 31.143-15.15 31.143-33.783H128zm-6.17 0c0 14.933-11.203 27.1-24.981 27.1-13.77 0-24.987-12.158-24.987-27.1 0-14.941 11.195-27.099 24.987-27.099 13.778 0 24.982 12.158 24.982 27.1z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/slider.svg b/ruoyi-ui/src/assets/icons/svg/slider.svg new file mode 100644 index 0000000..fbe4f39 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/slider.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577185310368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M951.453125 476.84375H523.671875a131.8359375 131.8359375 0 0 0-254.1796875 0H72.546875v70.3125h196.9453125a131.8359375 131.8359375 0 0 0 254.1796875 0H951.453125z" p-id="1239"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/star.svg b/ruoyi-ui/src/assets/icons/svg/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/star.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M70.66 4.328l14.01 29.693c1.088 2.29 3.177 3.882 5.603 4.25l31.347 4.76c6.087.926 8.528 8.756 4.117 13.247L103.05 79.395c-1.75 1.78-2.544 4.352-2.132 6.867l5.352 32.641c1.043 6.337-5.33 11.182-10.778 8.19l-28.039-15.409a7.13 7.13 0 0 0-6.91 0l-28.039 15.41c-5.448 2.99-11.821-1.854-10.777-8.19l5.352-32.642c.415-2.515-.387-5.088-2.136-6.867L2.264 56.278C-2.146 51.787.286 43.957 6.38 43.031l31.343-4.76c2.419-.368 4.51-1.96 5.595-4.25L57.334 4.328c2.728-5.77 10.605-5.77 13.325 0z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/swagger.svg b/ruoyi-ui/src/assets/icons/svg/swagger.svg new file mode 100644 index 0000000..05d4e7b --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/swagger.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036776944" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6463" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M64 223.995345h168.001164v47.997673c0 26.428509 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.569164 41.984-47.997673v-47.997673h504.003491a32.004655 32.004655 0 0 0 0-64.009309H455.996509V111.988364c0-26.428509-18.878836-47.997673-41.984-47.997673H273.985164c-23.095855 0-41.984 21.569164-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.009309zM288.004655 128h111.997672V256H288.004655V128zM960 479.995345H791.998836v-47.997672c0-26.372655-18.878836-47.997673-41.984-47.997673H609.978182c-23.095855 0-41.984 21.634327-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.00931h504.003491v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h168.001164a32.004655 32.004655 0 1 0-0.009309-64.00931zM735.995345 576H623.997673v-128h111.997672v128zM960 800.293236v-0.288581H455.996509v-47.997673c0-26.363345-18.878836-47.997673-41.984-47.997673H274.050327c-23.105164 0-41.984 21.634327-41.984 47.997673v47.997673H64v0.288581a32.004655 32.004655 0 0 0 0 64.009309c0.986764 0 1.917673-0.195491 2.885818-0.288581h165.115346v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h501.108364c0.968145 0.093091 1.899055 0.288582 2.895127 0.288581a32.004655 32.004655 0 1 0-0.009309-64.009309zM400.002327 896H288.004655V768h111.997672v128z" fill="" p-id="6464"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/switch.svg b/ruoyi-ui/src/assets/icons/svg/switch.svg new file mode 100644 index 0000000..0ba61e3 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/switch.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576042673958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1110" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M692 792H332c-150 0-270-120-270-270s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="1111"></path><path d="M341 522m-150 0a150 150 0 1 0 300 0 150 150 0 1 0-300 0Z" p-id="1112"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/system.svg b/ruoyi-ui/src/assets/icons/svg/system.svg new file mode 100644 index 0000000..5992593 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/system.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; } +</style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tab.svg b/ruoyi-ui/src/assets/icons/svg/tab.svg new file mode 100644 index 0000000..b4b48e4 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tab.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.921.052H49.08c-1.865 0-3.198 1.599-3.198 3.464v6.661c0 1.865 1.6 3.464 3.198 3.464h29.84c1.865 0 3.198-1.599 3.198-3.464V3.516C82.385 1.65 80.786.052 78.92.052zm45.563 0H94.642c-1.865 0-3.464 1.599-3.464 3.464v6.661c0 1.865 1.599 3.464 3.464 3.464h29.842c1.865-.266 3.464-1.599 3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464zm0 22.382H40.02c-1.866 0-3.464-1.599-3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464H3.516C1.65.052.052 1.651.052 3.516V124.75c0 1.598 1.599 3.197 3.464 3.197h120.968c1.865 0 3.464-1.599 3.464-3.464V25.898c0-1.865-1.599-3.464-3.464-3.464z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/table.svg b/ruoyi-ui/src/assets/icons/svg/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/table.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/textarea.svg b/ruoyi-ui/src/assets/icons/svg/textarea.svg new file mode 100644 index 0000000..2709f29 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/textarea.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802855098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2984" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64z m0 608c0 16-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V256c0-16 12.8-32 32-32h704c19.2 0 32 12.8 32 32v512z" p-id="2985"></path><path d="M224 288c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V320c0-16-12.8-32-32-32z m608 480c19.2 0 32-12.8 32-32V608L704 768h128z" p-id="2986"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/theme.svg b/ruoyi-ui/src/assets/icons/svg/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/theme.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M125.5 36.984L95.336 2.83C93.735 1.018 91.565 0 89.3 0c-2.263 0-4.433 1.018-6.033 2.83l-3.786 4.286c-1.6 1.812-3.77 2.83-6.032 2.831H54.553c-2.263 0-4.434-1.018-6.033-2.83L44.734 2.83C43.134 1.018 40.964 0 38.701 0c-2.263 0-4.434 1.018-6.034 2.83L2.5 36.984C.9 38.796 0 41.254 0 43.815c0 2.562.899 5.02 2.5 6.831L14.565 64.31c2.178 2.468 5.367 3.403 8.33 2.444 1.35-.435 2.709.592 2.709 2.18v49.407c0 5.313 3.84 9.66 8.532 9.66h59.726c4.693 0 8.532-4.347 8.532-9.66V68.934c0-1.59 1.36-2.616 2.71-2.181 2.962.96 6.15.024 8.329-2.444L125.5 50.646c1.6-1.811 2.499-4.269 2.499-6.83 0-2.563-.899-5.02-2.5-6.832z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/time-range.svg b/ruoyi-ui/src/assets/icons/svg/time-range.svg new file mode 100644 index 0000000..13c1202 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/time-range.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774825624" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1248" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.595712 482.290351 345.420077 482.290351l0 57.307194 210.477712 0L555.897789 274.196942l-57.301054 0L498.596735 482.290351zM498.595712 482.290351" p-id="1249"></path><path d="M577.685002 644.98478l379.879913 0 0 57.302077L577.685002 702.286858 577.685002 644.98478 577.685002 644.98478zM577.685002 644.98478" p-id="1250"></path><path d="M577.685002 773.764795l379.879913 0 0 57.307194L577.685002 831.071989 577.685002 773.764795 577.685002 773.764795zM577.685002 773.764795" p-id="1251"></path><path d="M577.685002 902.549927l379.879913 0 0 57.307194L577.685002 959.857121 577.685002 902.549927 577.685002 902.549927zM577.685002 902.549927" p-id="1252"></path><path d="M102.523001 382.290823c4.450359 2.615571 9.470699 3.954055 14.530948 3.954055 2.969635 0 5.952572-0.461511 8.836249-1.394766l190.809767-61.886489c15.052834-4.882194 23.297612-21.040199 18.415418-36.08894-4.882194-15.052834-21.040199-23.297612-36.093033-18.415418L175.676092 308.458257c15.994276-26.115797 35.170011-50.537 57.370639-72.743768 73.767074-73.767074 171.845857-114.388237 276.16783-114.388237 104.32095 0 202.39564 40.622186 276.16169 114.388237s114.393353 171.845857 114.393353 276.16783c0 26.427906-2.615571 52.449559-7.709589 77.780481l58.302871 0c4.464685-25.499767 6.708795-51.470255 6.708795-77.780481 0-60.449767-11.845793-119.102608-35.204803-174.336584-22.559808-53.334719-54.850236-101.226472-95.968725-142.349055-41.122583-41.122583-89.017406-73.408917-142.348032-95.968725C628.317169 75.866898 569.659211 64.021106 509.215584 64.021106c-60.448744 0-119.106702 11.845793-174.336584 35.207873-53.334719 22.559808-101.230566 54.846142-142.349055 95.968725-23.980157 23.980157-44.934398 50.278103-62.727647 78.601172l-20.738323-105.655342c-3.043313-15.527648-18.105357-25.642007-33.631982-22.599717-15.527648 3.048429-25.64303 18.105357-22.599717 33.637098l36.102243 183.932126C90.51348 371.153158 95.460142 378.13313 102.523001 382.290823L102.523001 382.290823zM102.523001 382.290823" p-id="1253"></path><path d="M126.020158 587.9416 67.768453 587.9416c5.759167 33.679054 15.368012 66.544579 28.789697 98.278327 22.559808 53.333696 54.850236 101.225449 95.971795 142.348032 41.122583 41.122583 89.014336 73.408917 142.349055 95.968725 54.112432 22.88829 111.517863 34.71157 170.668031 35.18229L505.547031 902.395408c-102.94972-0.941442-199.594851-41.445948-272.499277-114.349351C177.545672 732.543975 140.810003 663.275355 126.020158 587.9416L126.020158 587.9416zM126.020158 587.9416" p-id="1254"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/time.svg b/ruoyi-ui/src/assets/icons/svg/time.svg new file mode 100644 index 0000000..b376e32 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/time.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577099827399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M520 559h204c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H488c-17.673 0-32-14.327-32-32 0-0.167 0.001-0.334 0.004-0.5a32.65 32.65 0 0 1-0.004-0.5V277c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32v282z m-8 401C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-64c212.077 0 384-171.923 384-384S724.077 128 512 128 128 299.923 128 512s171.923 384 384 384z" p-id="1009"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tool.svg b/ruoyi-ui/src/assets/icons/svg/tool.svg new file mode 100644 index 0000000..48e0e35 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tool.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1553828490559" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1684" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M898.831744 900.517641 103.816972 900.517641c-36.002982 0-65.363683-29.286-65.363683-65.313541l0-554.949184c0-36.041868 29.361725-65.326844 65.363683-65.326844l795.015795 0c36.002982 0 65.198931 29.284977 65.198931 65.326844l0 554.949184C964.030675 871.231641 934.834726 900.517641 898.831744 900.517641L898.831744 900.517641zM103.816972 255.593236c-13.576203 0-24.711821 11.085476-24.711821 24.662703l0 554.949184c0 13.576203 11.136641 24.662703 24.711821 24.662703l795.015795 0c13.577227 0 24.547069-11.086499 24.547069-24.662703l0-554.949184c0-13.577227-10.970866-24.662703-24.547069-24.662703L103.816972 255.593236 103.816972 255.593236zM664.346245 251.774257c-11.161201 0-20.332071-9.080819-20.332071-20.332071l0-101.278661c0-13.576203-11.047614-24.623817-24.699542-24.623817L383.181611 105.539708c-13.576203 0-24.712845 11.04659-24.712845 24.623817l0 101.278661c0 11.252275-9.041934 20.332071-20.332071 20.332071-11.20111 0-20.319791-9.080819-20.319791-20.332071l0-101.278661c0-35.989679 29.323862-65.275679 65.364707-65.275679l236.133022 0c36.06745 0 65.402569 29.284977 65.402569 65.275679l0 101.278661C684.717202 242.694461 675.636383 251.774257 664.346245 251.774257L664.346245 251.774257zM413.233044 521.725502 75.694471 521.725502c-11.163247 0-20.333094-9.117658-20.333094-20.35663 0-11.252275 9.169847-20.332071 20.333094-20.332071l337.538573 0c11.277858 0 20.319791 9.080819 20.319791 20.332071C433.552835 512.607844 424.510902 521.725502 413.233044 521.725502L413.233044 521.725502zM912.894018 521.725502 575.367725 521.725502c-11.213389 0-20.332071-9.117658-20.332071-20.35663 0-11.252275 9.118682-20.332071 20.332071-20.332071l337.526293 0c11.290137 0 20.332071 9.080819 20.332071 20.332071C933.226089 512.607844 924.184155 521.725502 912.894018 521.725502L912.894018 521.725502zM557.56322 634.217552 445.085496 634.217552c-11.213389 0-20.332071-9.079796-20.332071-20.331048l0-168.763658c0-11.251252 9.118682-20.332071 20.332071-20.332071l112.478747 0c11.290137 0 20.370956 9.080819 20.370956 20.332071l0 168.763658C577.934177 625.137757 568.853357 634.217552 557.56322 634.217552L557.56322 634.217552zM465.417567 593.514525l71.827909 0L537.245476 465.454918l-71.827909 0L465.417567 593.514525 465.417567 593.514525z" p-id="1685"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tree-table.svg b/ruoyi-ui/src/assets/icons/svg/tree-table.svg new file mode 100644 index 0000000..8aafdb8 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tree-table.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/tree.svg b/ruoyi-ui/src/assets/icons/svg/tree.svg new file mode 100644 index 0000000..dd4b7dd --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/tree.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/upload.svg b/ruoyi-ui/src/assets/icons/svg/upload.svg new file mode 100644 index 0000000..bae49c0 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/upload.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/user.svg b/ruoyi-ui/src/assets/icons/svg/user.svg new file mode 100644 index 0000000..0ba0716 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/user.svg @@ -0,0 +1 @@ +<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/validCode.svg b/ruoyi-ui/src/assets/icons/svg/validCode.svg new file mode 100644 index 0000000..cfb1021 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/validCode.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569580729849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M513.3 958.5c-142.2 0-397.9-222.1-401.6-440.5V268c1.7-39.6 31.7-72.3 71.1-77.3 49-4.6 97.1-16.5 142.7-35.3 47.8-14 91.9-38.3 129.4-71.1 30.3-24.4 72.9-26.3 105.3-4.6 39.9 30.7 83.8 55.9 130.5 74.6 48.6 14.7 98.2 25.9 148.4 33.7 38.5 7.6 67.1 40.3 69.5 79.5 3.3 84.9 2.5 169.9-2.6 254.7-33.7 281.6-253.7 436.4-392.7 436.3z m-0.1-813.7c-7.2-0.2-14.3 2-20 6.4-39.7 35.2-86.8 61.1-137.7 75.7-46.8 19.2-96.2 31-146.6 35.2-11 3.2-18.8 13-19.5 24.4v230.1c3.5 180.3 223.3 361 323.9 361s287.3-120.2 317.6-360.5c7.3-142.7 0-228.6 0-229.6-1.3-13.3-11-24.3-24-27.3-49.6-7.7-98.6-19-146.5-33.7-46.3-19.5-89.7-45.3-129-76.7-5.8-3.8-12.7-5.5-19.5-4.9l1.3-0.1z" fill="#C6CCDA" p-id="1940"></path><path d="M750.1 428L490.7 673.2c-11.7 11.1-29.5 12.9-43.1 4.2l-6.8-5.8-141.2-149.4c-9.3-9.3-12.7-22.9-9-35.5 3.8-12.6 14.1-22.1 27-24.8 12.9-2.7 26.1 1.9 34.6 11.9L469 597.5l233.7-221c14.6-12.8 36.8-11.6 49.9 2.7 13.2 14.2 11.5 35.3-2.5 48.8" fill="#C6CCDA" p-id="1941"></path></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/wechat.svg b/ruoyi-ui/src/assets/icons/svg/wechat.svg new file mode 100644 index 0000000..c586e55 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/wechat.svg @@ -0,0 +1 @@ +<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svg/zip.svg b/ruoyi-ui/src/assets/icons/svg/zip.svg new file mode 100644 index 0000000..f806fc4 --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svg/zip.svg @@ -0,0 +1 @@ +<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.527 116.793c.178.008.348.024.527.024h40.233c4.711-.005 8.53-3.677 8.534-8.21V18.895c-.004-4.532-3.823-8.204-8.534-8.209H79.054c-.179 0-.353.016-.527.024V0L0 10.082v107.406l78.527 10.342v-11.037zm0-101.362c.174-.024.348-.052.527-.052h40.233c2.018 0 3.659 1.578 3.659 3.52v89.713c-.003 1.942-1.64 3.517-3.659 3.519H79.054c-.179 0-.353-.028-.527-.052V15.431zM30.262 75.757l-18.721-.46V72.37l11.3-16.673v-.148l-10.266.164v-4.51l17.504-.44v3.264L18.696 70.76v.144l11.566.176v4.678zm9.419.231l-5.823-.144V50.671l5.823-.144v25.461zm22.255-11.632c-2.168 1.922-5.353 2.76-9.02 2.736-.702.004-1.402-.04-2.097-.131v9.303l-5.997-.148V50.743c1.852-.352 4.473-.647 8.218-.743 3.838-.096 6.608.539 8.48 1.913 1.807 1.306 3.032 3.5 3.032 6.112s-.926 4.833-2.612 6.331h-.004zM53.36 54.45c-.856-.01-1.71.083-2.541.275v7.682c.523.116 1.167.152 2.06.152 3.301-.004 5.36-1.614 5.36-4.314 0-2.425-1.772-3.843-4.875-3.791l-.004-.004zm39.847-37.066h9.564v3.795h-9.564v-3.795zm-9.568 5.68h9.564v3.8h-9.564v-3.8zm9.568 6.216h9.564v3.799h-9.564V29.28zm0 12h9.564v3.794h-9.564V41.28zm-9.568-6.096h9.564v3.795h-9.564v-3.795zm9.472 47.064c2.512 0 4.921-.96 6.697-2.67 1.776-1.708 2.773-4.026 2.772-6.442l-1.748-15.263c0-5.033-2.492-9.112-7.725-9.112-5.232 0-7.72 4.079-7.72 9.112l-1.752 15.263c-.001 2.417.996 4.735 2.773 6.444 1.777 1.71 4.187 2.669 6.7 2.668h.003zm-3.135-16.75h6.27v12.743h-6.27V65.5z"/></svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/icons/svgo.yml b/ruoyi-ui/src/assets/icons/svgo.yml new file mode 100644 index 0000000..d11906a --- /dev/null +++ b/ruoyi-ui/src/assets/icons/svgo.yml @@ -0,0 +1,22 @@ +# replace default config + +# multipass: true +# full: true + +plugins: + + # - name + # + # or: + # - name: false + # - name: true + # + # or: + # - name: + # param1: 1 + # param2: 2 + +- removeAttrs: + attrs: + - 'fill' + - 'fill-rule' diff --git a/ruoyi-ui/src/assets/images/dark.svg b/ruoyi-ui/src/assets/images/dark.svg new file mode 100644 index 0000000..f646bd7 --- /dev/null +++ b/ruoyi-ui/src/assets/images/dark.svg @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> + <feMerge> + <feMergeNode in="shadowMatrixOuter1"></feMergeNode> + <feMergeNode in="SourceGraphic"></feMergeNode> + </feMerge> + </filter> + <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> + <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)"> + <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)"> + <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)"> + <mask id="mask-3" fill="white"> + <use xlink:href="#path-2"></use> + </mask> + <g id="Rectangle-18"> + <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> + <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> + <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/images/light.svg b/ruoyi-ui/src/assets/images/light.svg new file mode 100644 index 0000000..ab7cc08 --- /dev/null +++ b/ruoyi-ui/src/assets/images/light.svg @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> + <feMerge> + <feMergeNode in="shadowMatrixOuter1"></feMergeNode> + <feMergeNode in="SourceGraphic"></feMergeNode> + </feMerge> + </filter> + <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> + <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> + <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> + </filter> + </defs> + <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)"> + <g id="Group-8" transform="translate(1167.000000, 0.000000)"> + <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)"> + <mask id="mask-3" fill="white"> + <use xlink:href="#path-2"></use> + </mask> + <g id="Rectangle-18"> + <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> + <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> + </g> + <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> + <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> + </g> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/ruoyi-ui/src/assets/images/login-background.jpg b/ruoyi-ui/src/assets/images/login-background.jpg new file mode 100644 index 0000000..8a89eb8 --- /dev/null +++ b/ruoyi-ui/src/assets/images/login-background.jpg Binary files differ diff --git a/ruoyi-ui/src/assets/images/pay.png b/ruoyi-ui/src/assets/images/pay.png new file mode 100644 index 0000000..bb8b967 --- /dev/null +++ b/ruoyi-ui/src/assets/images/pay.png Binary files differ diff --git a/ruoyi-ui/src/assets/images/profile.jpg b/ruoyi-ui/src/assets/images/profile.jpg new file mode 100644 index 0000000..b3a940b --- /dev/null +++ b/ruoyi-ui/src/assets/images/profile.jpg Binary files differ diff --git a/ruoyi-ui/src/assets/logo/logo.png b/ruoyi-ui/src/assets/logo/logo.png new file mode 100644 index 0000000..e263760 --- /dev/null +++ b/ruoyi-ui/src/assets/logo/logo.png Binary files differ diff --git a/ruoyi-ui/src/assets/styles/btn.scss b/ruoyi-ui/src/assets/styles/btn.scss new file mode 100644 index 0000000..e6ba1a8 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/btn.scss @@ -0,0 +1,99 @@ +@import './variables.scss'; + +@mixin colorBtn($color) { + background: $color; + + &:hover { + color: $color; + + &:before, + &:after { + background: $color; + } + } +} + +.blue-btn { + @include colorBtn($blue) +} + +.light-blue-btn { + @include colorBtn($light-blue) +} + +.red-btn { + @include colorBtn($red) +} + +.pink-btn { + @include colorBtn($pink) +} + +.green-btn { + @include colorBtn($green) +} + +.tiffany-btn { + @include colorBtn($tiffany) +} + +.yellow-btn { + @include colorBtn($yellow) +} + +.pan-btn { + font-size: 14px; + color: #fff; + padding: 14px 36px; + border-radius: 8px; + border: none; + outline: none; + transition: 600ms ease all; + position: relative; + display: inline-block; + + &:hover { + background: #fff; + + &:before, + &:after { + width: 100%; + transition: 600ms ease all; + } + } + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 2px; + width: 0; + transition: 400ms ease all; + } + + &::after { + right: inherit; + top: inherit; + left: 0; + bottom: 0; + } +} + +.custom-button { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #fff; + color: #fff; + -webkit-appearance: none; + text-align: center; + box-sizing: border-box; + outline: 0; + margin: 0; + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; +} diff --git a/ruoyi-ui/src/assets/styles/element-ui.scss b/ruoyi-ui/src/assets/styles/element-ui.scss new file mode 100644 index 0000000..363092a --- /dev/null +++ b/ruoyi-ui/src/assets/styles/element-ui.scss @@ -0,0 +1,92 @@ +// cover some element-ui styles + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type="file"] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +.cell { + .el-tag { + margin-right: 0px; + } +} + +.small-padding { + .cell { + padding-left: 5px; + padding-right: 5px; + } +} + +.fixed-width { + .el-button--mini { + padding: 7px 10px; + width: 60px; + } +} + +.status-col { + .cell { + padding: 0 10px; + text-align: center; + + .el-tag { + margin-right: 0px; + } + } +} + +// to fixed https://github.com/ElemeFE/element/issues/2461 +.el-dialog { + transform: none; + left: 0; + position: relative; + margin: 0 auto; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block + } +} + +// fix date-picker ui bug in filter-item +.el-range-editor.el-input__inner { + display: inline-flex !important; +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +.el-menu--collapse + > div + > .el-submenu + > .el-submenu__title + .el-submenu__icon-arrow { + display: none; +} \ No newline at end of file diff --git a/ruoyi-ui/src/assets/styles/element-variables.scss b/ruoyi-ui/src/assets/styles/element-variables.scss new file mode 100644 index 0000000..1615ff2 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/element-variables.scss @@ -0,0 +1,31 @@ +/** +* I think element-ui's default theme color is too light for long-term use. +* So I modified the default color and you can modify it to your liking. +**/ + +/* theme color */ +$--color-primary: #1890ff; +$--color-success: #13ce66; +$--color-warning: #ffba00; +$--color-danger: #ff4949; +// $--color-info: #1E1E1E; + +$--button-font-weight: 400; + +// $--color-text-regular: #1f2d3d; + +$--border-color-light: #dfe4ed; +$--border-color-lighter: #e6ebf5; + +$--table-border: 1px solid #dfe6ec; + +/* icon font path, required */ +$--font-path: '~element-ui/lib/theme-chalk/fonts'; + +@import "~element-ui/packages/theme-chalk/src/index"; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + theme: $--color-primary; +} diff --git a/ruoyi-ui/src/assets/styles/index.scss b/ruoyi-ui/src/assets/styles/index.scss new file mode 100644 index 0000000..2f3b9ef --- /dev/null +++ b/ruoyi-ui/src/assets/styles/index.scss @@ -0,0 +1,182 @@ +@import './variables.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; +@import './btn.scss'; + +body { + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 20px; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 30px; +} + +.text-center { + text-align: center +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} diff --git a/ruoyi-ui/src/assets/styles/mixin.scss b/ruoyi-ui/src/assets/styles/mixin.scss new file mode 100644 index 0000000..06fa061 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/mixin.scss @@ -0,0 +1,66 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } + + @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/ruoyi-ui/src/assets/styles/ruoyi.scss b/ruoyi-ui/src/assets/styles/ruoyi.scss new file mode 100644 index 0000000..db8c29b --- /dev/null +++ b/ruoyi-ui/src/assets/styles/ruoyi.scss @@ -0,0 +1,277 @@ + /** + * 通用css样式布局处理 + * Copyright (c) 2019 ruoyi + */ + + /** 基础通用 **/ +.pt5 { + padding-top: 5px; +} +.pr5 { + padding-right: 5px; +} +.pb5 { + padding-bottom: 5px; +} +.mt5 { + margin-top: 5px; +} +.mr5 { + margin-right: 5px; +} +.mb5 { + margin-bottom: 5px; +} +.mb8 { + margin-bottom: 8px; +} +.ml5 { + margin-left: 5px; +} +.mt10 { + margin-top: 10px; +} +.mr10 { + margin-right: 10px; +} +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} +.mt20 { + margin-top: 20px; +} +.mr20 { + margin-right: 20px; +} +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-message-box__status + .el-message-box__message{ + word-break: break-word; +} + +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f8f8f9; + color: #515a6e; + height: 40px; + font-size: 13px; + } + } + .el-table__body-wrapper { + .el-button [class*="el-icon-"] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size:15px; + color:#6379bb; + border-bottom:1px solid #ddd; + margin:8px 10px 25px 10px; + padding-bottom:5px +} + +/** 表格布局 **/ +.pagination-container { + position: relative; + height: 25px; + margin-bottom: 10px; + margin-top: 15px; + padding: 10px 20px !important; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #FFFFFF none; + border-radius:4px; +} + +.pagination-container .el-pagination { + right: 0; + position: absolute; +} + +@media ( max-width : 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--mini { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine { + cursor: pointer; + margin-left: 5px; +} + +.el-table .el-dropdown, .el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px; +} + +.card-box { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost{ + opacity: .8; + color: #fff!important; + background: #42b983!important; +} + +.top-right-btn { + position: relative; + float: right; +} diff --git a/ruoyi-ui/src/assets/styles/sidebar.scss b/ruoyi-ui/src/assets/styles/sidebar.scss new file mode 100644 index 0000000..abe5b63 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/sidebar.scss @@ -0,0 +1,227 @@ +#app { + + .main-container { + height: 100%; + transition: margin-left .28s; + margin-left: $base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0!important; + } + + .sidebar-container { + -webkit-transition: width .28s; + transition: width 0.28s; + width: $base-sidebar-width !important; + background-color: $base-menu-background; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); + box-shadow: 2px 0 6px rgba(0,21,41,.35); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, .el-submenu__title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-submenu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .is-active > .el-submenu__title { + color: $base-menu-color-active !important; + } + + & .nest-menu .el-submenu>.el-submenu__title, + & .el-submenu .el-menu-item { + min-width: $base-sidebar-width !important; + + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .nest-menu .el-submenu>.el-submenu__title, + & .theme-dark .el-submenu .el-menu-item { + background-color: $base-sub-menu-background !important; + + &:hover { + background-color: $base-sub-menu-hover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-submenu { + overflow: hidden; + + &>.el-submenu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + } + } + + .el-menu--collapse { + .el-submenu { + &>.el-submenu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-submenu { + min-width: $base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: $base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$base-sidebar-width, 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-submenu>.el-submenu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + // the scroll bar appears when the subMenu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/ruoyi-ui/src/assets/styles/transition.scss b/ruoyi-ui/src/assets/styles/transition.scss new file mode 100644 index 0000000..073f8c6 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/transition.scss @@ -0,0 +1,49 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/ruoyi-ui/src/assets/styles/variables.scss b/ruoyi-ui/src/assets/styles/variables.scss new file mode 100644 index 0000000..34484d4 --- /dev/null +++ b/ruoyi-ui/src/assets/styles/variables.scss @@ -0,0 +1,54 @@ +// base color +$blue:#324157; +$light-blue:#3A71A8; +$red:#C03639; +$pink: #E65D6E; +$green: #30B08F; +$tiffany: #4AB7BD; +$yellow:#FEC171; +$panGreen: #30B08F; + +// 默认菜单主题风格 +$base-menu-color:#bfcbd9; +$base-menu-color-active:#f4f4f5; +$base-menu-background:#304156; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#1f2d3d; +$base-sub-menu-hover:#001528; + +// 自定义暗色菜单风格 +/** +$base-menu-color:hsla(0,0%,100%,.65); +$base-menu-color-active:#fff; +$base-menu-background:#001529; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#000c17; +$base-sub-menu-hover:#001528; +*/ + +$base-sidebar-width: 200px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuColor: $base-menu-color; + menuLightColor: $base-menu-light-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + menuLightBackground: $base-menu-light-background; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + sideBarWidth: $base-sidebar-width; + logoTitleColor: $base-logo-title-color; + logoLightTitleColor: $base-logo-light-title-color +} diff --git a/ruoyi-ui/src/components/Breadcrumb/index.vue b/ruoyi-ui/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..1696f54 --- /dev/null +++ b/ruoyi-ui/src/components/Breadcrumb/index.vue @@ -0,0 +1,74 @@ +<template> + <el-breadcrumb class="app-breadcrumb" separator="/"> + <transition-group name="breadcrumb"> + <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path"> + <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span> + <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> + </el-breadcrumb-item> + </transition-group> + </el-breadcrumb> +</template> + +<script> +export default { + data() { + return { + levelList: null + } + }, + watch: { + $route(route) { + // if you go to the redirect page, do not update the breadcrumbs + if (route.path.startsWith('/redirect/')) { + return + } + this.getBreadcrumb() + } + }, + created() { + this.getBreadcrumb() + }, + methods: { + getBreadcrumb() { + // only show routes with meta.title + let matched = this.$route.matched.filter(item => item.meta && item.meta.title) + const first = matched[0] + + if (!this.isDashboard(first)) { + matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched) + } + + this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false) + }, + isDashboard(route) { + const name = route && route.name + if (!name) { + return false + } + return name.trim() === 'Index' + }, + handleLink(item) { + const { redirect, path } = item + if (redirect) { + this.$router.push(redirect) + return + } + this.$router.push(path) + } + } +} +</script> + +<style lang="scss" scoped> +.app-breadcrumb.el-breadcrumb { + display: inline-block; + font-size: 14px; + line-height: 50px; + margin-left: 8px; + + .no-redirect { + color: #97a8be; + cursor: text; + } +} +</style> diff --git a/ruoyi-ui/src/components/Crontab/day.vue b/ruoyi-ui/src/components/Crontab/day.vue new file mode 100644 index 0000000..fe3eaf0 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/day.vue @@ -0,0 +1,161 @@ +<template> + <el-form size="small"> + <el-form-item> + <el-radio v-model='radioValue' :label="1"> + 日,允许的通配符[, - * ? / L W] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="2"> + 不指定 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="3"> + 周期从 + <el-input-number v-model='cycle01' :min="1" :max="30" /> - + <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="31" /> 日 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="4"> + 从 + <el-input-number v-model='average01' :min="1" :max="30" /> 号开始,每 + <el-input-number v-model='average02' :min="1" :max="31 - average01 || 1" /> 日执行一次 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="5"> + 每月 + <el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="6"> + 本月最后一天 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="7"> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> + <el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option> + </el-select> + </el-radio> + </el-form-item> + </el-form> +</template> + +<script> +export default { + data() { + return { + radioValue: 1, + workday: 1, + cycle01: 1, + cycle02: 2, + average01: 1, + average02: 1, + checkboxList: [], + checkNum: this.$options.propsData.check + } + }, + name: 'crontab-day', + props: ['check', 'cron'], + methods: { + // 单选按钮值变化时 + radioChange() { + ('day rachange'); + if (this.radioValue !== 2 && this.cron.week !== '?') { + this.$emit('update', 'week', '?', 'day') + } + + switch (this.radioValue) { + case 1: + this.$emit('update', 'day', '*'); + break; + case 2: + this.$emit('update', 'day', '?'); + break; + case 3: + this.$emit('update', 'day', this.cycleTotal); + break; + case 4: + this.$emit('update', 'day', this.averageTotal); + break; + case 5: + this.$emit('update', 'day', this.workday + 'W'); + break; + case 6: + this.$emit('update', 'day', 'L'); + break; + case 7: + this.$emit('update', 'day', this.checkboxString); + break; + } + ('day rachange end'); + }, + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '3') { + this.$emit('update', 'day', this.cycleTotal); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '4') { + this.$emit('update', 'day', this.averageTotal); + } + }, + // 最近工作日值变化时 + workdayChange() { + if (this.radioValue == '5') { + this.$emit('update', 'day', this.workdayCheck + 'W'); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '7') { + this.$emit('update', 'day', this.checkboxString); + } + } + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'workdayCheck': 'workdayChange', + 'checkboxString': 'checkboxChange', + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + const cycle01 = this.checkNum(this.cycle01, 1, 30) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31) + return cycle01 + '-' + cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + const average01 = this.checkNum(this.average01, 1, 30) + const average02 = this.checkNum(this.average02, 1, 31 - average01 || 0) + return average01 + '/' + average02; + }, + // 计算工作日格式 + workdayCheck: function () { + const workday = this.checkNum(this.workday, 1, 31) + return workday; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str == '' ? '*' : str; + } + } +} +</script> diff --git a/ruoyi-ui/src/components/Crontab/hour.vue b/ruoyi-ui/src/components/Crontab/hour.vue new file mode 100644 index 0000000..4b1f1fc --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/hour.vue @@ -0,0 +1,114 @@ +<template> + <el-form size="small"> + <el-form-item> + <el-radio v-model='radioValue' :label="1"> + 小时,允许的通配符[, - * /] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="2"> + 周期从 + <el-input-number v-model='cycle01' :min="0" :max="22" /> - + <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="23" /> 小时 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="3"> + 从 + <el-input-number v-model='average01' :min="0" :max="22" /> 小时开始,每 + <el-input-number v-model='average02' :min="1" :max="23 - average01 || 0" /> 小时执行一次 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="4"> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> + <el-option v-for="item in 24" :key="item" :value="item-1">{{item-1}}</el-option> + </el-select> + </el-radio> + </el-form-item> + </el-form> +</template> + +<script> +export default { + data() { + return { + radioValue: 1, + cycle01: 0, + cycle02: 1, + average01: 0, + average02: 1, + checkboxList: [], + checkNum: this.$options.propsData.check + } + }, + name: 'crontab-hour', + props: ['check', 'cron'], + methods: { + // 单选按钮值变化时 + radioChange() { + switch (this.radioValue) { + case 1: + this.$emit('update', 'hour', '*') + break; + case 2: + this.$emit('update', 'hour', this.cycleTotal); + break; + case 3: + this.$emit('update', 'hour', this.averageTotal); + break; + case 4: + this.$emit('update', 'hour', this.checkboxString); + break; + } + }, + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '2') { + this.$emit('update', 'hour', this.cycleTotal); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '3') { + this.$emit('update', 'hour', this.averageTotal); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '4') { + this.$emit('update', 'hour', this.checkboxString); + } + } + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'checkboxString': 'checkboxChange' + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + const cycle01 = this.checkNum(this.cycle01, 0, 22) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 23) + return cycle01 + '-' + cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + const average01 = this.checkNum(this.average01, 0, 22) + const average02 = this.checkNum(this.average02, 1, 23 - average01 || 0) + return average01 + '/' + average02; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str == '' ? '*' : str; + } + } +} +</script> diff --git a/ruoyi-ui/src/components/Crontab/index.vue b/ruoyi-ui/src/components/Crontab/index.vue new file mode 100644 index 0000000..3963df2 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/index.vue @@ -0,0 +1,430 @@ +<template> + <div> + <el-tabs type="border-card"> + <el-tab-pane label="秒" v-if="shouldHide('second')"> + <CrontabSecond + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronsecond" + /> + </el-tab-pane> + + <el-tab-pane label="分钟" v-if="shouldHide('min')"> + <CrontabMin + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronmin" + /> + </el-tab-pane> + + <el-tab-pane label="小时" v-if="shouldHide('hour')"> + <CrontabHour + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronhour" + /> + </el-tab-pane> + + <el-tab-pane label="日" v-if="shouldHide('day')"> + <CrontabDay + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronday" + /> + </el-tab-pane> + + <el-tab-pane label="月" v-if="shouldHide('month')"> + <CrontabMonth + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronmonth" + /> + </el-tab-pane> + + <el-tab-pane label="周" v-if="shouldHide('week')"> + <CrontabWeek + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronweek" + /> + </el-tab-pane> + + <el-tab-pane label="年" v-if="shouldHide('year')"> + <CrontabYear + @update="updateCrontabValue" + :check="checkNumber" + :cron="crontabValueObj" + ref="cronyear" + /> + </el-tab-pane> + </el-tabs> + + <div class="popup-main"> + <div class="popup-result"> + <p class="title">时间表达式</p> + <table> + <thead> + <th v-for="item of tabTitles" width="40" :key="item">{{item}}</th> + <th>Cron 表达式</th> + </thead> + <tbody> + <td> + <span>{{crontabValueObj.second}}</span> + </td> + <td> + <span>{{crontabValueObj.min}}</span> + </td> + <td> + <span>{{crontabValueObj.hour}}</span> + </td> + <td> + <span>{{crontabValueObj.day}}</span> + </td> + <td> + <span>{{crontabValueObj.month}}</span> + </td> + <td> + <span>{{crontabValueObj.week}}</span> + </td> + <td> + <span>{{crontabValueObj.year}}</span> + </td> + <td> + <span>{{crontabValueString}}</span> + </td> + </tbody> + </table> + </div> + <CrontabResult :ex="crontabValueString"></CrontabResult> + + <div class="pop_btn"> + <el-button size="small" type="primary" @click="submitFill">确定</el-button> + <el-button size="small" type="warning" @click="clearCron">重置</el-button> + <el-button size="small" @click="hidePopup">取消</el-button> + </div> + </div> + </div> +</template> + +<script> +import CrontabSecond from "./second.vue"; +import CrontabMin from "./min.vue"; +import CrontabHour from "./hour.vue"; +import CrontabDay from "./day.vue"; +import CrontabMonth from "./month.vue"; +import CrontabWeek from "./week.vue"; +import CrontabYear from "./year.vue"; +import CrontabResult from "./result.vue"; + +export default { + data() { + return { + tabTitles: ["秒", "分钟", "小时", "日", "月", "周", "年"], + tabActive: 0, + myindex: 0, + crontabValueObj: { + second: "*", + min: "*", + hour: "*", + day: "*", + month: "*", + week: "?", + year: "", + }, + }; + }, + name: "vcrontab", + props: ["expression", "hideComponent"], + methods: { + shouldHide(key) { + if (this.hideComponent && this.hideComponent.includes(key)) return false; + return true; + }, + resolveExp() { + // 反解析 表达式 + if (this.expression) { + let arr = this.expression.split(" "); + if (arr.length >= 6) { + //6 位以上是合法表达式 + let obj = { + second: arr[0], + min: arr[1], + hour: arr[2], + day: arr[3], + month: arr[4], + week: arr[5], + year: arr[6] ? arr[6] : "", + }; + this.crontabValueObj = { + ...obj, + }; + for (let i in obj) { + if (obj[i]) this.changeRadio(i, obj[i]); + } + } + } else { + // 没有传入的表达式 则还原 + this.clearCron(); + } + }, + // tab切换值 + tabCheck(index) { + this.tabActive = index; + }, + // 由子组件触发,更改表达式组成的字段值 + updateCrontabValue(name, value, from) { + "updateCrontabValue", name, value, from; + this.crontabValueObj[name] = value; + if (from && from !== name) { + console.log(`来自组件 ${from} 改变了 ${name} ${value}`); + this.changeRadio(name, value); + } + }, + // 赋值到组件 + changeRadio(name, value) { + let arr = ["second", "min", "hour", "month"], + refName = "cron" + name, + insValue; + + if (!this.$refs[refName]) return; + + if (arr.includes(name)) { + if (value === "*") { + insValue = 1; + } else if (value.indexOf("-") > -1) { + let indexArr = value.split("-"); + isNaN(indexArr[0]) + ? (this.$refs[refName].cycle01 = 0) + : (this.$refs[refName].cycle01 = indexArr[0]); + this.$refs[refName].cycle02 = indexArr[1]; + insValue = 2; + } else if (value.indexOf("/") > -1) { + let indexArr = value.split("/"); + isNaN(indexArr[0]) + ? (this.$refs[refName].average01 = 0) + : (this.$refs[refName].average01 = indexArr[0]); + this.$refs[refName].average02 = indexArr[1]; + insValue = 3; + } else { + insValue = 4; + this.$refs[refName].checkboxList = value.split(","); + } + } else if (name == "day") { + if (value === "*") { + insValue = 1; + } else if (value == "?") { + insValue = 2; + } else if (value.indexOf("-") > -1) { + let indexArr = value.split("-"); + isNaN(indexArr[0]) + ? (this.$refs[refName].cycle01 = 0) + : (this.$refs[refName].cycle01 = indexArr[0]); + this.$refs[refName].cycle02 = indexArr[1]; + insValue = 3; + } else if (value.indexOf("/") > -1) { + let indexArr = value.split("/"); + isNaN(indexArr[0]) + ? (this.$refs[refName].average01 = 0) + : (this.$refs[refName].average01 = indexArr[0]); + this.$refs[refName].average02 = indexArr[1]; + insValue = 4; + } else if (value.indexOf("W") > -1) { + let indexArr = value.split("W"); + isNaN(indexArr[0]) + ? (this.$refs[refName].workday = 0) + : (this.$refs[refName].workday = indexArr[0]); + insValue = 5; + } else if (value === "L") { + insValue = 6; + } else { + this.$refs[refName].checkboxList = value.split(","); + insValue = 7; + } + } else if (name == "week") { + if (value === "*") { + insValue = 1; + } else if (value == "?") { + insValue = 2; + } else if (value.indexOf("-") > -1) { + let indexArr = value.split("-"); + isNaN(indexArr[0]) + ? (this.$refs[refName].cycle01 = 0) + : (this.$refs[refName].cycle01 = indexArr[0]); + this.$refs[refName].cycle02 = indexArr[1]; + insValue = 3; + } else if (value.indexOf("#") > -1) { + let indexArr = value.split("#"); + isNaN(indexArr[0]) + ? (this.$refs[refName].average01 = 1) + : (this.$refs[refName].average01 = indexArr[0]); + this.$refs[refName].average02 = indexArr[1]; + insValue = 4; + } else if (value.indexOf("L") > -1) { + let indexArr = value.split("L"); + isNaN(indexArr[0]) + ? (this.$refs[refName].weekday = 1) + : (this.$refs[refName].weekday = indexArr[0]); + insValue = 5; + } else { + this.$refs[refName].checkboxList = value.split(","); + insValue = 6; + } + } else if (name == "year") { + if (value == "") { + insValue = 1; + } else if (value == "*") { + insValue = 2; + } else if (value.indexOf("-") > -1) { + insValue = 3; + } else if (value.indexOf("/") > -1) { + insValue = 4; + } else { + this.$refs[refName].checkboxList = value.split(","); + insValue = 5; + } + } + this.$refs[refName].radioValue = insValue; + }, + // 表单选项的子组件校验数字格式(通过-props传递) + checkNumber(value, minLimit, maxLimit) { + // 检查必须为整数 + value = Math.floor(value); + if (value < minLimit) { + value = minLimit; + } else if (value > maxLimit) { + value = maxLimit; + } + return value; + }, + // 隐藏弹窗 + hidePopup() { + this.$emit("hide"); + }, + // 填充表达式 + submitFill() { + this.$emit("fill", this.crontabValueString); + this.hidePopup(); + }, + clearCron() { + // 还原选择项 + ("准备还原"); + this.crontabValueObj = { + second: "*", + min: "*", + hour: "*", + day: "*", + month: "*", + week: "?", + year: "", + }; + for (let j in this.crontabValueObj) { + this.changeRadio(j, this.crontabValueObj[j]); + } + }, + }, + computed: { + crontabValueString: function() { + let obj = this.crontabValueObj; + let str = + obj.second + + " " + + obj.min + + " " + + obj.hour + + " " + + obj.day + + " " + + obj.month + + " " + + obj.week + + (obj.year == "" ? "" : " " + obj.year); + return str; + }, + }, + components: { + CrontabSecond, + CrontabMin, + CrontabHour, + CrontabDay, + CrontabMonth, + CrontabWeek, + CrontabYear, + CrontabResult, + }, + watch: { + expression: "resolveExp", + hideComponent(value) { + // 隐藏部分组件 + }, + }, + mounted: function() { + this.resolveExp(); + }, +}; +</script> +<style scoped> +.pop_btn { + text-align: center; + margin-top: 20px; +} +.popup-main { + position: relative; + margin: 10px auto; + background: #fff; + border-radius: 5px; + font-size: 12px; + overflow: hidden; +} +.popup-title { + overflow: hidden; + line-height: 34px; + padding-top: 6px; + background: #f2f2f2; +} +.popup-result { + box-sizing: border-box; + line-height: 24px; + margin: 25px auto; + padding: 15px 10px 10px; + border: 1px solid #ccc; + position: relative; +} +.popup-result .title { + position: absolute; + top: -28px; + left: 50%; + width: 140px; + font-size: 14px; + margin-left: -70px; + text-align: center; + line-height: 30px; + background: #fff; +} +.popup-result table { + text-align: center; + width: 100%; + margin: 0 auto; +} +.popup-result table span { + display: block; + width: 100%; + font-family: arial; + line-height: 30px; + height: 30px; + white-space: nowrap; + overflow: hidden; + border: 1px solid #e8e8e8; +} +.popup-result-scroll { + font-size: 12px; + line-height: 24px; + height: 10em; + overflow-y: auto; +} +</style> diff --git a/ruoyi-ui/src/components/Crontab/min.vue b/ruoyi-ui/src/components/Crontab/min.vue new file mode 100644 index 0000000..43cab90 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/min.vue @@ -0,0 +1,116 @@ +<template> + <el-form size="small"> + <el-form-item> + <el-radio v-model='radioValue' :label="1"> + 分钟,允许的通配符[, - * /] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="2"> + 周期从 + <el-input-number v-model='cycle01' :min="0" :max="58" /> - + <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="3"> + 从 + <el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始,每 + <el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="4"> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> + <el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option> + </el-select> + </el-radio> + </el-form-item> + </el-form> + +</template> + +<script> +export default { + data() { + return { + radioValue: 1, + cycle01: 1, + cycle02: 2, + average01: 0, + average02: 1, + checkboxList: [], + checkNum: this.$options.propsData.check + } + }, + name: 'crontab-min', + props: ['check', 'cron'], + methods: { + // 单选按钮值变化时 + radioChange() { + switch (this.radioValue) { + case 1: + this.$emit('update', 'min', '*', 'min'); + break; + case 2: + this.$emit('update', 'min', this.cycleTotal, 'min'); + break; + case 3: + this.$emit('update', 'min', this.averageTotal, 'min'); + break; + case 4: + this.$emit('update', 'min', this.checkboxString, 'min'); + break; + } + }, + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '2') { + this.$emit('update', 'min', this.cycleTotal, 'min'); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '3') { + this.$emit('update', 'min', this.averageTotal, 'min'); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '4') { + this.$emit('update', 'min', this.checkboxString, 'min'); + } + }, + + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'checkboxString': 'checkboxChange', + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + const cycle01 = this.checkNum(this.cycle01, 0, 58) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59) + return cycle01 + '-' + cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + const average01 = this.checkNum(this.average01, 0, 58) + const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0) + return average01 + '/' + average02; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str == '' ? '*' : str; + } + } +} +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/components/Crontab/month.vue b/ruoyi-ui/src/components/Crontab/month.vue new file mode 100644 index 0000000..fd0ac38 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/month.vue @@ -0,0 +1,114 @@ +<template> + <el-form size='small'> + <el-form-item> + <el-radio v-model='radioValue' :label="1"> + 月,允许的通配符[, - * /] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="2"> + 周期从 + <el-input-number v-model='cycle01' :min="1" :max="11" /> - + <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="12" /> 月 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="3"> + 从 + <el-input-number v-model='average01' :min="1" :max="11" /> 月开始,每 + <el-input-number v-model='average02' :min="1" :max="12 - average01 || 0" /> 月月执行一次 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="4"> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> + <el-option v-for="item in 12" :key="item" :value="item">{{item}}</el-option> + </el-select> + </el-radio> + </el-form-item> + </el-form> +</template> + +<script> +export default { + data() { + return { + radioValue: 1, + cycle01: 1, + cycle02: 2, + average01: 1, + average02: 1, + checkboxList: [], + checkNum: this.check + } + }, + name: 'crontab-month', + props: ['check', 'cron'], + methods: { + // 单选按钮值变化时 + radioChange() { + switch (this.radioValue) { + case 1: + this.$emit('update', 'month', '*'); + break; + case 2: + this.$emit('update', 'month', this.cycleTotal); + break; + case 3: + this.$emit('update', 'month', this.averageTotal); + break; + case 4: + this.$emit('update', 'month', this.checkboxString); + break; + } + }, + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '2') { + this.$emit('update', 'month', this.cycleTotal); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '3') { + this.$emit('update', 'month', this.averageTotal); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '4') { + this.$emit('update', 'month', this.checkboxString); + } + } + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'checkboxString': 'checkboxChange' + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + const cycle01 = this.checkNum(this.cycle01, 1, 11) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12) + return cycle01 + '-' + cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + const average01 = this.checkNum(this.average01, 1, 11) + const average02 = this.checkNum(this.average02, 1, 12 - average01 || 0) + return average01 + '/' + average02; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str == '' ? '*' : str; + } + } +} +</script> diff --git a/ruoyi-ui/src/components/Crontab/result.vue b/ruoyi-ui/src/components/Crontab/result.vue new file mode 100644 index 0000000..aea6e0e --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/result.vue @@ -0,0 +1,559 @@ +<template> + <div class="popup-result"> + <p class="title">最近5次运行时间</p> + <ul class="popup-result-scroll"> + <template v-if='isShow'> + <li v-for='item in resultList' :key="item">{{item}}</li> + </template> + <li v-else>计算结果中...</li> + </ul> + </div> +</template> + +<script> +export default { + data() { + return { + dayRule: '', + dayRuleSup: '', + dateArr: [], + resultList: [], + isShow: false + } + }, + name: 'crontab-result', + methods: { + // 表达式值变化时,开始去计算结果 + expressionChange() { + + // 计算开始-隐藏结果 + this.isShow = false; + // 获取规则数组[0秒、1分、2时、3日、4月、5星期、6年] + let ruleArr = this.$options.propsData.ex.split(' '); + // 用于记录进入循环的次数 + let nums = 0; + // 用于暂时存符号时间规则结果的数组 + let resultArr = []; + // 获取当前时间精确至[年、月、日、时、分、秒] + let nTime = new Date(); + let nYear = nTime.getFullYear(); + let nMonth = nTime.getMonth() + 1; + let nDay = nTime.getDate(); + let nHour = nTime.getHours(); + let nMin = nTime.getMinutes(); + let nSecond = nTime.getSeconds(); + // 根据规则获取到近100年可能年数组、月数组等等 + this.getSecondArr(ruleArr[0]); + this.getMinArr(ruleArr[1]); + this.getHourArr(ruleArr[2]); + this.getDayArr(ruleArr[3]); + this.getMonthArr(ruleArr[4]); + this.getWeekArr(ruleArr[5]); + this.getYearArr(ruleArr[6], nYear); + // 将获取到的数组赋值-方便使用 + let sDate = this.dateArr[0]; + let mDate = this.dateArr[1]; + let hDate = this.dateArr[2]; + let DDate = this.dateArr[3]; + let MDate = this.dateArr[4]; + let YDate = this.dateArr[5]; + // 获取当前时间在数组中的索引 + let sIdx = this.getIndex(sDate, nSecond); + let mIdx = this.getIndex(mDate, nMin); + let hIdx = this.getIndex(hDate, nHour); + let DIdx = this.getIndex(DDate, nDay); + let MIdx = this.getIndex(MDate, nMonth); + let YIdx = this.getIndex(YDate, nYear); + // 重置月日时分秒的函数(后面用的比较多) + const resetSecond = function () { + sIdx = 0; + nSecond = sDate[sIdx] + } + const resetMin = function () { + mIdx = 0; + nMin = mDate[mIdx] + resetSecond(); + } + const resetHour = function () { + hIdx = 0; + nHour = hDate[hIdx] + resetMin(); + } + const resetDay = function () { + DIdx = 0; + nDay = DDate[DIdx] + resetHour(); + } + const resetMonth = function () { + MIdx = 0; + nMonth = MDate[MIdx] + resetDay(); + } + // 如果当前年份不为数组中当前值 + if (nYear !== YDate[YIdx]) { + resetMonth(); + } + // 如果当前月份不为数组中当前值 + if (nMonth !== MDate[MIdx]) { + resetDay(); + } + // 如果当前“日”不为数组中当前值 + if (nDay !== DDate[DIdx]) { + resetHour(); + } + // 如果当前“时”不为数组中当前值 + if (nHour !== hDate[hIdx]) { + resetMin(); + } + // 如果当前“分”不为数组中当前值 + if (nMin !== mDate[mIdx]) { + resetSecond(); + } + + // 循环年份数组 + goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) { + let YY = YDate[Yi]; + // 如果到达最大值时 + if (nMonth > MDate[MDate.length - 1]) { + resetMonth(); + continue; + } + // 循环月份数组 + goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) { + // 赋值、方便后面运算 + let MM = MDate[Mi]; + MM = MM < 10 ? '0' + MM : MM; + // 如果到达最大值时 + if (nDay > DDate[DDate.length - 1]) { + resetDay(); + if (Mi == MDate.length - 1) { + resetMonth(); + continue goYear; + } + continue; + } + // 循环日期数组 + goDay: for (let Di = DIdx; Di < DDate.length; Di++) { + // 赋值、方便后面运算 + let DD = DDate[Di]; + let thisDD = DD < 10 ? '0' + DD : DD; + + // 如果到达最大值时 + if (nHour > hDate[hDate.length - 1]) { + resetHour(); + if (Di == DDate.length - 1) { + resetDay(); + if (Mi == MDate.length - 1) { + resetMonth(); + continue goYear; + } + continue goMonth; + } + continue; + } + + // 判断日期的合法性,不合法的话也是跳出当前循环 + if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && this.dayRule !== 'workDay' && this.dayRule !== 'lastWeek' && this.dayRule !== 'lastDay') { + resetDay(); + continue goMonth; + } + // 如果日期规则中有值时 + if (this.dayRule == 'lastDay') { + // 如果不是合法日期则需要将前将日期调到合法日期即月末最后一天 + + if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + DD--; + + thisDD = DD < 10 ? '0' + DD : DD; + } + } + } else if (this.dayRule == 'workDay') { + // 校验并调整如果是2月30号这种日期传进来时需调整至正常月底 + if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + DD--; + thisDD = DD < 10 ? '0' + DD : DD; + } + } + // 获取达到条件的日期是星期X + let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week'); + // 当星期日时 + if (thisWeek == 1) { + // 先找下一个日,并判断是否为月底 + DD++; + thisDD = DD < 10 ? '0' + DD : DD; + // 判断下一日已经不是合法日期 + if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + DD -= 3; + } + } else if (thisWeek == 7) { + // 当星期6时只需判断不是1号就可进行操作 + if (this.dayRuleSup !== 1) { + DD--; + } else { + DD += 2; + } + } + } else if (this.dayRule == 'weekDay') { + // 如果指定了是星期几 + // 获取当前日期是属于星期几 + let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week'); + // 校验当前星期是否在星期池(dayRuleSup)中 + if (this.dayRuleSup.indexOf(thisWeek) < 0) { + // 如果到达最大值时 + if (Di == DDate.length - 1) { + resetDay(); + if (Mi == MDate.length - 1) { + resetMonth(); + continue goYear; + } + continue goMonth; + } + continue; + } + } else if (this.dayRule == 'assWeek') { + // 如果指定了是第几周的星期几 + // 获取每月1号是属于星期几 + let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week'); + if (this.dayRuleSup[1] >= thisWeek) { + DD = (this.dayRuleSup[0] - 1) * 7 + this.dayRuleSup[1] - thisWeek + 1; + } else { + DD = this.dayRuleSup[0] * 7 + this.dayRuleSup[1] - thisWeek + 1; + } + } else if (this.dayRule == 'lastWeek') { + // 如果指定了每月最后一个星期几 + // 校验并调整如果是2月30号这种日期传进来时需调整至正常月底 + if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { + DD--; + thisDD = DD < 10 ? '0' + DD : DD; + } + } + // 获取月末最后一天是星期几 + let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week'); + // 找到要求中最近的那个星期几 + if (this.dayRuleSup < thisWeek) { + DD -= thisWeek - this.dayRuleSup; + } else if (this.dayRuleSup > thisWeek) { + DD -= 7 - (this.dayRuleSup - thisWeek) + } + } + // 判断时间值是否小于10置换成“05”这种格式 + DD = DD < 10 ? '0' + DD : DD; + + // 循环“时”数组 + goHour: for (let hi = hIdx; hi < hDate.length; hi++) { + let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi] + + // 如果到达最大值时 + if (nMin > mDate[mDate.length - 1]) { + resetMin(); + if (hi == hDate.length - 1) { + resetHour(); + if (Di == DDate.length - 1) { + resetDay(); + if (Mi == MDate.length - 1) { + resetMonth(); + continue goYear; + } + continue goMonth; + } + continue goDay; + } + continue; + } + // 循环"分"数组 + goMin: for (let mi = mIdx; mi < mDate.length; mi++) { + let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi]; + + // 如果到达最大值时 + if (nSecond > sDate[sDate.length - 1]) { + resetSecond(); + if (mi == mDate.length - 1) { + resetMin(); + if (hi == hDate.length - 1) { + resetHour(); + if (Di == DDate.length - 1) { + resetDay(); + if (Mi == MDate.length - 1) { + resetMonth(); + continue goYear; + } + continue goMonth; + } + continue goDay; + } + continue goHour; + } + continue; + } + // 循环"秒"数组 + goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) { + let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si]; + // 添加当前时间(时间合法性在日期循环时已经判断) + if (MM !== '00' && DD !== '00') { + resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss) + nums++; + } + // 如果条数满了就退出循环 + if (nums == 5) break goYear; + // 如果到达最大值时 + if (si == sDate.length - 1) { + resetSecond(); + if (mi == mDate.length - 1) { + resetMin(); + if (hi == hDate.length - 1) { + resetHour(); + if (Di == DDate.length - 1) { + resetDay(); + if (Mi == MDate.length - 1) { + resetMonth(); + continue goYear; + } + continue goMonth; + } + continue goDay; + } + continue goHour; + } + continue goMin; + } + } //goSecond + } //goMin + }//goHour + }//goDay + }//goMonth + } + // 判断100年内的结果条数 + if (resultArr.length == 0) { + this.resultList = ['没有达到条件的结果!']; + } else { + this.resultList = resultArr; + if (resultArr.length !== 5) { + this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!') + } + } + // 计算完成-显示结果 + this.isShow = true; + + + }, + // 用于计算某位数字在数组中的索引 + getIndex(arr, value) { + if (value <= arr[0] || value > arr[arr.length - 1]) { + return 0; + } else { + for (let i = 0; i < arr.length - 1; i++) { + if (value > arr[i] && value <= arr[i + 1]) { + return i + 1; + } + } + } + }, + // 获取"年"数组 + getYearArr(rule, year) { + this.dateArr[5] = this.getOrderArr(year, year + 100); + if (rule !== undefined) { + if (rule.indexOf('-') >= 0) { + this.dateArr[5] = this.getCycleArr(rule, year + 100, false) + } else if (rule.indexOf('/') >= 0) { + this.dateArr[5] = this.getAverageArr(rule, year + 100) + } else if (rule !== '*') { + this.dateArr[5] = this.getAssignArr(rule) + } + } + }, + // 获取"月"数组 + getMonthArr(rule) { + this.dateArr[4] = this.getOrderArr(1, 12); + if (rule.indexOf('-') >= 0) { + this.dateArr[4] = this.getCycleArr(rule, 12, false) + } else if (rule.indexOf('/') >= 0) { + this.dateArr[4] = this.getAverageArr(rule, 12) + } else if (rule !== '*') { + this.dateArr[4] = this.getAssignArr(rule) + } + }, + // 获取"日"数组-主要为日期规则 + getWeekArr(rule) { + // 只有当日期规则的两个值均为“”时则表达日期是有选项的 + if (this.dayRule == '' && this.dayRuleSup == '') { + if (rule.indexOf('-') >= 0) { + this.dayRule = 'weekDay'; + this.dayRuleSup = this.getCycleArr(rule, 7, false) + } else if (rule.indexOf('#') >= 0) { + this.dayRule = 'assWeek'; + let matchRule = rule.match(/[0-9]{1}/g); + this.dayRuleSup = [Number(matchRule[1]), Number(matchRule[0])]; + this.dateArr[3] = [1]; + if (this.dayRuleSup[1] == 7) { + this.dayRuleSup[1] = 0; + } + } else if (rule.indexOf('L') >= 0) { + this.dayRule = 'lastWeek'; + this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]); + this.dateArr[3] = [31]; + if (this.dayRuleSup == 7) { + this.dayRuleSup = 0; + } + } else if (rule !== '*' && rule !== '?') { + this.dayRule = 'weekDay'; + this.dayRuleSup = this.getAssignArr(rule) + } + } + }, + // 获取"日"数组-少量为日期规则 + getDayArr(rule) { + this.dateArr[3] = this.getOrderArr(1, 31); + this.dayRule = ''; + this.dayRuleSup = ''; + if (rule.indexOf('-') >= 0) { + this.dateArr[3] = this.getCycleArr(rule, 31, false) + this.dayRuleSup = 'null'; + } else if (rule.indexOf('/') >= 0) { + this.dateArr[3] = this.getAverageArr(rule, 31) + this.dayRuleSup = 'null'; + } else if (rule.indexOf('W') >= 0) { + this.dayRule = 'workDay'; + this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]); + this.dateArr[3] = [this.dayRuleSup]; + } else if (rule.indexOf('L') >= 0) { + this.dayRule = 'lastDay'; + this.dayRuleSup = 'null'; + this.dateArr[3] = [31]; + } else if (rule !== '*' && rule !== '?') { + this.dateArr[3] = this.getAssignArr(rule) + this.dayRuleSup = 'null'; + } else if (rule == '*') { + this.dayRuleSup = 'null'; + } + }, + // 获取"时"数组 + getHourArr(rule) { + this.dateArr[2] = this.getOrderArr(0, 23); + if (rule.indexOf('-') >= 0) { + this.dateArr[2] = this.getCycleArr(rule, 24, true) + } else if (rule.indexOf('/') >= 0) { + this.dateArr[2] = this.getAverageArr(rule, 23) + } else if (rule !== '*') { + this.dateArr[2] = this.getAssignArr(rule) + } + }, + // 获取"分"数组 + getMinArr(rule) { + this.dateArr[1] = this.getOrderArr(0, 59); + if (rule.indexOf('-') >= 0) { + this.dateArr[1] = this.getCycleArr(rule, 60, true) + } else if (rule.indexOf('/') >= 0) { + this.dateArr[1] = this.getAverageArr(rule, 59) + } else if (rule !== '*') { + this.dateArr[1] = this.getAssignArr(rule) + } + }, + // 获取"秒"数组 + getSecondArr(rule) { + this.dateArr[0] = this.getOrderArr(0, 59); + if (rule.indexOf('-') >= 0) { + this.dateArr[0] = this.getCycleArr(rule, 60, true) + } else if (rule.indexOf('/') >= 0) { + this.dateArr[0] = this.getAverageArr(rule, 59) + } else if (rule !== '*') { + this.dateArr[0] = this.getAssignArr(rule) + } + }, + // 根据传进来的min-max返回一个顺序的数组 + getOrderArr(min, max) { + let arr = []; + for (let i = min; i <= max; i++) { + arr.push(i); + } + return arr; + }, + // 根据规则中指定的零散值返回一个数组 + getAssignArr(rule) { + let arr = []; + let assiginArr = rule.split(','); + for (let i = 0; i < assiginArr.length; i++) { + arr[i] = Number(assiginArr[i]) + } + arr.sort(this.compare) + return arr; + }, + // 根据一定算术规则计算返回一个数组 + getAverageArr(rule, limit) { + let arr = []; + let agArr = rule.split('/'); + let min = Number(agArr[0]); + let step = Number(agArr[1]); + while (min <= limit) { + arr.push(min); + min += step; + } + return arr; + }, + // 根据规则返回一个具有周期性的数组 + getCycleArr(rule, limit, status) { + // status--表示是否从0开始(则从1开始) + let arr = []; + let cycleArr = rule.split('-'); + let min = Number(cycleArr[0]); + let max = Number(cycleArr[1]); + if (min > max) { + max += limit; + } + for (let i = min; i <= max; i++) { + let add = 0; + if (status == false && i % limit == 0) { + add = limit; + } + arr.push(Math.round(i % limit + add)) + } + arr.sort(this.compare) + return arr; + }, + // 比较数字大小(用于Array.sort) + compare(value1, value2) { + if (value2 - value1 > 0) { + return -1; + } else { + return 1; + } + }, + // 格式化日期格式如:2017-9-19 18:04:33 + formatDate(value, type) { + // 计算日期相关值 + let time = typeof value == 'number' ? new Date(value) : value; + let Y = time.getFullYear(); + let M = time.getMonth() + 1; + let D = time.getDate(); + let h = time.getHours(); + let m = time.getMinutes(); + let s = time.getSeconds(); + let week = time.getDay(); + // 如果传递了type的话 + if (type == undefined) { + return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s); + } else if (type == 'week') { + // 在quartz中 1为星期日 + return week + 1; + } + }, + // 检查日期是否存在 + checkDate(value) { + let time = new Date(value); + let format = this.formatDate(time) + return value === format; + } + }, + watch: { + 'ex': 'expressionChange' + }, + props: ['ex'], + mounted: function () { + // 初始化 获取一次结果 + this.expressionChange(); + } +} + +</script> diff --git a/ruoyi-ui/src/components/Crontab/second.vue b/ruoyi-ui/src/components/Crontab/second.vue new file mode 100644 index 0000000..e7b7761 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/second.vue @@ -0,0 +1,117 @@ +<template> + <el-form size="small"> + <el-form-item> + <el-radio v-model='radioValue' :label="1"> + 秒,允许的通配符[, - * /] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="2"> + 周期从 + <el-input-number v-model='cycle01' :min="0" :max="58" /> - + <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 秒 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="3"> + 从 + <el-input-number v-model='average01' :min="0" :max="58" /> 秒开始,每 + <el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="4"> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> + <el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option> + </el-select> + </el-radio> + </el-form-item> + </el-form> +</template> + +<script> +export default { + data() { + return { + radioValue: 1, + cycle01: 1, + cycle02: 2, + average01: 0, + average02: 1, + checkboxList: [], + checkNum: this.$options.propsData.check + } + }, + name: 'crontab-second', + props: ['check', 'radioParent'], + methods: { + // 单选按钮值变化时 + radioChange() { + switch (this.radioValue) { + case 1: + this.$emit('update', 'second', '*', 'second'); + break; + case 2: + this.$emit('update', 'second', this.cycleTotal); + break; + case 3: + this.$emit('update', 'second', this.averageTotal); + break; + case 4: + this.$emit('update', 'second', this.checkboxString); + break; + } + }, + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '2') { + this.$emit('update', 'second', this.cycleTotal); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '3') { + this.$emit('update', 'second', this.averageTotal); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '4') { + this.$emit('update', 'second', this.checkboxString); + } + } + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'checkboxString': 'checkboxChange', + radioParent() { + this.radioValue = this.radioParent + } + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + const cycle01 = this.checkNum(this.cycle01, 0, 58) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59) + return cycle01 + '-' + cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + const average01 = this.checkNum(this.average01, 0, 58) + const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0) + return average01 + '/' + average02; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str == '' ? '*' : str; + } + } +} +</script> diff --git a/ruoyi-ui/src/components/Crontab/week.vue b/ruoyi-ui/src/components/Crontab/week.vue new file mode 100644 index 0000000..1cec700 --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/week.vue @@ -0,0 +1,202 @@ +<template> + <el-form size='small'> + <el-form-item> + <el-radio v-model='radioValue' :label="1"> + 周,允许的通配符[, - * ? / L #] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="2"> + 不指定 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="3"> + 周期从星期 + <el-select clearable v-model="cycle01"> + <el-option + v-for="(item,index) of weekList" + :key="index" + :label="item.value" + :value="item.key" + :disabled="item.key === 1" + >{{item.value}}</el-option> + </el-select> + - + <el-select clearable v-model="cycle02"> + <el-option + v-for="(item,index) of weekList" + :key="index" + :label="item.value" + :value="item.key" + :disabled="item.key < cycle01 && item.key !== 1" + >{{item.value}}</el-option> + </el-select> + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="4"> + 第 + <el-input-number v-model='average01' :min="1" :max="4" /> 周的星期 + <el-select clearable v-model="average02"> + <el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option> + </el-select> + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="5"> + 本月最后一个星期 + <el-select clearable v-model="weekday"> + <el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option> + </el-select> + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio v-model='radioValue' :label="6"> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> + <el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="String(item.key)">{{item.value}}</el-option> + </el-select> + </el-radio> + </el-form-item> + + </el-form> +</template> + +<script> +export default { + data() { + return { + radioValue: 2, + weekday: 2, + cycle01: 2, + cycle02: 3, + average01: 1, + average02: 2, + checkboxList: [], + weekList: [ + { + key: 2, + value: '星期一' + }, + { + key: 3, + value: '星期二' + }, + { + key: 4, + value: '星期三' + }, + { + key: 5, + value: '星期四' + }, + { + key: 6, + value: '星期五' + }, + { + key: 7, + value: '星期六' + }, + { + key: 1, + value: '星期日' + } + ], + checkNum: this.$options.propsData.check + } + }, + name: 'crontab-week', + props: ['check', 'cron'], + methods: { + // 单选按钮值变化时 + radioChange() { + if (this.radioValue !== 2 && this.cron.day !== '?') { + this.$emit('update', 'day', '?', 'week'); + } + switch (this.radioValue) { + case 1: + this.$emit('update', 'week', '*'); + break; + case 2: + this.$emit('update', 'week', '?'); + break; + case 3: + this.$emit('update', 'week', this.cycleTotal); + break; + case 4: + this.$emit('update', 'week', this.averageTotal); + break; + case 5: + this.$emit('update', 'week', this.weekdayCheck + 'L'); + break; + case 6: + this.$emit('update', 'week', this.checkboxString); + break; + } + }, + + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '3') { + this.$emit('update', 'week', this.cycleTotal); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '4') { + this.$emit('update', 'week', this.averageTotal); + } + }, + // 最近工作日值变化时 + weekdayChange() { + if (this.radioValue == '5') { + this.$emit('update', 'week', this.weekday + 'L'); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '6') { + this.$emit('update', 'week', this.checkboxString); + } + }, + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'weekdayCheck': 'weekdayChange', + 'checkboxString': 'checkboxChange', + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + this.cycle01 = this.checkNum(this.cycle01, 1, 7) + this.cycle02 = this.checkNum(this.cycle02, 1, 7) + return this.cycle01 + '-' + this.cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + this.average01 = this.checkNum(this.average01, 1, 4) + this.average02 = this.checkNum(this.average02, 1, 7) + return this.average02 + '#' + this.average01; + }, + // 最近的工作日(格式) + weekdayCheck: function () { + this.weekday = this.checkNum(this.weekday, 1, 7) + return this.weekday; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str == '' ? '*' : str; + } + } +} +</script> diff --git a/ruoyi-ui/src/components/Crontab/year.vue b/ruoyi-ui/src/components/Crontab/year.vue new file mode 100644 index 0000000..5487a6c --- /dev/null +++ b/ruoyi-ui/src/components/Crontab/year.vue @@ -0,0 +1,131 @@ +<template> + <el-form size="small"> + <el-form-item> + <el-radio :label="1" v-model='radioValue'> + 不填,允许的通配符[, - * /] + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio :label="2" v-model='radioValue'> + 每年 + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio :label="3" v-model='radioValue'> + 周期从 + <el-input-number v-model='cycle01' :min='fullYear' :max="2098" /> - + <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099" /> + </el-radio> + </el-form-item> + + <el-form-item> + <el-radio :label="4" v-model='radioValue'> + 从 + <el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始,每 + <el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear" /> 年执行一次 + </el-radio> + + </el-form-item> + + <el-form-item> + <el-radio :label="5" v-model='radioValue'> + 指定 + <el-select clearable v-model="checkboxList" placeholder="可多选" multiple> + <el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" /> + </el-select> + </el-radio> + </el-form-item> + </el-form> +</template> + +<script> +export default { + data() { + return { + fullYear: 0, + radioValue: 1, + cycle01: 0, + cycle02: 0, + average01: 0, + average02: 1, + checkboxList: [], + checkNum: this.$options.propsData.check + } + }, + name: 'crontab-year', + props: ['check', 'month', 'cron'], + methods: { + // 单选按钮值变化时 + radioChange() { + switch (this.radioValue) { + case 1: + this.$emit('update', 'year', ''); + break; + case 2: + this.$emit('update', 'year', '*'); + break; + case 3: + this.$emit('update', 'year', this.cycleTotal); + break; + case 4: + this.$emit('update', 'year', this.averageTotal); + break; + case 5: + this.$emit('update', 'year', this.checkboxString); + break; + } + }, + // 周期两个值变化时 + cycleChange() { + if (this.radioValue == '3') { + this.$emit('update', 'year', this.cycleTotal); + } + }, + // 平均两个值变化时 + averageChange() { + if (this.radioValue == '4') { + this.$emit('update', 'year', this.averageTotal); + } + }, + // checkbox值变化时 + checkboxChange() { + if (this.radioValue == '5') { + this.$emit('update', 'year', this.checkboxString); + } + } + }, + watch: { + 'radioValue': 'radioChange', + 'cycleTotal': 'cycleChange', + 'averageTotal': 'averageChange', + 'checkboxString': 'checkboxChange' + }, + computed: { + // 计算两个周期值 + cycleTotal: function () { + const cycle01 = this.checkNum(this.cycle01, this.fullYear, 2098) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : this.fullYear + 1, 2099) + return cycle01 + '-' + cycle02; + }, + // 计算平均用到的值 + averageTotal: function () { + const average01 = this.checkNum(this.average01, this.fullYear, 2098) + const average02 = this.checkNum(this.average02, 1, 2099 - average01 || this.fullYear) + return average01 + '/' + average02; + }, + // 计算勾选的checkbox值合集 + checkboxString: function () { + let str = this.checkboxList.join(); + return str; + } + }, + mounted: function () { + // 仅获取当前年份 + this.fullYear = Number(new Date().getFullYear()); + this.cycle01 = this.fullYear + this.average01 = this.fullYear + } +} +</script> diff --git a/ruoyi-ui/src/components/DictData/index.js b/ruoyi-ui/src/components/DictData/index.js new file mode 100644 index 0000000..7b85d4a --- /dev/null +++ b/ruoyi-ui/src/components/DictData/index.js @@ -0,0 +1,49 @@ +import Vue from 'vue' +import store from '@/store' +import DataDict from '@/utils/dict' +import { getDicts as getDicts } from '@/api/system/dict/data' + +function searchDictByKey(dict, key) { + if (key == null && key == "") { + return null + } + try { + for (let i = 0; i < dict.length; i++) { + if (dict[i].key == key) { + return dict[i].value + } + } + } catch (e) { + return null + } +} + +function install() { + Vue.use(DataDict, { + metas: { + '*': { + labelField: 'dictLabel', + valueField: 'dictValue', + request(dictMeta) { + const storeDict = searchDictByKey(store.getters.dict, dictMeta.type) + if (storeDict) { + return new Promise(resolve => { resolve(storeDict) }) + } else { + return new Promise((resolve, reject) => { + getDicts(dictMeta.type).then(res => { + store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data }) + resolve(res.data) + }).catch(error => { + reject(error) + }) + }) + } + }, + }, + }, + }) +} + +export default { + install, +} \ No newline at end of file diff --git a/ruoyi-ui/src/components/DictTag/index.vue b/ruoyi-ui/src/components/DictTag/index.vue new file mode 100644 index 0000000..1ef13b9 --- /dev/null +++ b/ruoyi-ui/src/components/DictTag/index.vue @@ -0,0 +1,92 @@ +<template> + <div> + <template v-for="(item, index) in options"> + <template v-if="values.includes(item.value)"> + <span + v-if="(item.raw.listClass == 'default' || item.raw.listClass == '') && (item.raw.cssClass == '' || item.raw.cssClass == null)" + :key="item.value" + :index="index" + :class="item.raw.cssClass" + >{{ item.label + " " }}</span + > + <el-tag + v-else + :disable-transitions="true" + :key="item.value" + :index="index" + :type="item.raw.listClass == 'primary' ? '' : item.raw.listClass" + :class="item.raw.cssClass" + > + {{ item.label + " " }} + </el-tag> + </template> + </template> + <template v-if="unmatch && showValue"> + {{ unmatchArray | handleArray }} + </template> + </div> +</template> + +<script> +export default { + name: "DictTag", + props: { + options: { + type: Array, + default: null, + }, + value: [Number, String, Array], + // 当未找到匹配的数据时,显示value + showValue: { + type: Boolean, + default: true, + } + }, + data() { + return { + unmatchArray: [], // 记录未匹配的项 + } + }, + computed: { + values() { + if (this.value !== null && typeof this.value !== "undefined") { + return Array.isArray(this.value) ? this.value : [String(this.value)]; + } else { + return []; + } + }, + unmatch() { + this.unmatchArray = []; + if (this.value !== null && typeof this.value !== "undefined") { + // 传入值为非数组 + if (!Array.isArray(this.value)) { + if (this.options.some((v) => v.value == this.value)) return false; + this.unmatchArray.push(this.value); + return true; + } + // 传入值为Array + this.value.forEach((item) => { + if (!this.options.some((v) => v.value == item)) + this.unmatchArray.push(item); + }); + return true; + } + // 没有value不显示 + return false; + }, + }, + filters: { + handleArray(array) { + if (array.length === 0) return ""; + return array.reduce((pre, cur) => { + return pre + " " + cur; + }) + } + } +}; +</script> +<style scoped> +.el-tag + .el-tag { + margin-left: 10px; +} +</style> diff --git a/ruoyi-ui/src/components/Editor/index.vue b/ruoyi-ui/src/components/Editor/index.vue new file mode 100644 index 0000000..6bb5a18 --- /dev/null +++ b/ruoyi-ui/src/components/Editor/index.vue @@ -0,0 +1,272 @@ +<template> + <div> + <el-upload + :action="uploadUrl" + :before-upload="handleBeforeUpload" + :on-success="handleUploadSuccess" + :on-error="handleUploadError" + name="file" + :show-file-list="false" + :headers="headers" + style="display: none" + ref="upload" + v-if="this.type == 'url'" + > + </el-upload> + <div class="editor" ref="editor" :style="styles"></div> + </div> +</template> + +<script> +import Quill from "quill"; +import "quill/dist/quill.core.css"; +import "quill/dist/quill.snow.css"; +import "quill/dist/quill.bubble.css"; +import { getToken } from "@/utils/auth"; + +export default { + name: "Editor", + props: { + /* 编辑器的内容 */ + value: { + type: String, + default: "", + }, + /* 高度 */ + height: { + type: Number, + default: null, + }, + /* 最小高度 */ + minHeight: { + type: Number, + default: null, + }, + /* 只读 */ + readOnly: { + type: Boolean, + default: false, + }, + // 上传文件大小限制(MB) + fileSize: { + type: Number, + default: 5, + }, + /* 类型(base64格式、url格式) */ + type: { + type: String, + default: "url", + } + }, + data() { + return { + uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址 + headers: { + Authorization: "Bearer " + getToken() + }, + Quill: null, + currentValue: "", + options: { + theme: "snow", + bounds: document.body, + debug: "warn", + modules: { + // 工具栏配置 + toolbar: [ + ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 + ["blockquote", "code-block"], // 引用 代码块 + [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表 + [{ indent: "-1" }, { indent: "+1" }], // 缩进 + [{ size: ["small", false, "large", "huge"] }], // 字体大小 + [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 + [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色 + [{ align: [] }], // 对齐方式 + ["clean"], // 清除文本格式 + ["link", "image", "video"] // 链接、图片、视频 + ], + }, + placeholder: "请输入内容", + readOnly: this.readOnly, + }, + }; + }, + computed: { + styles() { + let style = {}; + if (this.minHeight) { + style.minHeight = `${this.minHeight}px`; + } + if (this.height) { + style.height = `${this.height}px`; + } + return style; + }, + }, + watch: { + value: { + handler(val) { + if (val !== this.currentValue) { + this.currentValue = val === null ? "" : val; + if (this.Quill) { + this.Quill.pasteHTML(this.currentValue); + } + } + }, + immediate: true, + }, + }, + mounted() { + this.init(); + }, + beforeDestroy() { + this.Quill = null; + }, + methods: { + init() { + const editor = this.$refs.editor; + this.Quill = new Quill(editor, this.options); + // 如果设置了上传地址则自定义图片上传事件 + if (this.type == 'url') { + let toolbar = this.Quill.getModule("toolbar"); + toolbar.addHandler("image", (value) => { + this.uploadType = "image"; + if (value) { + this.$refs.upload.$children[0].$refs.input.click(); + } else { + this.quill.format("image", false); + } + }); + } + this.Quill.pasteHTML(this.currentValue); + this.Quill.on("text-change", (delta, oldDelta, source) => { + const html = this.$refs.editor.children[0].innerHTML; + const text = this.Quill.getText(); + const quill = this.Quill; + this.currentValue = html; + this.$emit("input", html); + this.$emit("on-change", { html, text, quill }); + }); + this.Quill.on("text-change", (delta, oldDelta, source) => { + this.$emit("on-text-change", delta, oldDelta, source); + }); + this.Quill.on("selection-change", (range, oldRange, source) => { + this.$emit("on-selection-change", range, oldRange, source); + }); + this.Quill.on("editor-change", (eventName, ...args) => { + this.$emit("on-editor-change", eventName, ...args); + }); + }, + // 上传前校检格式和大小 + handleBeforeUpload(file) { + // 校检文件大小 + if (this.fileSize) { + const isLt = file.size / 1024 / 1024 < this.fileSize; + if (!isLt) { + this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`); + return false; + } + } + return true; + }, + handleUploadSuccess(res, file) { + // 获取富文本组件实例 + let quill = this.Quill; + // 如果上传成功 + if (res.code == 200) { + // 获取光标所在位置 + let length = quill.getSelection().index; + // 插入图片 res.url为服务器返回的图片地址 + quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.fileName); + // 调整光标到最后 + quill.setSelection(length + 1); + } else { + this.$message.error("图片插入失败"); + } + }, + handleUploadError() { + this.$message.error("图片插入失败"); + }, + }, +}; +</script> + +<style> +.editor, .ql-toolbar { + white-space: pre-wrap !important; + line-height: normal !important; +} +.quill-img { + display: none; +} +.ql-snow .ql-tooltip[data-mode="link"]::before { + content: "请输入链接地址:"; +} +.ql-snow .ql-tooltip.ql-editing a.ql-action::after { + border-right: 0px; + content: "保存"; + padding-right: 0px; +} + +.ql-snow .ql-tooltip[data-mode="video"]::before { + content: "请输入视频地址:"; +} + +.ql-snow .ql-picker.ql-size .ql-picker-label::before, +.ql-snow .ql-picker.ql-size .ql-picker-item::before { + content: "14px"; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before { + content: "10px"; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before { + content: "18px"; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before { + content: "32px"; +} + +.ql-snow .ql-picker.ql-header .ql-picker-label::before, +.ql-snow .ql-picker.ql-header .ql-picker-item::before { + content: "文本"; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { + content: "标题1"; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { + content: "标题2"; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { + content: "标题3"; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { + content: "标题4"; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { + content: "标题5"; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { + content: "标题6"; +} + +.ql-snow .ql-picker.ql-font .ql-picker-label::before, +.ql-snow .ql-picker.ql-font .ql-picker-item::before { + content: "标准字体"; +} +.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before { + content: "衬线字体"; +} +.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before { + content: "等宽字体"; +} +</style> diff --git a/ruoyi-ui/src/components/FileUpload/index.vue b/ruoyi-ui/src/components/FileUpload/index.vue new file mode 100644 index 0000000..6c583cf --- /dev/null +++ b/ruoyi-ui/src/components/FileUpload/index.vue @@ -0,0 +1,215 @@ +<template> + <div class="upload-file"> + <el-upload + multiple + :action="uploadFileUrl" + :before-upload="handleBeforeUpload" + :file-list="fileList" + :limit="limit" + :on-error="handleUploadError" + :on-exceed="handleExceed" + :on-success="handleUploadSuccess" + :show-file-list="false" + :headers="headers" + class="upload-file-uploader" + ref="fileUpload" + > + <!-- 上传按钮 --> + <el-button size="mini" type="primary">选取文件</el-button> + <!-- 上传提示 --> + <div class="el-upload__tip" slot="tip" v-if="showTip"> + 请上传 + <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> + <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> + 的文件 + </div> + </el-upload> + + <!-- 文件列表 --> + <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> + <li :key="file.url" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> + <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank"> + <span class="el-icon-document"> {{ getFileName(file.name) }} </span> + </el-link> + <div class="ele-upload-list__item-content-action"> + <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link> + </div> + </li> + </transition-group> + </div> +</template> + +<script> +import { getToken } from "@/utils/auth"; + +export default { + name: "FileUpload", + props: { + // 值 + value: [String, Object, Array], + // 数量限制 + limit: { + type: Number, + default: 5, + }, + // 大小限制(MB) + fileSize: { + type: Number, + default: 5, + }, + // 文件类型, 例如['png', 'jpg', 'jpeg'] + fileType: { + type: Array, + default: () => ["doc", "xls", "ppt", "txt", "pdf"], + }, + // 是否显示提示 + isShowTip: { + type: Boolean, + default: true + } + }, + data() { + return { + number: 0, + uploadList: [], + baseUrl: process.env.VUE_APP_BASE_API, + uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传文件服务器地址 + headers: { + Authorization: "Bearer " + getToken(), + }, + fileList: [], + }; + }, + watch: { + value: { + handler(val) { + if (val) { + let temp = 1; + // 首先将值转为数组 + const list = Array.isArray(val) ? val : this.value.split(','); + // 然后将数组转为对象数组 + this.fileList = list.map(item => { + if (typeof item === "string") { + item = { name: item, url: item }; + } + item.uid = item.uid || new Date().getTime() + temp++; + return item; + }); + } else { + this.fileList = []; + return []; + } + }, + deep: true, + immediate: true + } + }, + computed: { + // 是否显示提示 + showTip() { + return this.isShowTip && (this.fileType || this.fileSize); + }, + }, + methods: { + // 上传前校检格式和大小 + handleBeforeUpload(file) { + // 校检文件类型 + if (this.fileType) { + const fileName = file.name.split('.'); + const fileExt = fileName[fileName.length - 1]; + const isTypeOk = this.fileType.indexOf(fileExt) >= 0; + if (!isTypeOk) { + this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`); + return false; + } + } + // 校检文件大小 + if (this.fileSize) { + const isLt = file.size / 1024 / 1024 < this.fileSize; + if (!isLt) { + this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`); + return false; + } + } + this.$modal.loading("正在上传文件,请稍候..."); + this.number++; + return true; + }, + // 文件个数超出 + handleExceed() { + this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`); + }, + // 上传失败 + handleUploadError(err) { + this.$modal.msgError("上传文件失败,请重试"); + this.$modal.closeLoading() + }, + // 上传成功回调 + handleUploadSuccess(res, file) { + if (res.code === 200) { + this.uploadList.push({ name: res.fileName, url: res.fileName }); + this.uploadedSuccessfully(); + } else { + this.number--; + this.$modal.closeLoading(); + this.$modal.msgError(res.msg); + this.$refs.fileUpload.handleRemove(file); + this.uploadedSuccessfully(); + } + }, + // 删除文件 + handleDelete(index) { + this.fileList.splice(index, 1); + this.$emit("input", this.listToString(this.fileList)); + }, + // 上传结束处理 + uploadedSuccessfully() { + if (this.number > 0 && this.uploadList.length === this.number) { + this.fileList = this.fileList.concat(this.uploadList); + this.uploadList = []; + this.number = 0; + this.$emit("input", this.listToString(this.fileList)); + this.$modal.closeLoading(); + } + }, + // 获取文件名称 + getFileName(name) { + if (name.lastIndexOf("/") > -1) { + return name.slice(name.lastIndexOf("/") + 1); + } else { + return ""; + } + }, + // 对象转成指定字符串分隔 + listToString(list, separator) { + let strs = ""; + separator = separator || ","; + for (let i in list) { + strs += list[i].url + separator; + } + return strs != '' ? strs.substr(0, strs.length - 1) : ''; + } + } +}; +</script> + +<style scoped lang="scss"> +.upload-file-uploader { + margin-bottom: 5px; +} +.upload-file-list .el-upload-list__item { + border: 1px solid #e4e7ed; + line-height: 2; + margin-bottom: 10px; + position: relative; +} +.upload-file-list .ele-upload-list__item-content { + display: flex; + justify-content: space-between; + align-items: center; + color: inherit; +} +.ele-upload-list__item-content-action .el-link { + margin-right: 10px; +} +</style> diff --git a/ruoyi-ui/src/components/Hamburger/index.vue b/ruoyi-ui/src/components/Hamburger/index.vue new file mode 100644 index 0000000..368b002 --- /dev/null +++ b/ruoyi-ui/src/components/Hamburger/index.vue @@ -0,0 +1,44 @@ +<template> + <div style="padding: 0 15px;" @click="toggleClick"> + <svg + :class="{'is-active':isActive}" + class="hamburger" + viewBox="0 0 1024 1024" + xmlns="http://www.w3.org/2000/svg" + width="64" + height="64" + > + <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" /> + </svg> + </div> +</template> + +<script> +export default { + name: 'Hamburger', + props: { + isActive: { + type: Boolean, + default: false + } + }, + methods: { + toggleClick() { + this.$emit('toggleClick') + } + } +} +</script> + +<style scoped> +.hamburger { + display: inline-block; + vertical-align: middle; + width: 20px; + height: 20px; +} + +.hamburger.is-active { + transform: rotate(180deg); +} +</style> diff --git a/ruoyi-ui/src/components/HeaderSearch/index.vue b/ruoyi-ui/src/components/HeaderSearch/index.vue new file mode 100644 index 0000000..888be5d --- /dev/null +++ b/ruoyi-ui/src/components/HeaderSearch/index.vue @@ -0,0 +1,198 @@ +<template> + <div :class="{'show':show}" class="header-search"> + <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> + <el-select + ref="headerSearchSelect" + v-model="search" + :remote-method="querySearch" + filterable + default-first-option + remote + placeholder="Search" + class="header-search-select" + @change="change" + > + <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" /> + </el-select> + </div> +</template> + +<script> +// fuse is a lightweight fuzzy-search module +// make search results more in line with expectations +import Fuse from 'fuse.js/dist/fuse.min.js' +import path from 'path' + +export default { + name: 'HeaderSearch', + data() { + return { + search: '', + options: [], + searchPool: [], + show: false, + fuse: undefined + } + }, + computed: { + routes() { + return this.$store.getters.permission_routes + } + }, + watch: { + routes() { + this.searchPool = this.generateRoutes(this.routes) + }, + searchPool(list) { + this.initFuse(list) + }, + show(value) { + if (value) { + document.body.addEventListener('click', this.close) + } else { + document.body.removeEventListener('click', this.close) + } + } + }, + mounted() { + this.searchPool = this.generateRoutes(this.routes) + }, + methods: { + click() { + this.show = !this.show + if (this.show) { + this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus() + } + }, + close() { + this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur() + this.options = [] + this.show = false + }, + change(val) { + const path = val.path; + const query = val.query; + if(this.ishttp(val.path)) { + // http(s):// 路径新窗口打开 + const pindex = path.indexOf("http"); + window.open(path.substr(pindex, path.length), "_blank"); + } else { + if (query) { + this.$router.push({ path: path, query: JSON.parse(query) }); + } else { + this.$router.push(path) + } + } + this.search = '' + this.options = [] + this.$nextTick(() => { + this.show = false + }) + }, + initFuse(list) { + this.fuse = new Fuse(list, { + shouldSort: true, + threshold: 0.4, + location: 0, + distance: 100, + minMatchCharLength: 1, + keys: [{ + name: 'title', + weight: 0.7 + }, { + name: 'path', + weight: 0.3 + }] + }) + }, + // Filter out the routes that can be displayed in the sidebar + // And generate the internationalized title + generateRoutes(routes, basePath = '/', prefixTitle = [], query = {}) { + let res = [] + + for (const router of routes) { + // skip hidden router + if (router.hidden) { continue } + + const data = { + path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path, + title: [...prefixTitle] + } + + if (router.meta && router.meta.title) { + data.title = [...data.title, router.meta.title] + + if (router.redirect !== 'noRedirect') { + // only push the routes with title + // special case: need to exclude parent router without redirect + res.push(data) + } + } + + if (router.query) { + data.query = router.query + } + + // recursive child routes + if (router.children) { + const tempRoutes = this.generateRoutes(router.children, data.path, data.title, data.query) + if (tempRoutes.length >= 1) { + res = [...res, ...tempRoutes] + } + } + } + return res + }, + querySearch(query) { + if (query !== '') { + this.options = this.fuse.search(query) + } else { + this.options = [] + } + }, + ishttp(url) { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 + } + } +} +</script> + +<style lang="scss" scoped> +.header-search { + font-size: 0 !important; + + .search-icon { + cursor: pointer; + font-size: 18px; + vertical-align: middle; + } + + .header-search-select { + font-size: 18px; + transition: width 0.2s; + width: 0; + overflow: hidden; + background: transparent; + border-radius: 0; + display: inline-block; + vertical-align: middle; + + ::v-deep .el-input__inner { + border-radius: 0; + border: 0; + padding-left: 0; + padding-right: 0; + box-shadow: none !important; + border-bottom: 1px solid #d9d9d9; + vertical-align: middle; + } + } + + &.show { + .header-search-select { + width: 210px; + margin-left: 10px; + } + } +} +</style> diff --git a/ruoyi-ui/src/components/IconSelect/index.vue b/ruoyi-ui/src/components/IconSelect/index.vue new file mode 100644 index 0000000..8dadc02 --- /dev/null +++ b/ruoyi-ui/src/components/IconSelect/index.vue @@ -0,0 +1,104 @@ +<!-- @author zhengjie --> +<template> + <div class="icon-body"> + <el-input v-model="name" class="icon-search" clearable placeholder="请输入图标名称" @clear="filterIcons" @input="filterIcons"> + <i slot="suffix" class="el-icon-search el-input__icon" /> + </el-input> + <div class="icon-list"> + <div class="list-container"> + <div v-for="(item, index) in iconList" class="icon-item-wrapper" :key="index" @click="selectedIcon(item)"> + <div :class="['icon-item', { active: activeIcon === item }]"> + <svg-icon :icon-class="item" class-name="icon" style="height: 25px;width: 16px;"/> + <span>{{ item }}</span> + </div> + </div> + </div> + </div> + </div> +</template> + +<script> +import icons from './requireIcons' +export default { + name: 'IconSelect', + props: { + activeIcon: { + type: String + } + }, + data() { + return { + name: '', + iconList: icons + } + }, + methods: { + filterIcons() { + this.iconList = icons + if (this.name) { + this.iconList = this.iconList.filter(item => item.includes(this.name)) + } + }, + selectedIcon(name) { + this.$emit('selected', name) + document.body.click() + }, + reset() { + this.name = '' + this.iconList = icons + } + } +} +</script> + +<style rel="stylesheet/scss" lang="scss" scoped> + .icon-body { + width: 100%; + padding: 10px; + .icon-search { + position: relative; + margin-bottom: 5px; + } + .icon-list { + height: 200px; + overflow: auto; + .list-container { + display: flex; + flex-wrap: wrap; + .icon-item-wrapper { + width: calc(100% / 3); + height: 25px; + line-height: 25px; + cursor: pointer; + display: flex; + .icon-item { + display: flex; + max-width: 100%; + height: 100%; + padding: 0 5px; + &:hover { + background: #ececec; + border-radius: 5px; + } + .icon { + flex-shrink: 0; + } + span { + display: inline-block; + vertical-align: -0.15em; + fill: currentColor; + padding-left: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + .icon-item.active { + background: #ececec; + border-radius: 5px; + } + } + } + } + } +</style> diff --git a/ruoyi-ui/src/components/IconSelect/requireIcons.js b/ruoyi-ui/src/components/IconSelect/requireIcons.js new file mode 100644 index 0000000..99e5c54 --- /dev/null +++ b/ruoyi-ui/src/components/IconSelect/requireIcons.js @@ -0,0 +1,11 @@ + +const req = require.context('../../assets/icons/svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys() + +const re = /\.\/(.*)\.svg/ + +const icons = requireAll(req).map(i => { + return i.match(re)[1] +}) + +export default icons diff --git a/ruoyi-ui/src/components/ImagePreview/index.vue b/ruoyi-ui/src/components/ImagePreview/index.vue new file mode 100644 index 0000000..3c770c7 --- /dev/null +++ b/ruoyi-ui/src/components/ImagePreview/index.vue @@ -0,0 +1,90 @@ +<template> + <el-image + :src="`${realSrc}`" + fit="cover" + :style="`width:${realWidth};height:${realHeight};`" + :preview-src-list="realSrcList" + > + <div slot="error" class="image-slot"> + <i class="el-icon-picture-outline"></i> + </div> + </el-image> +</template> + +<script> +import { isExternal } from "@/utils/validate"; + +export default { + name: "ImagePreview", + props: { + src: { + type: String, + default: "" + }, + width: { + type: [Number, String], + default: "" + }, + height: { + type: [Number, String], + default: "" + } + }, + computed: { + realSrc() { + if (!this.src) { + return; + } + let real_src = this.src.split(",")[0]; + if (isExternal(real_src)) { + return real_src; + } + return process.env.VUE_APP_BASE_API + real_src; + }, + realSrcList() { + if (!this.src) { + return; + } + let real_src_list = this.src.split(","); + let srcList = []; + real_src_list.forEach(item => { + if (isExternal(item)) { + return srcList.push(item); + } + return srcList.push(process.env.VUE_APP_BASE_API + item); + }); + return srcList; + }, + realWidth() { + return typeof this.width == "string" ? this.width : `${this.width}px`; + }, + realHeight() { + return typeof this.height == "string" ? this.height : `${this.height}px`; + } + }, +}; +</script> + +<style lang="scss" scoped> +.el-image { + border-radius: 5px; + background-color: #ebeef5; + box-shadow: 0 0 5px 1px #ccc; + ::v-deep .el-image__inner { + transition: all 0.3s; + cursor: pointer; + &:hover { + transform: scale(1.2); + } + } + ::v-deep .image-slot { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + color: #909399; + font-size: 30px; + } +} +</style> diff --git a/ruoyi-ui/src/components/ImageUpload/index.vue b/ruoyi-ui/src/components/ImageUpload/index.vue new file mode 100644 index 0000000..b57a15e --- /dev/null +++ b/ruoyi-ui/src/components/ImageUpload/index.vue @@ -0,0 +1,226 @@ +<template> + <div class="component-upload-image"> + <el-upload + multiple + :action="uploadImgUrl" + list-type="picture-card" + :on-success="handleUploadSuccess" + :before-upload="handleBeforeUpload" + :limit="limit" + :on-error="handleUploadError" + :on-exceed="handleExceed" + ref="imageUpload" + :on-remove="handleDelete" + :show-file-list="true" + :headers="headers" + :file-list="fileList" + :on-preview="handlePictureCardPreview" + :class="{hide: this.fileList.length >= this.limit}" + > + <i class="el-icon-plus"></i> + </el-upload> + + <!-- 上传提示 --> + <div class="el-upload__tip" slot="tip" v-if="showTip"> + 请上传 + <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> + <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> + 的文件 + </div> + + <el-dialog + :visible.sync="dialogVisible" + title="预览" + width="800" + append-to-body + > + <img + :src="dialogImageUrl" + style="display: block; max-width: 100%; margin: 0 auto" + /> + </el-dialog> + </div> +</template> + +<script> +import { getToken } from "@/utils/auth"; + +export default { + props: { + value: [String, Object, Array], + // 图片数量限制 + limit: { + type: Number, + default: 5, + }, + // 大小限制(MB) + fileSize: { + type: Number, + default: 5, + }, + // 文件类型, 例如['png', 'jpg', 'jpeg'] + fileType: { + type: Array, + default: () => ["png", "jpg", "jpeg"], + }, + // 是否显示提示 + isShowTip: { + type: Boolean, + default: true + } + }, + data() { + return { + number: 0, + uploadList: [], + dialogImageUrl: "", + dialogVisible: false, + hideUpload: false, + baseUrl: process.env.VUE_APP_BASE_API, + uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址 + headers: { + Authorization: "Bearer " + getToken(), + }, + fileList: [] + }; + }, + watch: { + value: { + handler(val) { + if (val) { + // 首先将值转为数组 + const list = Array.isArray(val) ? val : this.value.split(','); + // 然后将数组转为对象数组 + this.fileList = list.map(item => { + if (typeof item === "string") { + if (item.indexOf(this.baseUrl) === -1) { + item = { name: this.baseUrl + item, url: this.baseUrl + item }; + } else { + item = { name: item, url: item }; + } + } + return item; + }); + } else { + this.fileList = []; + return []; + } + }, + deep: true, + immediate: true + } + }, + computed: { + // 是否显示提示 + showTip() { + return this.isShowTip && (this.fileType || this.fileSize); + }, + }, + methods: { + // 上传前loading加载 + handleBeforeUpload(file) { + let isImg = false; + if (this.fileType.length) { + let fileExtension = ""; + if (file.name.lastIndexOf(".") > -1) { + fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1); + } + isImg = this.fileType.some(type => { + if (file.type.indexOf(type) > -1) return true; + if (fileExtension && fileExtension.indexOf(type) > -1) return true; + return false; + }); + } else { + isImg = file.type.indexOf("image") > -1; + } + + if (!isImg) { + this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`); + return false; + } + if (this.fileSize) { + const isLt = file.size / 1024 / 1024 < this.fileSize; + if (!isLt) { + this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`); + return false; + } + } + this.$modal.loading("正在上传图片,请稍候..."); + this.number++; + }, + // 文件个数超出 + handleExceed() { + this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`); + }, + // 上传成功回调 + handleUploadSuccess(res, file) { + if (res.code === 200) { + this.uploadList.push({ name: res.fileName, url: res.fileName }); + this.uploadedSuccessfully(); + } else { + this.number--; + this.$modal.closeLoading(); + this.$modal.msgError(res.msg); + this.$refs.imageUpload.handleRemove(file); + this.uploadedSuccessfully(); + } + }, + // 删除图片 + handleDelete(file) { + const findex = this.fileList.map(f => f.name).indexOf(file.name); + if(findex > -1) { + this.fileList.splice(findex, 1); + this.$emit("input", this.listToString(this.fileList)); + } + }, + // 上传失败 + handleUploadError() { + this.$modal.msgError("上传图片失败,请重试"); + this.$modal.closeLoading(); + }, + // 上传结束处理 + uploadedSuccessfully() { + if (this.number > 0 && this.uploadList.length === this.number) { + this.fileList = this.fileList.concat(this.uploadList); + this.uploadList = []; + this.number = 0; + this.$emit("input", this.listToString(this.fileList)); + this.$modal.closeLoading(); + } + }, + // 预览 + handlePictureCardPreview(file) { + this.dialogImageUrl = file.url; + this.dialogVisible = true; + }, + // 对象转成指定字符串分隔 + listToString(list, separator) { + let strs = ""; + separator = separator || ","; + for (let i in list) { + if (list[i].url) { + strs += list[i].url.replace(this.baseUrl, "") + separator; + } + } + return strs != '' ? strs.substr(0, strs.length - 1) : ''; + } + } +}; +</script> +<style scoped lang="scss"> +// .el-upload--picture-card 控制加号部分 +::v-deep.hide .el-upload--picture-card { + display: none; +} +// 去掉动画效果 +::v-deep .el-list-enter-active, +::v-deep .el-list-leave-active { + transition: all 0s; +} + +::v-deep .el-list-enter, .el-list-leave-active { + opacity: 0; + transform: translateY(0); +} +</style> + diff --git a/ruoyi-ui/src/components/Pagination/index.vue b/ruoyi-ui/src/components/Pagination/index.vue new file mode 100644 index 0000000..56f5a6b --- /dev/null +++ b/ruoyi-ui/src/components/Pagination/index.vue @@ -0,0 +1,114 @@ +<template> + <div :class="{'hidden':hidden}" class="pagination-container"> + <el-pagination + :background="background" + :current-page.sync="currentPage" + :page-size.sync="pageSize" + :layout="layout" + :page-sizes="pageSizes" + :pager-count="pagerCount" + :total="total" + v-bind="$attrs" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> +</template> + +<script> +import { scrollTo } from '@/utils/scroll-to' + +export default { + name: 'Pagination', + props: { + total: { + required: true, + type: Number + }, + page: { + type: Number, + default: 1 + }, + limit: { + type: Number, + default: 20 + }, + pageSizes: { + type: Array, + default() { + return [10, 20, 30, 50] + } + }, + // 移动端页码按钮的数量端默认值5 + pagerCount: { + type: Number, + default: document.body.clientWidth < 992 ? 5 : 7 + }, + layout: { + type: String, + default: 'total, sizes, prev, pager, next, jumper' + }, + background: { + type: Boolean, + default: true + }, + autoScroll: { + type: Boolean, + default: true + }, + hidden: { + type: Boolean, + default: false + } + }, + data() { + return { + }; + }, + computed: { + currentPage: { + get() { + return this.page + }, + set(val) { + this.$emit('update:page', val) + } + }, + pageSize: { + get() { + return this.limit + }, + set(val) { + this.$emit('update:limit', val) + } + } + }, + methods: { + handleSizeChange(val) { + if (this.currentPage * val > this.total) { + this.currentPage = 1 + } + this.$emit('pagination', { page: this.currentPage, limit: val }) + if (this.autoScroll) { + scrollTo(0, 800) + } + }, + handleCurrentChange(val) { + this.$emit('pagination', { page: val, limit: this.pageSize }) + if (this.autoScroll) { + scrollTo(0, 800) + } + } + } +} +</script> + +<style scoped> +.pagination-container { + background: #fff; + padding: 32px 16px; +} +.pagination-container.hidden { + display: none; +} +</style> diff --git a/ruoyi-ui/src/components/PanThumb/index.vue b/ruoyi-ui/src/components/PanThumb/index.vue new file mode 100644 index 0000000..1bcf417 --- /dev/null +++ b/ruoyi-ui/src/components/PanThumb/index.vue @@ -0,0 +1,142 @@ +<template> + <div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item"> + <div class="pan-info"> + <div class="pan-info-roles-container"> + <slot /> + </div> + </div> + <!-- eslint-disable-next-line --> + <div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div> + </div> +</template> + +<script> +export default { + name: 'PanThumb', + props: { + image: { + type: String, + required: true + }, + zIndex: { + type: Number, + default: 1 + }, + width: { + type: String, + default: '150px' + }, + height: { + type: String, + default: '150px' + } + } +} +</script> + +<style scoped> +.pan-item { + width: 200px; + height: 200px; + border-radius: 50%; + display: inline-block; + position: relative; + cursor: default; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.pan-info-roles-container { + padding: 20px; + text-align: center; +} + +.pan-thumb { + width: 100%; + height: 100%; + background-position: center center; + background-size: cover; + border-radius: 50%; + overflow: hidden; + position: absolute; + transform-origin: 95% 40%; + transition: all 0.3s ease-in-out; +} + +/* .pan-thumb:after { + content: ''; + width: 8px; + height: 8px; + position: absolute; + border-radius: 50%; + top: 40%; + left: 95%; + margin: -4px 0 0 -4px; + background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%); + box-shadow: 0 0 1px rgba(255, 255, 255, 0.9); +} */ + +.pan-info { + position: absolute; + width: inherit; + height: inherit; + border-radius: 50%; + overflow: hidden; + box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05); +} + +.pan-info h3 { + color: #fff; + text-transform: uppercase; + position: relative; + letter-spacing: 2px; + font-size: 18px; + margin: 0 60px; + padding: 22px 0 0 0; + height: 85px; + font-family: 'Open Sans', Arial, sans-serif; + text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3); +} + +.pan-info p { + color: #fff; + padding: 10px 5px; + font-style: italic; + margin: 0 30px; + font-size: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.5); +} + +.pan-info p a { + display: block; + color: #333; + width: 80px; + height: 80px; + background: rgba(255, 255, 255, 0.3); + border-radius: 50%; + color: #fff; + font-style: normal; + font-weight: 700; + text-transform: uppercase; + font-size: 9px; + letter-spacing: 1px; + padding-top: 24px; + margin: 7px auto 0; + font-family: 'Open Sans', Arial, sans-serif; + opacity: 0; + transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s; + transform: translateX(60px) rotate(90deg); +} + +.pan-info p a:hover { + background: rgba(255, 255, 255, 0.5); +} + +.pan-item:hover .pan-thumb { + transform: rotate(-110deg); +} + +.pan-item:hover .pan-info p a { + opacity: 1; + transform: translateX(0px) rotate(0deg); +} +</style> diff --git a/ruoyi-ui/src/components/ParentView/index.vue b/ruoyi-ui/src/components/ParentView/index.vue new file mode 100644 index 0000000..7bf6148 --- /dev/null +++ b/ruoyi-ui/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ +<template > + <router-view /> +</template> diff --git a/ruoyi-ui/src/components/RightPanel/index.vue b/ruoyi-ui/src/components/RightPanel/index.vue new file mode 100644 index 0000000..5abeecb --- /dev/null +++ b/ruoyi-ui/src/components/RightPanel/index.vue @@ -0,0 +1,106 @@ +<template> + <div ref="rightPanel" class="rightPanel-container"> + <div class="rightPanel-background" /> + <div class="rightPanel"> + <div class="rightPanel-items"> + <slot /> + </div> + </div> + </div> +</template> + +<script> +export default { + name: 'RightPanel', + props: { + clickNotClose: { + default: false, + type: Boolean + } + }, + computed: { + show: { + get() { + return this.$store.state.settings.showSettings + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'showSettings', + value: val + }) + } + } + }, + watch: { + show(value) { + if (value && !this.clickNotClose) { + this.addEventClick() + } + } + }, + mounted() { + this.addEventClick() + }, + beforeDestroy() { + const elx = this.$refs.rightPanel + elx.remove() + }, + methods: { + addEventClick() { + window.addEventListener('click', this.closeSidebar) + }, + closeSidebar(evt) { + const parent = evt.target.closest('.el-drawer__body') + if (!parent) { + this.show = false + window.removeEventListener('click', this.closeSidebar) + } + } + } +} +</script> + +<style lang="scss" scoped> +.rightPanel-background { + position: fixed; + top: 0; + left: 0; + opacity: 0; + transition: opacity .3s cubic-bezier(.7, .3, .1, 1); + background: rgba(0, 0, 0, .2); + z-index: -1; +} + +.rightPanel { + width: 100%; + max-width: 260px; + height: 100vh; + position: fixed; + top: 0; + right: 0; + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05); + transition: all .25s cubic-bezier(.7, .3, .1, 1); + transform: translate(100%); + background: #fff; + z-index: 40000; +} + +.handle-button { + width: 48px; + height: 48px; + position: absolute; + left: -48px; + text-align: center; + font-size: 24px; + border-radius: 6px 0 0 6px !important; + z-index: 0; + pointer-events: auto; + cursor: pointer; + color: #fff; + line-height: 48px; + i { + font-size: 24px; + line-height: 48px; + } +} +</style> diff --git a/ruoyi-ui/src/components/RightToolbar/index.vue b/ruoyi-ui/src/components/RightToolbar/index.vue new file mode 100644 index 0000000..527e07c --- /dev/null +++ b/ruoyi-ui/src/components/RightToolbar/index.vue @@ -0,0 +1,104 @@ +<template> + <div class="top-right-btn" :style="style"> + <el-row> + <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search"> + <el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" /> + </el-tooltip> + <el-tooltip class="item" effect="dark" content="刷新" placement="top"> + <el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" /> + </el-tooltip> + <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns"> + <el-button size="mini" circle icon="el-icon-menu" @click="showColumn()" /> + </el-tooltip> + </el-row> + <el-dialog :title="title" :visible.sync="open" append-to-body> + <el-transfer + :titles="['显示', '隐藏']" + v-model="value" + :data="columns" + @change="dataChange" + ></el-transfer> + </el-dialog> + </div> +</template> +<script> +export default { + name: "RightToolbar", + data() { + return { + // 显隐数据 + value: [], + // 弹出层标题 + title: "显示/隐藏", + // 是否显示弹出层 + open: false, + }; + }, + props: { + showSearch: { + type: Boolean, + default: true, + }, + columns: { + type: Array, + }, + search: { + type: Boolean, + default: true, + }, + gutter: { + type: Number, + default: 10, + }, + }, + computed: { + style() { + const ret = {}; + if (this.gutter) { + ret.marginRight = `${this.gutter / 2}px`; + } + return ret; + } + }, + created() { + // 显隐列初始默认隐藏列 + for (let item in this.columns) { + if (this.columns[item].visible === false) { + this.value.push(parseInt(item)); + } + } + }, + methods: { + // 搜索 + toggleSearch() { + this.$emit("update:showSearch", !this.showSearch); + }, + // 刷新 + refresh() { + this.$emit("queryTable"); + }, + // 右侧列表元素变化 + dataChange(data) { + for (let item in this.columns) { + const key = this.columns[item].key; + this.columns[item].visible = !data.includes(key); + } + }, + // 打开显隐列dialog + showColumn() { + this.open = true; + }, + }, +}; +</script> +<style lang="scss" scoped> +::v-deep .el-transfer__button { + border-radius: 50%; + padding: 12px; + display: block; + margin-left: 0px; +} +::v-deep .el-transfer__button:first-child { + margin-bottom: 10px; +} +</style> diff --git a/ruoyi-ui/src/components/RuoYi/Doc/index.vue b/ruoyi-ui/src/components/RuoYi/Doc/index.vue new file mode 100644 index 0000000..75fa864 --- /dev/null +++ b/ruoyi-ui/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,21 @@ +<template> + <div> + <svg-icon icon-class="question" @click="goto" /> + </div> +</template> + +<script> +export default { + name: 'RuoYiDoc', + data() { + return { + url: 'http://doc.ruoyi.vip/ruoyi-vue' + } + }, + methods: { + goto() { + window.open(this.url) + } + } +} +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/components/RuoYi/Git/index.vue b/ruoyi-ui/src/components/RuoYi/Git/index.vue new file mode 100644 index 0000000..bdafbae --- /dev/null +++ b/ruoyi-ui/src/components/RuoYi/Git/index.vue @@ -0,0 +1,21 @@ +<template> + <div> + <svg-icon icon-class="github" @click="goto" /> + </div> +</template> + +<script> +export default { + name: 'RuoYiGit', + data() { + return { + url: 'https://gitee.com/y_project/RuoYi-Vue' + } + }, + methods: { + goto() { + window.open(this.url) + } + } +} +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/components/Screenfull/index.vue b/ruoyi-ui/src/components/Screenfull/index.vue new file mode 100644 index 0000000..d4e539c --- /dev/null +++ b/ruoyi-ui/src/components/Screenfull/index.vue @@ -0,0 +1,57 @@ +<template> + <div> + <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" /> + </div> +</template> + +<script> +import screenfull from 'screenfull' + +export default { + name: 'Screenfull', + data() { + return { + isFullscreen: false + } + }, + mounted() { + this.init() + }, + beforeDestroy() { + this.destroy() + }, + methods: { + click() { + if (!screenfull.isEnabled) { + this.$message({ message: '你的浏览器不支持全屏', type: 'warning' }) + return false + } + screenfull.toggle() + }, + change() { + this.isFullscreen = screenfull.isFullscreen + }, + init() { + if (screenfull.isEnabled) { + screenfull.on('change', this.change) + } + }, + destroy() { + if (screenfull.isEnabled) { + screenfull.off('change', this.change) + } + } + } +} +</script> + +<style scoped> +.screenfull-svg { + display: inline-block; + cursor: pointer; + fill: #5a5e66;; + width: 20px; + height: 20px; + vertical-align: 10px; +} +</style> diff --git a/ruoyi-ui/src/components/SizeSelect/index.vue b/ruoyi-ui/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..069b5de --- /dev/null +++ b/ruoyi-ui/src/components/SizeSelect/index.vue @@ -0,0 +1,56 @@ +<template> + <el-dropdown trigger="click" @command="handleSetSize"> + <div> + <svg-icon class-name="size-icon" icon-class="size" /> + </div> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value"> + {{ item.label }} + </el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> +</template> + +<script> +export default { + data() { + return { + sizeOptions: [ + { label: 'Default', value: 'default' }, + { label: 'Medium', value: 'medium' }, + { label: 'Small', value: 'small' }, + { label: 'Mini', value: 'mini' } + ] + } + }, + computed: { + size() { + return this.$store.getters.size + } + }, + methods: { + handleSetSize(size) { + this.$ELEMENT.size = size + this.$store.dispatch('app/setSize', size) + this.refreshView() + this.$message({ + message: 'Switch Size Success', + type: 'success' + }) + }, + refreshView() { + // In order to make the cached page re-rendered + this.$store.dispatch('tagsView/delAllCachedViews', this.$route) + + const { fullPath } = this.$route + + this.$nextTick(() => { + this.$router.replace({ + path: '/redirect' + fullPath + }) + }) + } + } + +} +</script> diff --git a/ruoyi-ui/src/components/SvgIcon/index.vue b/ruoyi-ui/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..e4bf5ad --- /dev/null +++ b/ruoyi-ui/src/components/SvgIcon/index.vue @@ -0,0 +1,61 @@ +<template> + <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" /> + <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners"> + <use :xlink:href="iconName" /> + </svg> +</template> + +<script> +import { isExternal } from '@/utils/validate' + +export default { + name: 'SvgIcon', + props: { + iconClass: { + type: String, + required: true + }, + className: { + type: String, + default: '' + } + }, + computed: { + isExternal() { + return isExternal(this.iconClass) + }, + iconName() { + return `#icon-${this.iconClass}` + }, + svgClass() { + if (this.className) { + return 'svg-icon ' + this.className + } else { + return 'svg-icon' + } + }, + styleExternalIcon() { + return { + mask: `url(${this.iconClass}) no-repeat 50% 50%`, + '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%` + } + } + } +} +</script> + +<style scoped> +.svg-icon { + width: 1em; + height: 1em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +.svg-external-icon { + background-color: currentColor; + mask-size: cover!important; + display: inline-block; +} +</style> diff --git a/ruoyi-ui/src/components/ThemePicker/index.vue b/ruoyi-ui/src/components/ThemePicker/index.vue new file mode 100644 index 0000000..1714e1f --- /dev/null +++ b/ruoyi-ui/src/components/ThemePicker/index.vue @@ -0,0 +1,173 @@ +<template> + <el-color-picker + v-model="theme" + :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]" + class="theme-picker" + popper-class="theme-picker-dropdown" + /> +</template> + +<script> +const version = require('element-ui/package.json').version // element-ui version from node_modules +const ORIGINAL_THEME = '#409EFF' // default color + +export default { + data() { + return { + chalk: '', // content of theme-chalk css + theme: '' + } + }, + computed: { + defaultTheme() { + return this.$store.state.settings.theme + } + }, + watch: { + defaultTheme: { + handler: function(val, oldVal) { + this.theme = val + }, + immediate: true + }, + async theme(val) { + await this.setTheme(val) + } + }, + created() { + if(this.defaultTheme !== ORIGINAL_THEME) { + this.setTheme(this.defaultTheme) + } + }, + + methods: { + async setTheme(val) { + const oldVal = this.chalk ? this.theme : ORIGINAL_THEME + if (typeof val !== 'string') return + const themeCluster = this.getThemeCluster(val.replace('#', '')) + const originalCluster = this.getThemeCluster(oldVal.replace('#', '')) + + const getHandler = (variable, id) => { + return () => { + const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', '')) + const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster) + + let styleTag = document.getElementById(id) + if (!styleTag) { + styleTag = document.createElement('style') + styleTag.setAttribute('id', id) + document.head.appendChild(styleTag) + } + styleTag.innerText = newStyle + } + } + + if (!this.chalk) { + const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css` + await this.getCSSString(url, 'chalk') + } + + const chalkHandler = getHandler('chalk', 'chalk-style') + + chalkHandler() + + const styles = [].slice.call(document.querySelectorAll('style')) + .filter(style => { + const text = style.innerText + return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text) + }) + styles.forEach(style => { + const { innerText } = style + if (typeof innerText !== 'string') return + style.innerText = this.updateStyle(innerText, originalCluster, themeCluster) + }) + + this.$emit('change', val) + }, + + updateStyle(style, oldCluster, newCluster) { + let newStyle = style + oldCluster.forEach((color, index) => { + newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index]) + }) + return newStyle + }, + + getCSSString(url, variable) { + return new Promise(resolve => { + const xhr = new XMLHttpRequest() + xhr.onreadystatechange = () => { + if (xhr.readyState === 4 && xhr.status === 200) { + this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '') + resolve() + } + } + xhr.open('GET', url) + xhr.send() + }) + }, + + getThemeCluster(theme) { + const tintColor = (color, tint) => { + let red = parseInt(color.slice(0, 2), 16) + let green = parseInt(color.slice(2, 4), 16) + let blue = parseInt(color.slice(4, 6), 16) + + if (tint === 0) { // when primary color is in its rgb space + return [red, green, blue].join(',') + } else { + red += Math.round(tint * (255 - red)) + green += Math.round(tint * (255 - green)) + blue += Math.round(tint * (255 - blue)) + + red = red.toString(16) + green = green.toString(16) + blue = blue.toString(16) + + return `#${red}${green}${blue}` + } + } + + const shadeColor = (color, shade) => { + let red = parseInt(color.slice(0, 2), 16) + let green = parseInt(color.slice(2, 4), 16) + let blue = parseInt(color.slice(4, 6), 16) + + red = Math.round((1 - shade) * red) + green = Math.round((1 - shade) * green) + blue = Math.round((1 - shade) * blue) + + red = red.toString(16) + green = green.toString(16) + blue = blue.toString(16) + + return `#${red}${green}${blue}` + } + + const clusters = [theme] + for (let i = 0; i <= 9; i++) { + clusters.push(tintColor(theme, Number((i / 10).toFixed(2)))) + } + clusters.push(shadeColor(theme, 0.1)) + return clusters + } + } +} +</script> + +<style> +.theme-message, +.theme-picker-dropdown { + z-index: 99999 !important; +} + +.theme-picker .el-color-picker__trigger { + height: 26px !important; + width: 26px !important; + padding: 2px; +} + +.theme-picker-dropdown .el-color-dropdown__link-btn { + display: none; +} +</style> diff --git a/ruoyi-ui/src/components/TopNav/index.vue b/ruoyi-ui/src/components/TopNav/index.vue new file mode 100644 index 0000000..daee1b8 --- /dev/null +++ b/ruoyi-ui/src/components/TopNav/index.vue @@ -0,0 +1,194 @@ +<template> + <el-menu + :default-active="activeMenu" + mode="horizontal" + @select="handleSelect" + > + <template v-for="(item, index) in topMenus"> + <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber" + ><svg-icon + v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" + :icon-class="item.meta.icon" + /> + {{ item.meta.title }}</el-menu-item + > + </template> + + <!-- 顶部菜单超出数量折叠 --> + <el-submenu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber"> + <template slot="title">更多菜单</template> + <template v-for="(item, index) in topMenus"> + <el-menu-item + :index="item.path" + :key="index" + v-if="index >= visibleNumber" + ><svg-icon :icon-class="item.meta.icon" /> + {{ item.meta.title }}</el-menu-item + > + </template> + </el-submenu> + </el-menu> +</template> + +<script> +import { constantRoutes } from "@/router"; + +// 隐藏侧边栏路由 +const hideList = ['/index', '/user/profile']; + +export default { + data() { + return { + // 顶部栏初始数 + visibleNumber: 5, + // 当前激活菜单的 index + currentIndex: undefined + }; + }, + computed: { + theme() { + return this.$store.state.settings.theme; + }, + // 顶部显示菜单 + topMenus() { + let topMenus = []; + this.routers.map((menu) => { + if (menu.hidden !== true) { + // 兼容顶部栏一级菜单内部跳转 + if (menu.path === "/") { + topMenus.push(menu.children[0]); + } else { + topMenus.push(menu); + } + } + }); + return topMenus; + }, + // 所有的路由信息 + routers() { + return this.$store.state.permission.topbarRouters; + }, + // 设置子路由 + childrenMenus() { + var childrenMenus = []; + this.routers.map((router) => { + for (var item in router.children) { + if (router.children[item].parentPath === undefined) { + if(router.path === "/") { + router.children[item].path = "/" + router.children[item].path; + } else { + if(!this.ishttp(router.children[item].path)) { + router.children[item].path = router.path + "/" + router.children[item].path; + } + } + router.children[item].parentPath = router.path; + } + childrenMenus.push(router.children[item]); + } + }); + return constantRoutes.concat(childrenMenus); + }, + // 默认激活的菜单 + activeMenu() { + const path = this.$route.path; + let activePath = path; + if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) { + const tmpPath = path.substring(1, path.length); + activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/")); + if (!this.$route.meta.link) { + this.$store.dispatch('app/toggleSideBarHide', false); + } + } else if(!this.$route.children) { + activePath = path; + this.$store.dispatch('app/toggleSideBarHide', true); + } + this.activeRoutes(activePath); + return activePath; + }, + }, + beforeMount() { + window.addEventListener('resize', this.setVisibleNumber) + }, + beforeDestroy() { + window.removeEventListener('resize', this.setVisibleNumber) + }, + mounted() { + this.setVisibleNumber(); + }, + methods: { + // 根据宽度计算设置显示栏数 + setVisibleNumber() { + const width = document.body.getBoundingClientRect().width / 3; + this.visibleNumber = parseInt(width / 85); + }, + // 菜单选择事件 + handleSelect(key, keyPath) { + this.currentIndex = key; + const route = this.routers.find(item => item.path === key); + if (this.ishttp(key)) { + // http(s):// 路径新窗口打开 + window.open(key, "_blank"); + } else if (!route || !route.children) { + // 没有子路由路径内部打开 + const routeMenu = this.childrenMenus.find(item => item.path === key); + if (routeMenu && routeMenu.query) { + let query = JSON.parse(routeMenu.query); + this.$router.push({ path: key, query: query }); + } else { + this.$router.push({ path: key }); + } + this.$store.dispatch('app/toggleSideBarHide', true); + } else { + // 显示左侧联动菜单 + this.activeRoutes(key); + this.$store.dispatch('app/toggleSideBarHide', false); + } + }, + // 当前激活的路由 + activeRoutes(key) { + var routes = []; + if (this.childrenMenus && this.childrenMenus.length > 0) { + this.childrenMenus.map((item) => { + if (key == item.parentPath || (key == "index" && "" == item.path)) { + routes.push(item); + } + }); + } + if(routes.length > 0) { + this.$store.commit("SET_SIDEBAR_ROUTERS", routes); + } else { + this.$store.dispatch('app/toggleSideBarHide', true); + } + }, + ishttp(url) { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 + } + }, +}; +</script> + +<style lang="scss"> +.topmenu-container.el-menu--horizontal > .el-menu-item { + float: left; + height: 50px !important; + line-height: 50px !important; + color: #999093 !important; + padding: 0 5px !important; + margin: 0 10px !important; +} + +.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-submenu.is-active .el-submenu__title { + border-bottom: 2px solid #{'var(--theme)'} !important; + color: #303133; +} + +/* submenu item */ +.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title { + float: left; + height: 50px !important; + line-height: 50px !important; + color: #999093 !important; + padding: 0 5px !important; + margin: 0 10px !important; +} +</style> diff --git a/ruoyi-ui/src/components/iFrame/index.vue b/ruoyi-ui/src/components/iFrame/index.vue new file mode 100644 index 0000000..426857f --- /dev/null +++ b/ruoyi-ui/src/components/iFrame/index.vue @@ -0,0 +1,36 @@ +<template> + <div v-loading="loading" :style="'height:' + height"> + <iframe + :src="src" + frameborder="no" + style="width: 100%; height: 100%" + scrolling="auto" + /> + </div> +</template> +<script> +export default { + props: { + src: { + type: String, + required: true + }, + }, + data() { + return { + height: document.documentElement.clientHeight - 94.5 + "px;", + loading: true, + url: this.src + }; + }, + mounted: function () { + setTimeout(() => { + this.loading = false; + }, 300); + const that = this; + window.onresize = function temp() { + that.height = document.documentElement.clientHeight - 94.5 + "px;"; + }; + } +}; +</script> diff --git a/ruoyi-ui/src/directive/dialog/drag.js b/ruoyi-ui/src/directive/dialog/drag.js new file mode 100644 index 0000000..2e82346 --- /dev/null +++ b/ruoyi-ui/src/directive/dialog/drag.js @@ -0,0 +1,64 @@ +/** +* v-dialogDrag 弹窗拖拽 +* Copyright (c) 2019 ruoyi +*/ + +export default { + bind(el, binding, vnode, oldVnode) { + const value = binding.value + if (value == false) return + // 获取拖拽内容头部 + const dialogHeaderEl = el.querySelector('.el-dialog__header'); + const dragDom = el.querySelector('.el-dialog'); + dialogHeaderEl.style.cursor = 'move'; + // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); + const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null); + dragDom.style.position = 'absolute'; + dragDom.style.marginTop = 0; + let width = dragDom.style.width; + if (width.includes('%')) { + width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100); + } else { + width = +width.replace(/\px/g, ''); + } + dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`; + // 鼠标按下事件 + dialogHeaderEl.onmousedown = (e) => { + // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离) + const disX = e.clientX - dialogHeaderEl.offsetLeft; + const disY = e.clientY - dialogHeaderEl.offsetTop; + + // 获取到的值带px 正则匹配替换 + let styL, styT; + + // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px + if (sty.left.includes('%')) { + styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100); + styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100); + } else { + styL = +sty.left.replace(/\px/g, ''); + styT = +sty.top.replace(/\px/g, ''); + }; + + // 鼠标拖拽事件 + document.onmousemove = function (e) { + // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离) + const l = e.clientX - disX; + const t = e.clientY - disY; + + let finallyL = l + styL + let finallyT = t + styT + + // 移动当前元素 + dragDom.style.left = `${finallyL}px`; + dragDom.style.top = `${finallyT}px`; + + }; + + document.onmouseup = function (e) { + document.onmousemove = null; + document.onmouseup = null; + }; + } + } +}; \ No newline at end of file diff --git a/ruoyi-ui/src/directive/dialog/dragHeight.js b/ruoyi-ui/src/directive/dialog/dragHeight.js new file mode 100644 index 0000000..d1590f8 --- /dev/null +++ b/ruoyi-ui/src/directive/dialog/dragHeight.js @@ -0,0 +1,34 @@ +/** +* v-dialogDragWidth 可拖动弹窗高度(右下角) +* Copyright (c) 2019 ruoyi +*/ + +export default { + bind(el) { + const dragDom = el.querySelector('.el-dialog'); + const lineEl = document.createElement('div'); + lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;'; + lineEl.addEventListener('mousedown', + function(e) { + // 鼠标按下,计算当前元素距离可视区的距离 + const disX = e.clientX - el.offsetLeft; + const disY = e.clientY - el.offsetTop; + // 当前宽度 高度 + const curWidth = dragDom.offsetWidth; + const curHeight = dragDom.offsetHeight; + document.onmousemove = function(e) { + e.preventDefault(); // 移动时禁用默认事件 + // 通过事件委托,计算移动的距离 + const xl = e.clientX - disX; + const yl = e.clientY - disY + dragDom.style.width = `${curWidth + xl}px`; + dragDom.style.height = `${curHeight + yl}px`; + }; + document.onmouseup = function(e) { + document.onmousemove = null; + document.onmouseup = null; + }; + }, false); + dragDom.appendChild(lineEl); + } +} \ No newline at end of file diff --git a/ruoyi-ui/src/directive/dialog/dragWidth.js b/ruoyi-ui/src/directive/dialog/dragWidth.js new file mode 100644 index 0000000..d5cda3a --- /dev/null +++ b/ruoyi-ui/src/directive/dialog/dragWidth.js @@ -0,0 +1,30 @@ +/** +* v-dialogDragWidth 可拖动弹窗宽度(右侧边) +* Copyright (c) 2019 ruoyi +*/ + +export default { + bind(el) { + const dragDom = el.querySelector('.el-dialog'); + const lineEl = document.createElement('div'); + lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;'; + lineEl.addEventListener('mousedown', + function (e) { + // 鼠标按下,计算当前元素距离可视区的距离 + const disX = e.clientX - el.offsetLeft; + // 当前宽度 + const curWidth = dragDom.offsetWidth; + document.onmousemove = function (e) { + e.preventDefault(); // 移动时禁用默认事件 + // 通过事件委托,计算移动的距离 + const l = e.clientX - disX; + dragDom.style.width = `${curWidth + l}px`; + }; + document.onmouseup = function (e) { + document.onmousemove = null; + document.onmouseup = null; + }; + }, false); + dragDom.appendChild(lineEl); + } +} \ No newline at end of file diff --git a/ruoyi-ui/src/directive/index.js b/ruoyi-ui/src/directive/index.js new file mode 100644 index 0000000..b9b07da --- /dev/null +++ b/ruoyi-ui/src/directive/index.js @@ -0,0 +1,23 @@ +import hasRole from './permission/hasRole' +import hasPermi from './permission/hasPermi' +import dialogDrag from './dialog/drag' +import dialogDragWidth from './dialog/dragWidth' +import dialogDragHeight from './dialog/dragHeight' +import clipboard from './module/clipboard' + +const install = function(Vue) { + Vue.directive('hasRole', hasRole) + Vue.directive('hasPermi', hasPermi) + Vue.directive('clipboard', clipboard) + Vue.directive('dialogDrag', dialogDrag) + Vue.directive('dialogDragWidth', dialogDragWidth) + Vue.directive('dialogDragHeight', dialogDragHeight) +} + +if (window.Vue) { + window['hasRole'] = hasRole + window['hasPermi'] = hasPermi + Vue.use(install); // eslint-disable-line +} + +export default install diff --git a/ruoyi-ui/src/directive/module/clipboard.js b/ruoyi-ui/src/directive/module/clipboard.js new file mode 100644 index 0000000..635315a --- /dev/null +++ b/ruoyi-ui/src/directive/module/clipboard.js @@ -0,0 +1,54 @@ +/** +* v-clipboard 文字复制剪贴 +* Copyright (c) 2021 ruoyi +*/ + +import Clipboard from 'clipboard' +export default { + bind(el, binding, vnode) { + switch (binding.arg) { + case 'success': + el._vClipBoard_success = binding.value; + break; + case 'error': + el._vClipBoard_error = binding.value; + break; + default: { + const clipboard = new Clipboard(el, { + text: () => binding.value, + action: () => binding.arg === 'cut' ? 'cut' : 'copy' + }); + clipboard.on('success', e => { + const callback = el._vClipBoard_success; + callback && callback(e); + }); + clipboard.on('error', e => { + const callback = el._vClipBoard_error; + callback && callback(e); + }); + el._vClipBoard = clipboard; + } + } + }, + update(el, binding) { + if (binding.arg === 'success') { + el._vClipBoard_success = binding.value; + } else if (binding.arg === 'error') { + el._vClipBoard_error = binding.value; + } else { + el._vClipBoard.text = function () { return binding.value; }; + el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy'; + } + }, + unbind(el, binding) { + if (!el._vClipboard) return + if (binding.arg === 'success') { + delete el._vClipBoard_success; + } else if (binding.arg === 'error') { + delete el._vClipBoard_error; + } else { + el._vClipBoard.destroy(); + delete el._vClipBoard; + } + } +} diff --git a/ruoyi-ui/src/directive/permission/hasPermi.js b/ruoyi-ui/src/directive/permission/hasPermi.js new file mode 100644 index 0000000..719536c --- /dev/null +++ b/ruoyi-ui/src/directive/permission/hasPermi.js @@ -0,0 +1,28 @@ + /** + * v-hasPermi 操作权限处理 + * Copyright (c) 2019 ruoyi + */ + +import store from '@/store' + +export default { + inserted(el, binding, vnode) { + const { value } = binding + const all_permission = "*:*:*"; + const permissions = store.getters && store.getters.permissions + + if (value && value instanceof Array && value.length > 0) { + const permissionFlag = value + + const hasPermissions = permissions.some(permission => { + return all_permission === permission || permissionFlag.includes(permission) + }) + + if (!hasPermissions) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error(`请设置操作权限标签值`) + } + } +} diff --git a/ruoyi-ui/src/directive/permission/hasRole.js b/ruoyi-ui/src/directive/permission/hasRole.js new file mode 100644 index 0000000..eec4a5b --- /dev/null +++ b/ruoyi-ui/src/directive/permission/hasRole.js @@ -0,0 +1,28 @@ + /** + * v-hasRole 角色权限处理 + * Copyright (c) 2019 ruoyi + */ + +import store from '@/store' + +export default { + inserted(el, binding, vnode) { + const { value } = binding + const super_admin = "admin"; + const roles = store.getters && store.getters.roles + + if (value && value instanceof Array && value.length > 0) { + const roleFlag = value + + const hasRole = roles.some(role => { + return super_admin === role || roleFlag.includes(role) + }) + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error(`请设置角色权限标签值"`) + } + } +} diff --git a/ruoyi-ui/src/layout/components/AppMain.vue b/ruoyi-ui/src/layout/components/AppMain.vue new file mode 100644 index 0000000..a25c562 --- /dev/null +++ b/ruoyi-ui/src/layout/components/AppMain.vue @@ -0,0 +1,75 @@ +<template> + <section class="app-main"> + <transition name="fade-transform" mode="out-in"> + <keep-alive :include="cachedViews"> + <router-view v-if="!$route.meta.link" :key="key" /> + </keep-alive> + </transition> + <iframe-toggle /> + </section> +</template> + +<script> +import iframeToggle from "./IframeToggle/index" + +export default { + name: 'AppMain', + components: { iframeToggle }, + computed: { + cachedViews() { + return this.$store.state.tagsView.cachedViews + }, + key() { + return this.$route.path + } + } +} +</script> + +<style lang="scss" scoped> +.app-main { + /* 50= navbar 50 */ + min-height: calc(100vh - 50px); + width: 100%; + position: relative; + overflow: hidden; +} + +.fixed-header + .app-main { + padding-top: 50px; +} + +.hasTagsView { + .app-main { + /* 84 = navbar + tags-view = 50 + 34 */ + min-height: calc(100vh - 84px); + } + + .fixed-header + .app-main { + padding-top: 84px; + } +} +</style> + +<style lang="scss"> +// fix css style bug in open el-dialog +.el-popup-parent--hidden { + .fixed-header { + padding-right: 6px; + } +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background-color: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background-color: #c0c0c0; + border-radius: 3px; +} +</style> diff --git a/ruoyi-ui/src/layout/components/IframeToggle/index.vue b/ruoyi-ui/src/layout/components/IframeToggle/index.vue new file mode 100644 index 0000000..26e17c1 --- /dev/null +++ b/ruoyi-ui/src/layout/components/IframeToggle/index.vue @@ -0,0 +1,24 @@ +<template> + <transition-group name="fade-transform" mode="out-in"> + <inner-link + v-for="(item, index) in iframeViews" + :key="item.path" + :iframeId="'iframe' + index" + v-show="$route.path === item.path" + :src="item.meta.link" + ></inner-link> + </transition-group> +</template> + +<script> +import InnerLink from "../InnerLink/index" + +export default { + components: { InnerLink }, + computed: { + iframeViews() { + return this.$store.state.tagsView.iframeViews + } + } +} +</script> diff --git a/ruoyi-ui/src/layout/components/InnerLink/index.vue b/ruoyi-ui/src/layout/components/InnerLink/index.vue new file mode 100644 index 0000000..badefc5 --- /dev/null +++ b/ruoyi-ui/src/layout/components/InnerLink/index.vue @@ -0,0 +1,47 @@ +<template> + <div :style="'height:' + height" v-loading="loading" element-loading-text="正在加载页面,请稍候!"> + <iframe + :id="iframeId" + style="width: 100%; height: 100%" + :src="src" + frameborder="no" + ></iframe> + </div> +</template> + +<script> +export default { + props: { + src: { + type: String, + default: "/" + }, + iframeId: { + type: String + } + }, + data() { + return { + loading: false, + height: document.documentElement.clientHeight - 94.5 + "px;" + }; + }, + mounted() { + var _this = this; + const iframeId = ("#" + this.iframeId).replace(/\//g, "\\/"); + const iframe = document.querySelector(iframeId); + // iframe页面loading控制 + if (iframe.attachEvent) { + this.loading = true; + iframe.attachEvent("onload", function () { + _this.loading = false; + }); + } else { + this.loading = true; + iframe.onload = function () { + _this.loading = false; + }; + } + } +}; +</script> diff --git a/ruoyi-ui/src/layout/components/Navbar.vue b/ruoyi-ui/src/layout/components/Navbar.vue new file mode 100644 index 0000000..39b3dad --- /dev/null +++ b/ruoyi-ui/src/layout/components/Navbar.vue @@ -0,0 +1,200 @@ +<template> + <div class="navbar"> + <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> + + <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/> + <top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/> + + <div class="right-menu"> + <template v-if="device!=='mobile'"> + <search id="header-search" class="right-menu-item" /> + + <el-tooltip content="源码地址" effect="dark" placement="bottom"> + <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" /> + </el-tooltip> + + <el-tooltip content="文档地址" effect="dark" placement="bottom"> + <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" /> + </el-tooltip> + + <screenfull id="screenfull" class="right-menu-item hover-effect" /> + + <el-tooltip content="布局大小" effect="dark" placement="bottom"> + <size-select id="size-select" class="right-menu-item hover-effect" /> + </el-tooltip> + + </template> + + <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click"> + <div class="avatar-wrapper"> + <img :src="avatar" class="user-avatar"> + <i class="el-icon-caret-bottom" /> + </div> + <el-dropdown-menu slot="dropdown"> + <router-link to="/user/profile"> + <el-dropdown-item>个人中心</el-dropdown-item> + </router-link> + <el-dropdown-item @click.native="setting = true"> + <span>布局设置</span> + </el-dropdown-item> + <el-dropdown-item divided @click.native="logout"> + <span>退出登录</span> + </el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + </div> + </div> +</template> + +<script> +import { mapGetters } from 'vuex' +import Breadcrumb from '@/components/Breadcrumb' +import TopNav from '@/components/TopNav' +import Hamburger from '@/components/Hamburger' +import Screenfull from '@/components/Screenfull' +import SizeSelect from '@/components/SizeSelect' +import Search from '@/components/HeaderSearch' +import RuoYiGit from '@/components/RuoYi/Git' +import RuoYiDoc from '@/components/RuoYi/Doc' + +export default { + components: { + Breadcrumb, + TopNav, + Hamburger, + Screenfull, + SizeSelect, + Search, + RuoYiGit, + RuoYiDoc + }, + computed: { + ...mapGetters([ + 'sidebar', + 'avatar', + 'device' + ]), + setting: { + get() { + return this.$store.state.settings.showSettings + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'showSettings', + value: val + }) + } + }, + topNav: { + get() { + return this.$store.state.settings.topNav + } + } + }, + methods: { + toggleSideBar() { + this.$store.dispatch('app/toggleSideBar') + }, + async logout() { + this.$confirm('确定注销并退出系统吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.$store.dispatch('LogOut').then(() => { + location.href = '/index'; + }) + }).catch(() => {}); + } + } +} +</script> + +<style lang="scss" scoped> +.navbar { + height: 50px; + overflow: hidden; + position: relative; + background: #fff; + box-shadow: 0 1px 4px rgba(0,21,41,.08); + + .hamburger-container { + line-height: 46px; + height: 100%; + float: left; + cursor: pointer; + transition: background .3s; + -webkit-tap-highlight-color:transparent; + + &:hover { + background: rgba(0, 0, 0, .025) + } + } + + .breadcrumb-container { + float: left; + } + + .topmenu-container { + position: absolute; + left: 50px; + } + + .errLog-container { + display: inline-block; + vertical-align: top; + } + + .right-menu { + float: right; + height: 100%; + line-height: 50px; + + &:focus { + outline: none; + } + + .right-menu-item { + display: inline-block; + padding: 0 8px; + height: 100%; + font-size: 18px; + color: #5a5e66; + vertical-align: text-bottom; + + &.hover-effect { + cursor: pointer; + transition: background .3s; + + &:hover { + background: rgba(0, 0, 0, .025) + } + } + } + + .avatar-container { + margin-right: 30px; + + .avatar-wrapper { + margin-top: 5px; + position: relative; + + .user-avatar { + cursor: pointer; + width: 40px; + height: 40px; + border-radius: 10px; + } + + .el-icon-caret-bottom { + cursor: pointer; + position: absolute; + right: -20px; + top: 25px; + font-size: 12px; + } + } + } + } +} +</style> diff --git a/ruoyi-ui/src/layout/components/Settings/index.vue b/ruoyi-ui/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..8b49842 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Settings/index.vue @@ -0,0 +1,260 @@ +<template> + <el-drawer size="280px" :visible="visible" :with-header="false" :append-to-body="true" :show-close="false"> + <div class="drawer-container"> + <div> + <div class="setting-drawer-content"> + <div class="setting-drawer-title"> + <h3 class="drawer-title">主题风格设置</h3> + </div> + <div class="setting-drawer-block-checbox"> + <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')"> + <img src="@/assets/images/dark.svg" alt="dark"> + <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;"> + <i aria-label="图标: check" class="anticon anticon-check"> + <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class=""> + <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/> + </svg> + </i> + </div> + </div> + <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')"> + <img src="@/assets/images/light.svg" alt="light"> + <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;"> + <i aria-label="图标: check" class="anticon anticon-check"> + <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class=""> + <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/> + </svg> + </i> + </div> + </div> + </div> + + <div class="drawer-item"> + <span>主题颜色</span> + <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" /> + </div> + </div> + + <el-divider/> + + <h3 class="drawer-title">系统布局配置</h3> + + <div class="drawer-item"> + <span>开启 TopNav</span> + <el-switch v-model="topNav" class="drawer-switch" /> + </div> + + <div class="drawer-item"> + <span>开启 Tags-Views</span> + <el-switch v-model="tagsView" class="drawer-switch" /> + </div> + + <div class="drawer-item"> + <span>固定 Header</span> + <el-switch v-model="fixedHeader" class="drawer-switch" /> + </div> + + <div class="drawer-item"> + <span>显示 Logo</span> + <el-switch v-model="sidebarLogo" class="drawer-switch" /> + </div> + + <div class="drawer-item"> + <span>动态标题</span> + <el-switch v-model="dynamicTitle" class="drawer-switch" /> + </div> + + <el-divider/> + + <el-button size="small" type="primary" plain icon="el-icon-document-add" @click="saveSetting">保存配置</el-button> + <el-button size="small" plain icon="el-icon-refresh" @click="resetSetting">重置配置</el-button> + </div> + </div> + </el-drawer> +</template> + +<script> +import ThemePicker from '@/components/ThemePicker' + +export default { + components: { ThemePicker }, + data() { + return { + theme: this.$store.state.settings.theme, + sideTheme: this.$store.state.settings.sideTheme + }; + }, + computed: { + visible: { + get() { + return this.$store.state.settings.showSettings + } + }, + fixedHeader: { + get() { + return this.$store.state.settings.fixedHeader + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'fixedHeader', + value: val + }) + } + }, + topNav: { + get() { + return this.$store.state.settings.topNav + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'topNav', + value: val + }) + if (!val) { + this.$store.dispatch('app/toggleSideBarHide', false); + this.$store.commit("SET_SIDEBAR_ROUTERS", this.$store.state.permission.defaultRoutes); + } + } + }, + tagsView: { + get() { + return this.$store.state.settings.tagsView + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'tagsView', + value: val + }) + } + }, + sidebarLogo: { + get() { + return this.$store.state.settings.sidebarLogo + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'sidebarLogo', + value: val + }) + } + }, + dynamicTitle: { + get() { + return this.$store.state.settings.dynamicTitle + }, + set(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'dynamicTitle', + value: val + }) + } + }, + }, + methods: { + themeChange(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'theme', + value: val + }) + this.theme = val; + }, + handleTheme(val) { + this.$store.dispatch('settings/changeSetting', { + key: 'sideTheme', + value: val + }) + this.sideTheme = val; + }, + saveSetting() { + this.$modal.loading("正在保存到本地,请稍候..."); + this.$cache.local.set( + "layout-setting", + `{ + "topNav":${this.topNav}, + "tagsView":${this.tagsView}, + "fixedHeader":${this.fixedHeader}, + "sidebarLogo":${this.sidebarLogo}, + "dynamicTitle":${this.dynamicTitle}, + "sideTheme":"${this.sideTheme}", + "theme":"${this.theme}" + }` + ); + setTimeout(this.$modal.closeLoading(), 1000) + }, + resetSetting() { + this.$modal.loading("正在清除设置缓存并刷新,请稍候..."); + this.$cache.local.remove("layout-setting") + setTimeout("window.location.reload()", 1000) + } + } +} +</script> + +<style lang="scss" scoped> + .setting-drawer-content { + .setting-drawer-title { + margin-bottom: 12px; + color: rgba(0, 0, 0, .85); + font-size: 14px; + line-height: 22px; + font-weight: bold; + } + + .setting-drawer-block-checbox { + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: 10px; + margin-bottom: 20px; + + .setting-drawer-block-checbox-item { + position: relative; + margin-right: 16px; + border-radius: 2px; + cursor: pointer; + + img { + width: 48px; + height: 48px; + } + + .setting-drawer-block-checbox-selectIcon { + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + padding-top: 15px; + padding-left: 24px; + color: #1890ff; + font-weight: 700; + font-size: 14px; + } + } + } + } + + .drawer-container { + padding: 20px; + font-size: 14px; + line-height: 1.5; + word-wrap: break-word; + + .drawer-title { + margin-bottom: 12px; + color: rgba(0, 0, 0, .85); + font-size: 14px; + line-height: 22px; + } + + .drawer-item { + color: rgba(0, 0, 0, .65); + font-size: 14px; + padding: 12px 0; + } + + .drawer-switch { + float: right + } + } +</style> diff --git a/ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js b/ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js new file mode 100644 index 0000000..6823726 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/FixiOSBug.js @@ -0,0 +1,25 @@ +export default { + computed: { + device() { + return this.$store.state.app.device + } + }, + mounted() { + // In order to fix the click on menu on the ios device will trigger the mouseleave bug + this.fixBugIniOS() + }, + methods: { + fixBugIniOS() { + const $subMenu = this.$refs.subMenu + if ($subMenu) { + const handleMouseleave = $subMenu.handleMouseleave + $subMenu.handleMouseleave = (e) => { + if (this.device === 'mobile') { + return + } + handleMouseleave(e) + } + } + } + } +} diff --git a/ruoyi-ui/src/layout/components/Sidebar/Item.vue b/ruoyi-ui/src/layout/components/Sidebar/Item.vue new file mode 100644 index 0000000..be3285d --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/Item.vue @@ -0,0 +1,33 @@ +<script> +export default { + name: 'MenuItem', + functional: true, + props: { + icon: { + type: String, + default: '' + }, + title: { + type: String, + default: '' + } + }, + render(h, context) { + const { icon, title } = context.props + const vnodes = [] + + if (icon) { + vnodes.push(<svg-icon icon-class={icon}/>) + } + + if (title) { + if (title.length > 5) { + vnodes.push(<span slot='title' title={(title)}>{(title)}</span>) + } else { + vnodes.push(<span slot='title'>{(title)}</span>) + } + } + return vnodes + } +} +</script> diff --git a/ruoyi-ui/src/layout/components/Sidebar/Link.vue b/ruoyi-ui/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..8b0bc93 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,43 @@ +<template> + <component :is="type" v-bind="linkProps(to)"> + <slot /> + </component> +</template> + +<script> +import { isExternal } from '@/utils/validate' + +export default { + props: { + to: { + type: [String, Object], + required: true + } + }, + computed: { + isExternal() { + return isExternal(this.to) + }, + type() { + if (this.isExternal) { + return 'a' + } + return 'router-link' + } + }, + methods: { + linkProps(to) { + if (this.isExternal) { + return { + href: to, + target: '_blank', + rel: 'noopener' + } + } + return { + to: to + } + } + } +} +</script> diff --git a/ruoyi-ui/src/layout/components/Sidebar/Logo.vue b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..2774cc8 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,93 @@ +<template> + <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"> + <transition name="sidebarLogoFade"> + <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> + <img v-if="logo" :src="logo" class="sidebar-logo" /> + <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1> + </router-link> + <router-link v-else key="expand" class="sidebar-logo-link" to="/"> + <img v-if="logo" :src="logo" class="sidebar-logo" /> + <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1> + </router-link> + </transition> + </div> +</template> + +<script> +import logoImg from '@/assets/logo/logo.png' +import variables from '@/assets/styles/variables.scss' + +export default { + name: 'SidebarLogo', + props: { + collapse: { + type: Boolean, + required: true + } + }, + computed: { + variables() { + return variables; + }, + sideTheme() { + return this.$store.state.settings.sideTheme + } + }, + data() { + return { + title: process.env.VUE_APP_TITLE, + logo: logoImg + } + } +} +</script> + +<style lang="scss" scoped> +.sidebarLogoFade-enter-active { + transition: opacity 1.5s; +} + +.sidebarLogoFade-enter, +.sidebarLogoFade-leave-to { + opacity: 0; +} + +.sidebar-logo-container { + position: relative; + width: 100%; + height: 50px; + line-height: 50px; + background: #2b2f3a; + text-align: center; + overflow: hidden; + + & .sidebar-logo-link { + height: 100%; + width: 100%; + + & .sidebar-logo { + width: 32px; + height: 32px; + vertical-align: middle; + margin-right: 12px; + } + + & .sidebar-title { + display: inline-block; + margin: 0; + color: #fff; + font-weight: 600; + line-height: 50px; + font-size: 14px; + font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; + vertical-align: middle; + } + } + + &.collapse { + .sidebar-logo { + margin-right: 0px; + } + } +} +</style> diff --git a/ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue b/ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..4853fbb --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,100 @@ +<template> + <div v-if="!item.hidden"> + <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"> + <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)"> + <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> + <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" /> + </el-menu-item> + </app-link> + </template> + + <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> + <template slot="title"> + <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> + </template> + <sidebar-item + v-for="child in item.children" + :key="child.path" + :is-nest="true" + :item="child" + :base-path="resolvePath(child.path)" + class="nest-menu" + /> + </el-submenu> + </div> +</template> + +<script> +import path from 'path' +import { isExternal } from '@/utils/validate' +import Item from './Item' +import AppLink from './Link' +import FixiOSBug from './FixiOSBug' + +export default { + name: 'SidebarItem', + components: { Item, AppLink }, + mixins: [FixiOSBug], + props: { + // route object + item: { + type: Object, + required: true + }, + isNest: { + type: Boolean, + default: false + }, + basePath: { + type: String, + default: '' + } + }, + data() { + this.onlyOneChild = null + return {} + }, + methods: { + hasOneShowingChild(children = [], parent) { + if (!children) { + children = []; + } + const showingChildren = children.filter(item => { + if (item.hidden) { + return false + } else { + // Temp set(will be used if only has one showing child) + this.onlyOneChild = item + return true + } + }) + + // When there is only one child router, the child router is displayed by default + if (showingChildren.length === 1) { + return true + } + + // Show parent if there are no child router to display + if (showingChildren.length === 0) { + this.onlyOneChild = { ... parent, path: '', noShowingChildren: true } + return true + } + + return false + }, + resolvePath(routePath, routeQuery) { + if (isExternal(routePath)) { + return routePath + } + if (isExternal(this.basePath)) { + return this.basePath + } + if (routeQuery) { + let query = JSON.parse(routeQuery); + return { path: path.resolve(this.basePath, routePath), query: query } + } + return path.resolve(this.basePath, routePath) + } + } +} +</script> diff --git a/ruoyi-ui/src/layout/components/Sidebar/index.vue b/ruoyi-ui/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..51d0839 --- /dev/null +++ b/ruoyi-ui/src/layout/components/Sidebar/index.vue @@ -0,0 +1,57 @@ +<template> + <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"> + <logo v-if="showLogo" :collapse="isCollapse" /> + <el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper"> + <el-menu + :default-active="activeMenu" + :collapse="isCollapse" + :background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground" + :text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor" + :unique-opened="true" + :active-text-color="settings.theme" + :collapse-transition="false" + mode="vertical" + > + <sidebar-item + v-for="(route, index) in sidebarRouters" + :key="route.path + index" + :item="route" + :base-path="route.path" + /> + </el-menu> + </el-scrollbar> + </div> +</template> + +<script> +import { mapGetters, mapState } from "vuex"; +import Logo from "./Logo"; +import SidebarItem from "./SidebarItem"; +import variables from "@/assets/styles/variables.scss"; + +export default { + components: { SidebarItem, Logo }, + computed: { + ...mapState(["settings"]), + ...mapGetters(["sidebarRouters", "sidebar"]), + activeMenu() { + const route = this.$route; + const { meta, path } = route; + // if set path, the sidebar will highlight the path you set + if (meta.activeMenu) { + return meta.activeMenu; + } + return path; + }, + showLogo() { + return this.$store.state.settings.sidebarLogo; + }, + variables() { + return variables; + }, + isCollapse() { + return !this.sidebar.opened; + } + } +}; +</script> diff --git a/ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue b/ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..bb753a1 --- /dev/null +++ b/ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,94 @@ +<template> + <el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll"> + <slot /> + </el-scrollbar> +</template> + +<script> +const tagAndTagSpacing = 4 // tagAndTagSpacing + +export default { + name: 'ScrollPane', + data() { + return { + left: 0 + } + }, + computed: { + scrollWrapper() { + return this.$refs.scrollContainer.$refs.wrap + } + }, + mounted() { + this.scrollWrapper.addEventListener('scroll', this.emitScroll, true) + }, + beforeDestroy() { + this.scrollWrapper.removeEventListener('scroll', this.emitScroll) + }, + methods: { + handleScroll(e) { + const eventDelta = e.wheelDelta || -e.deltaY * 40 + const $scrollWrapper = this.scrollWrapper + $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4 + }, + emitScroll() { + this.$emit('scroll') + }, + moveToTarget(currentTag) { + const $container = this.$refs.scrollContainer.$el + const $containerWidth = $container.offsetWidth + const $scrollWrapper = this.scrollWrapper + const tagList = this.$parent.$refs.tag + + let firstTag = null + let lastTag = null + + // find first tag and last tag + if (tagList.length > 0) { + firstTag = tagList[0] + lastTag = tagList[tagList.length - 1] + } + + if (firstTag === currentTag) { + $scrollWrapper.scrollLeft = 0 + } else if (lastTag === currentTag) { + $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth + } else { + // find preTag and nextTag + const currentIndex = tagList.findIndex(item => item === currentTag) + const prevTag = tagList[currentIndex - 1] + const nextTag = tagList[currentIndex + 1] + + // the tag's offsetLeft after of nextTag + const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing + + // the tag's offsetLeft before of prevTag + const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing + + if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) { + $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth + } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) { + $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft + } + } + } + } +} +</script> + +<style lang="scss" scoped> +.scroll-container { + white-space: nowrap; + position: relative; + overflow: hidden; + width: 100%; + ::v-deep { + .el-scrollbar__bar { + bottom: 0px; + } + .el-scrollbar__wrap { + height: 49px; + } + } +} +</style> diff --git a/ruoyi-ui/src/layout/components/TagsView/index.vue b/ruoyi-ui/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..96585a5 --- /dev/null +++ b/ruoyi-ui/src/layout/components/TagsView/index.vue @@ -0,0 +1,332 @@ +<template> + <div id="tags-view-container" class="tags-view-container"> + <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll"> + <router-link + v-for="tag in visitedViews" + ref="tag" + :key="tag.path" + :class="isActive(tag)?'active':''" + :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" + tag="span" + class="tags-view-item" + :style="activeStyle(tag)" + @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''" + @contextmenu.prevent.native="openMenu(tag,$event)" + > + {{ tag.title }} + <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" /> + </router-link> + </scroll-pane> + <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu"> + <li @click="refreshSelectedTag(selectedTag)"><i class="el-icon-refresh-right"></i> 刷新页面</li> + <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> 关闭当前</li> + <li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 关闭其他</li> + <li v-if="!isFirstView()" @click="closeLeftTags"><i class="el-icon-back"></i> 关闭左侧</li> + <li v-if="!isLastView()" @click="closeRightTags"><i class="el-icon-right"></i> 关闭右侧</li> + <li @click="closeAllTags(selectedTag)"><i class="el-icon-circle-close"></i> 全部关闭</li> + </ul> + </div> +</template> + +<script> +import ScrollPane from './ScrollPane' +import path from 'path' + +export default { + components: { ScrollPane }, + data() { + return { + visible: false, + top: 0, + left: 0, + selectedTag: {}, + affixTags: [] + } + }, + computed: { + visitedViews() { + return this.$store.state.tagsView.visitedViews + }, + routes() { + return this.$store.state.permission.routes + }, + theme() { + return this.$store.state.settings.theme; + } + }, + watch: { + $route() { + this.addTags() + this.moveToCurrentTag() + }, + visible(value) { + if (value) { + document.body.addEventListener('click', this.closeMenu) + } else { + document.body.removeEventListener('click', this.closeMenu) + } + } + }, + mounted() { + this.initTags() + this.addTags() + }, + methods: { + isActive(route) { + return route.path === this.$route.path + }, + activeStyle(tag) { + if (!this.isActive(tag)) return {}; + return { + "background-color": this.theme, + "border-color": this.theme + }; + }, + isAffix(tag) { + return tag.meta && tag.meta.affix + }, + isFirstView() { + try { + return this.selectedTag.fullPath === '/index' || this.selectedTag.fullPath === this.visitedViews[1].fullPath + } catch (err) { + return false + } + }, + isLastView() { + try { + return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath + } catch (err) { + return false + } + }, + filterAffixTags(routes, basePath = '/') { + let tags = [] + routes.forEach(route => { + if (route.meta && route.meta.affix) { + const tagPath = path.resolve(basePath, route.path) + tags.push({ + fullPath: tagPath, + path: tagPath, + name: route.name, + meta: { ...route.meta } + }) + } + if (route.children) { + const tempTags = this.filterAffixTags(route.children, route.path) + if (tempTags.length >= 1) { + tags = [...tags, ...tempTags] + } + } + }) + return tags + }, + initTags() { + const affixTags = this.affixTags = this.filterAffixTags(this.routes) + for (const tag of affixTags) { + // Must have tag name + if (tag.name) { + this.$store.dispatch('tagsView/addVisitedView', tag) + } + } + }, + addTags() { + const { name } = this.$route + if (name) { + this.$store.dispatch('tagsView/addView', this.$route) + if (this.$route.meta.link) { + this.$store.dispatch('tagsView/addIframeView', this.$route) + } + } + return false + }, + moveToCurrentTag() { + const tags = this.$refs.tag + this.$nextTick(() => { + for (const tag of tags) { + if (tag.to.path === this.$route.path) { + this.$refs.scrollPane.moveToTarget(tag) + // when query is different then update + if (tag.to.fullPath !== this.$route.fullPath) { + this.$store.dispatch('tagsView/updateVisitedView', this.$route) + } + break + } + } + }) + }, + refreshSelectedTag(view) { + this.$tab.refreshPage(view); + if (this.$route.meta.link) { + this.$store.dispatch('tagsView/delIframeView', this.$route) + } + }, + closeSelectedTag(view) { + this.$tab.closePage(view).then(({ visitedViews }) => { + if (this.isActive(view)) { + this.toLastView(visitedViews, view) + } + }) + }, + closeRightTags() { + this.$tab.closeRightPage(this.selectedTag).then(visitedViews => { + if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) { + this.toLastView(visitedViews) + } + }) + }, + closeLeftTags() { + this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => { + if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) { + this.toLastView(visitedViews) + } + }) + }, + closeOthersTags() { + this.$router.push(this.selectedTag.fullPath).catch(()=>{}); + this.$tab.closeOtherPage(this.selectedTag).then(() => { + this.moveToCurrentTag() + }) + }, + closeAllTags(view) { + this.$tab.closeAllPage().then(({ visitedViews }) => { + if (this.affixTags.some(tag => tag.path === this.$route.path)) { + return + } + this.toLastView(visitedViews, view) + }) + }, + toLastView(visitedViews, view) { + const latestView = visitedViews.slice(-1)[0] + if (latestView) { + this.$router.push(latestView.fullPath) + } else { + // now the default is to redirect to the home page if there is no tags-view, + // you can adjust it according to your needs. + if (view.name === 'Dashboard') { + // to reload home page + this.$router.replace({ path: '/redirect' + view.fullPath }) + } else { + this.$router.push('/') + } + } + }, + openMenu(tag, e) { + const menuMinWidth = 105 + const offsetLeft = this.$el.getBoundingClientRect().left // container margin left + const offsetWidth = this.$el.offsetWidth // container width + const maxLeft = offsetWidth - menuMinWidth // left boundary + const left = e.clientX - offsetLeft + 15 // 15: margin right + + if (left > maxLeft) { + this.left = maxLeft + } else { + this.left = left + } + + this.top = e.clientY + this.visible = true + this.selectedTag = tag + }, + closeMenu() { + this.visible = false + }, + handleScroll() { + this.closeMenu() + } + } +} +</script> + +<style lang="scss" scoped> +.tags-view-container { + height: 34px; + width: 100%; + background: #fff; + border-bottom: 1px solid #d8dce5; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04); + .tags-view-wrapper { + .tags-view-item { + display: inline-block; + position: relative; + cursor: pointer; + height: 26px; + line-height: 26px; + border: 1px solid #d8dce5; + color: #495060; + background: #fff; + padding: 0 8px; + font-size: 12px; + margin-left: 5px; + margin-top: 4px; + &:first-of-type { + margin-left: 15px; + } + &:last-of-type { + margin-right: 15px; + } + &.active { + background-color: #42b983; + color: #fff; + border-color: #42b983; + &::before { + content: ''; + background: #fff; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + position: relative; + margin-right: 2px; + } + } + } + } + .contextmenu { + margin: 0; + background: #fff; + z-index: 3000; + position: absolute; + list-style-type: none; + padding: 5px 0; + border-radius: 4px; + font-size: 12px; + font-weight: 400; + color: #333; + box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3); + li { + margin: 0; + padding: 7px 16px; + cursor: pointer; + &:hover { + background: #eee; + } + } + } +} +</style> + +<style lang="scss"> +//reset element css of el-icon-close +.tags-view-wrapper { + .tags-view-item { + .el-icon-close { + width: 16px; + height: 16px; + vertical-align: 2px; + border-radius: 50%; + text-align: center; + transition: all .3s cubic-bezier(.645, .045, .355, 1); + transform-origin: 100% 50%; + &:before { + transform: scale(.6); + display: inline-block; + vertical-align: -3px; + } + &:hover { + background-color: #b4bccc; + color: #fff; + } + } + } +} +</style> diff --git a/ruoyi-ui/src/layout/components/index.js b/ruoyi-ui/src/layout/components/index.js new file mode 100644 index 0000000..104bd3a --- /dev/null +++ b/ruoyi-ui/src/layout/components/index.js @@ -0,0 +1,5 @@ +export { default as AppMain } from './AppMain' +export { default as Navbar } from './Navbar' +export { default as Settings } from './Settings' +export { default as Sidebar } from './Sidebar/index.vue' +export { default as TagsView } from './TagsView/index.vue' diff --git a/ruoyi-ui/src/layout/index.vue b/ruoyi-ui/src/layout/index.vue new file mode 100644 index 0000000..dba4393 --- /dev/null +++ b/ruoyi-ui/src/layout/index.vue @@ -0,0 +1,111 @@ +<template> + <div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}"> + <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/> + <sidebar v-if="!sidebar.hide" class="sidebar-container"/> + <div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container"> + <div :class="{'fixed-header':fixedHeader}"> + <navbar/> + <tags-view v-if="needTagsView"/> + </div> + <app-main/> + <right-panel> + <settings/> + </right-panel> + </div> + </div> +</template> + +<script> +import RightPanel from '@/components/RightPanel' +import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components' +import ResizeMixin from './mixin/ResizeHandler' +import { mapState } from 'vuex' +import variables from '@/assets/styles/variables.scss' + +export default { + name: 'Layout', + components: { + AppMain, + Navbar, + RightPanel, + Settings, + Sidebar, + TagsView + }, + mixins: [ResizeMixin], + computed: { + ...mapState({ + theme: state => state.settings.theme, + sideTheme: state => state.settings.sideTheme, + sidebar: state => state.app.sidebar, + device: state => state.app.device, + needTagsView: state => state.settings.tagsView, + fixedHeader: state => state.settings.fixedHeader + }), + classObj() { + return { + hideSidebar: !this.sidebar.opened, + openSidebar: this.sidebar.opened, + withoutAnimation: this.sidebar.withoutAnimation, + mobile: this.device === 'mobile' + } + }, + variables() { + return variables; + } + }, + methods: { + handleClickOutside() { + this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) + } + } +} +</script> + +<style lang="scss" scoped> + @import "~@/assets/styles/mixin.scss"; + @import "~@/assets/styles/variables.scss"; + + .app-wrapper { + @include clearfix; + position: relative; + height: 100%; + width: 100%; + + &.mobile.openSidebar { + position: fixed; + top: 0; + } + } + + .drawer-bg { + background: #000; + opacity: 0.3; + width: 100%; + top: 0; + height: 100%; + position: absolute; + z-index: 999; + } + + .fixed-header { + position: fixed; + top: 0; + right: 0; + z-index: 9; + width: calc(100% - #{$base-sidebar-width}); + transition: width 0.28s; + } + + .hideSidebar .fixed-header { + width: calc(100% - 54px); + } + + .sidebarHide .fixed-header { + width: 100%; + } + + .mobile .fixed-header { + width: 100%; + } +</style> diff --git a/ruoyi-ui/src/layout/mixin/ResizeHandler.js b/ruoyi-ui/src/layout/mixin/ResizeHandler.js new file mode 100644 index 0000000..e8d0df8 --- /dev/null +++ b/ruoyi-ui/src/layout/mixin/ResizeHandler.js @@ -0,0 +1,45 @@ +import store from '@/store' + +const { body } = document +const WIDTH = 992 // refer to Bootstrap's responsive design + +export default { + watch: { + $route(route) { + if (this.device === 'mobile' && this.sidebar.opened) { + store.dispatch('app/closeSideBar', { withoutAnimation: false }) + } + } + }, + beforeMount() { + window.addEventListener('resize', this.$_resizeHandler) + }, + beforeDestroy() { + window.removeEventListener('resize', this.$_resizeHandler) + }, + mounted() { + const isMobile = this.$_isMobile() + if (isMobile) { + store.dispatch('app/toggleDevice', 'mobile') + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_isMobile() { + const rect = body.getBoundingClientRect() + return rect.width - 1 < WIDTH + }, + $_resizeHandler() { + if (!document.hidden) { + const isMobile = this.$_isMobile() + store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') + + if (isMobile) { + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + } + } + } +} diff --git a/ruoyi-ui/src/main.js b/ruoyi-ui/src/main.js new file mode 100644 index 0000000..13c6cf2 --- /dev/null +++ b/ruoyi-ui/src/main.js @@ -0,0 +1,86 @@ +import Vue from 'vue' + +import Cookies from 'js-cookie' + +import Element from 'element-ui' +import './assets/styles/element-variables.scss' + +import '@/assets/styles/index.scss' // global css +import '@/assets/styles/ruoyi.scss' // ruoyi css +import App from './App' +import store from './store' +import router from './router' +import directive from './directive' // directive +import plugins from './plugins' // plugins +import { download } from '@/utils/request' + +import './assets/icons' // icon +import './permission' // permission control +import { getDicts } from "@/api/system/dict/data"; +import { getConfigKey } from "@/api/system/config"; +import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi"; +// 分页组件 +import Pagination from "@/components/Pagination"; +// 自定义表格工具组件 +import RightToolbar from "@/components/RightToolbar" +// 富文本组件 +import Editor from "@/components/Editor" +// 文件上传组件 +import FileUpload from "@/components/FileUpload" +// 图片上传组件 +import ImageUpload from "@/components/ImageUpload" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview" +// 字典标签组件 +import DictTag from '@/components/DictTag' +// 头部标签组件 +import VueMeta from 'vue-meta' +// 字典数据组件 +import DictData from '@/components/DictData' + +// 全局方法挂载 +Vue.prototype.getDicts = getDicts +Vue.prototype.getConfigKey = getConfigKey +Vue.prototype.parseTime = parseTime +Vue.prototype.resetForm = resetForm +Vue.prototype.addDateRange = addDateRange +Vue.prototype.selectDictLabel = selectDictLabel +Vue.prototype.selectDictLabels = selectDictLabels +Vue.prototype.download = download +Vue.prototype.handleTree = handleTree + +// 全局组件挂载 +Vue.component('DictTag', DictTag) +Vue.component('Pagination', Pagination) +Vue.component('RightToolbar', RightToolbar) +Vue.component('Editor', Editor) +Vue.component('FileUpload', FileUpload) +Vue.component('ImageUpload', ImageUpload) +Vue.component('ImagePreview', ImagePreview) + +Vue.use(directive) +Vue.use(plugins) +Vue.use(VueMeta) +DictData.install() + +/** + * If you don't want to use mock-server + * you want to use MockJs for mock api + * you can execute: mockXHR() + * + * Currently MockJs will be used in the production environment, + * please remove it before going online! ! ! + */ + +Vue.use(Element, { + size: Cookies.get('size') || 'medium' // set element-ui default size +}) + +Vue.config.productionTip = false + +new Vue({ + el: '#app', + router, + store, + render: h => h(App) +}) diff --git a/ruoyi-ui/src/permission.js b/ruoyi-ui/src/permission.js new file mode 100644 index 0000000..e1a14da --- /dev/null +++ b/ruoyi-ui/src/permission.js @@ -0,0 +1,56 @@ +import router from './router' +import store from './store' +import { Message } from 'element-ui' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import { getToken } from '@/utils/auth' +import { isRelogin } from '@/utils/request' + +NProgress.configure({ showSpinner: false }) + +const whiteList = ['/login', '/register'] + +router.beforeEach((to, from, next) => { + NProgress.start() + if (getToken()) { + to.meta.title && store.dispatch('settings/setTitle', to.meta.title) + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else { + if (store.getters.roles.length === 0) { + isRelogin.show = true + // 判断当前用户是否已拉取完user_info信息 + store.dispatch('GetInfo').then(() => { + isRelogin.show = false + store.dispatch('GenerateRoutes').then(accessRoutes => { + // 根据roles权限生成可访问的路由表 + router.addRoutes(accessRoutes) // 动态添加可访问路由表 + next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + }) + }).catch(err => { + store.dispatch('LogOut').then(() => { + Message.error(err) + next({ path: '/' }) + }) + }) + } else { + next() + } + } + } else { + // 没有token + if (whiteList.indexOf(to.path) !== -1) { + // 在免登录白名单,直接进入 + next() + } else { + next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页 + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/ruoyi-ui/src/plugins/auth.js b/ruoyi-ui/src/plugins/auth.js new file mode 100644 index 0000000..6c6bc24 --- /dev/null +++ b/ruoyi-ui/src/plugins/auth.js @@ -0,0 +1,60 @@ +import store from '@/store' + +function authPermission(permission) { + const all_permission = "*:*:*"; + const permissions = store.getters && store.getters.permissions + if (permission && permission.length > 0) { + return permissions.some(v => { + return all_permission === v || v === permission + }) + } else { + return false + } +} + +function authRole(role) { + const super_admin = "admin"; + const roles = store.getters && store.getters.roles + if (role && role.length > 0) { + return roles.some(v => { + return super_admin === v || v === role + }) + } else { + return false + } +} + +export default { + // 验证用户是否具备某权限 + hasPermi(permission) { + return authPermission(permission); + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions) { + return permissions.some(item => { + return authPermission(item) + }) + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions) { + return permissions.every(item => { + return authPermission(item) + }) + }, + // 验证用户是否具备某角色 + hasRole(role) { + return authRole(role); + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles) { + return roles.some(item => { + return authRole(item) + }) + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles) { + return roles.every(item => { + return authRole(item) + }) + } +} diff --git a/ruoyi-ui/src/plugins/cache.js b/ruoyi-ui/src/plugins/cache.js new file mode 100644 index 0000000..6b5c00b --- /dev/null +++ b/ruoyi-ui/src/plugins/cache.js @@ -0,0 +1,77 @@ +const sessionCache = { + set (key, value) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + sessionStorage.setItem(key, value) + } + }, + get (key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + return sessionStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + sessionStorage.removeItem(key); + } +} +const localCache = { + set (key, value) { + if (!localStorage) { + return + } + if (key != null && value != null) { + localStorage.setItem(key, value) + } + }, + get (key) { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + return localStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + localStorage.removeItem(key); + } +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache +} diff --git a/ruoyi-ui/src/plugins/download.js b/ruoyi-ui/src/plugins/download.js new file mode 100644 index 0000000..ffb8c14 --- /dev/null +++ b/ruoyi-ui/src/plugins/download.js @@ -0,0 +1,72 @@ +import axios from 'axios' +import { Message } from 'element-ui' +import { saveAs } from 'file-saver' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from "@/utils/ruoyi"; + +const baseURL = process.env.VUE_APP_BASE_API + +export default { + name(name, isDelete = true) { + var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + resource(resource) { + var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource); + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + zip(url, name) { + var url = baseURL + url + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data); + } + }) + }, + saveAs(text, name, opts) { + saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } +} + diff --git a/ruoyi-ui/src/plugins/index.js b/ruoyi-ui/src/plugins/index.js new file mode 100644 index 0000000..d000f2d --- /dev/null +++ b/ruoyi-ui/src/plugins/index.js @@ -0,0 +1,20 @@ +import tab from './tab' +import auth from './auth' +import cache from './cache' +import modal from './modal' +import download from './download' + +export default { + install(Vue) { + // 页签操作 + Vue.prototype.$tab = tab + // 认证对象 + Vue.prototype.$auth = auth + // 缓存对象 + Vue.prototype.$cache = cache + // 模态框对象 + Vue.prototype.$modal = modal + // 下载文件 + Vue.prototype.$download = download + } +} diff --git a/ruoyi-ui/src/plugins/modal.js b/ruoyi-ui/src/plugins/modal.js new file mode 100644 index 0000000..b37ca14 --- /dev/null +++ b/ruoyi-ui/src/plugins/modal.js @@ -0,0 +1,83 @@ +import { Message, MessageBox, Notification, Loading } from 'element-ui' + +let loadingInstance; + +export default { + // 消息提示 + msg(content) { + Message.info(content) + }, + // 错误消息 + msgError(content) { + Message.error(content) + }, + // 成功消息 + msgSuccess(content) { + Message.success(content) + }, + // 警告消息 + msgWarning(content) { + Message.warning(content) + }, + // 弹出提示 + alert(content) { + MessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content) { + MessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content) { + MessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content) { + MessageBox.alert(content, "系统提示", { type: 'warning' }) + }, + // 通知提示 + notify(content) { + Notification.info(content) + }, + // 错误通知 + notifyError(content) { + Notification.error(content); + }, + // 成功通知 + notifySuccess(content) { + Notification.success(content) + }, + // 警告通知 + notifyWarning(content) { + Notification.warning(content) + }, + // 确认窗体 + confirm(content) { + return MessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content) { + return MessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content) { + loadingInstance = Loading.service({ + lock: true, + text: content, + spinner: "el-icon-loading", + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +} diff --git a/ruoyi-ui/src/plugins/tab.js b/ruoyi-ui/src/plugins/tab.js new file mode 100644 index 0000000..b029c0e --- /dev/null +++ b/ruoyi-ui/src/plugins/tab.js @@ -0,0 +1,71 @@ +import store from '@/store' +import router from '@/router'; + +export default { + // 刷新当前tab页签 + refreshPage(obj) { + const { path, query, matched } = router.currentRoute; + if (obj === undefined) { + matched.forEach((m) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query }; + } + } + }); + } + return store.dispatch('tagsView/delCachedView', obj).then(() => { + const { path, query } = obj + router.replace({ + path: '/redirect' + path, + query: query + }) + }) + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj) { + store.dispatch("tagsView/delView", router.currentRoute); + if (obj !== undefined) { + return router.push(obj); + } + }, + // 关闭指定tab页签 + closePage(obj) { + if (obj === undefined) { + return store.dispatch('tagsView/delView', router.currentRoute).then(({ visitedViews }) => { + const latestView = visitedViews.slice(-1)[0] + if (latestView) { + return router.push(latestView.fullPath) + } + return router.push('/'); + }); + } + return store.dispatch('tagsView/delView', obj); + }, + // 关闭所有tab页签 + closeAllPage() { + return store.dispatch('tagsView/delAllViews'); + }, + // 关闭左侧tab页签 + closeLeftPage(obj) { + return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute); + }, + // 关闭右侧tab页签 + closeRightPage(obj) { + return store.dispatch('tagsView/delRightTags', obj || router.currentRoute); + }, + // 关闭其他tab页签 + closeOtherPage(obj) { + return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute); + }, + // 添加tab页签 + openPage(title, url, params) { + var obj = { path: url, meta: { title: title } } + store.dispatch('tagsView/addView', obj); + return router.push({ path: url, query: params }); + }, + // 修改tab页签 + updatePage(obj) { + return store.dispatch('tagsView/updateVisitedView', obj); + } +} diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js new file mode 100644 index 0000000..71907b6 --- /dev/null +++ b/ruoyi-ui/src/router/index.js @@ -0,0 +1,183 @@ +import Vue from 'vue' +import Router from 'vue-router' + +Vue.use(Router) + +/* Layout */ +import Layout from '@/layout' + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register'), + hidden: true + }, + { + path: '/404', + component: () => import('@/views/error/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: 'index', + children: [ + { + path: 'index', + component: () => import('@/views/index'), + name: 'Index', + meta: { title: '首页', icon: 'dashboard', affix: true } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile', + component: () => import('@/views/system/user/profile/index'), + name: 'Profile', + meta: { title: '个人中心', icon: 'user' } + } + ] + } +] + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log'), + name: 'JobLog', + meta: { title: '调度日志', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + } + ] + } +] + +// 防止连续点击多次路由报错 +let routerPush = Router.prototype.push; +let routerReplace = Router.prototype.replace; +// push +Router.prototype.push = function push(location) { + return routerPush.call(this, location).catch(err => err) +} +// replace +Router.prototype.replace = function push(location) { + return routerReplace.call(this, location).catch(err => err) +} + +export default new Router({ + mode: 'history', // 去掉url中的# + scrollBehavior: () => ({ y: 0 }), + routes: constantRoutes +}) diff --git a/ruoyi-ui/src/settings.js b/ruoyi-ui/src/settings.js new file mode 100644 index 0000000..6a0b09f --- /dev/null +++ b/ruoyi-ui/src/settings.js @@ -0,0 +1,44 @@ +module.exports = { + /** + * 侧边栏主题 深色主题theme-dark,浅色主题theme-light + */ + sideTheme: 'theme-dark', + + /** + * 是否系统布局配置 + */ + showSettings: false, + + /** + * 是否显示顶部导航 + */ + topNav: false, + + /** + * 是否显示 tagsView + */ + tagsView: true, + + /** + * 是否固定头部 + */ + fixedHeader: false, + + /** + * 是否显示logo + */ + sidebarLogo: true, + + /** + * 是否显示动态标题 + */ + dynamicTitle: false, + + /** + * @type {string | array} 'production' | ['production', 'development'] + * @description Need show err logs component. + * The default is only used in the production env + * If you want to also use it in dev, you can pass ['production', 'development'] + */ + errorLog: 'production' +} diff --git a/ruoyi-ui/src/store/getters.js b/ruoyi-ui/src/store/getters.js new file mode 100644 index 0000000..8adb1b6 --- /dev/null +++ b/ruoyi-ui/src/store/getters.js @@ -0,0 +1,19 @@ +const getters = { + sidebar: state => state.app.sidebar, + size: state => state.app.size, + device: state => state.app.device, + dict: state => state.dict.dict, + visitedViews: state => state.tagsView.visitedViews, + cachedViews: state => state.tagsView.cachedViews, + token: state => state.user.token, + avatar: state => state.user.avatar, + name: state => state.user.name, + introduction: state => state.user.introduction, + roles: state => state.user.roles, + permissions: state => state.user.permissions, + permission_routes: state => state.permission.routes, + topbarRouters:state => state.permission.topbarRouters, + defaultRoutes:state => state.permission.defaultRoutes, + sidebarRouters:state => state.permission.sidebarRouters, +} +export default getters diff --git a/ruoyi-ui/src/store/index.js b/ruoyi-ui/src/store/index.js new file mode 100644 index 0000000..97aaef8 --- /dev/null +++ b/ruoyi-ui/src/store/index.js @@ -0,0 +1,25 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import app from './modules/app' +import dict from './modules/dict' +import user from './modules/user' +import tagsView from './modules/tagsView' +import permission from './modules/permission' +import settings from './modules/settings' +import getters from './getters' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + modules: { + app, + dict, + user, + tagsView, + permission, + settings + }, + getters +}) + +export default store diff --git a/ruoyi-ui/src/store/modules/app.js b/ruoyi-ui/src/store/modules/app.js new file mode 100644 index 0000000..3e22d1c --- /dev/null +++ b/ruoyi-ui/src/store/modules/app.js @@ -0,0 +1,66 @@ +import Cookies from 'js-cookie' + +const state = { + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, + withoutAnimation: false, + hide: false + }, + device: 'desktop', + size: Cookies.get('size') || 'medium' +} + +const mutations = { + TOGGLE_SIDEBAR: state => { + if (state.sidebar.hide) { + return false; + } + state.sidebar.opened = !state.sidebar.opened + state.sidebar.withoutAnimation = false + if (state.sidebar.opened) { + Cookies.set('sidebarStatus', 1) + } else { + Cookies.set('sidebarStatus', 0) + } + }, + CLOSE_SIDEBAR: (state, withoutAnimation) => { + Cookies.set('sidebarStatus', 0) + state.sidebar.opened = false + state.sidebar.withoutAnimation = withoutAnimation + }, + TOGGLE_DEVICE: (state, device) => { + state.device = device + }, + SET_SIZE: (state, size) => { + state.size = size + Cookies.set('size', size) + }, + SET_SIDEBAR_HIDE: (state, status) => { + state.sidebar.hide = status + } +} + +const actions = { + toggleSideBar({ commit }) { + commit('TOGGLE_SIDEBAR') + }, + closeSideBar({ commit }, { withoutAnimation }) { + commit('CLOSE_SIDEBAR', withoutAnimation) + }, + toggleDevice({ commit }, device) { + commit('TOGGLE_DEVICE', device) + }, + setSize({ commit }, size) { + commit('SET_SIZE', size) + }, + toggleSideBarHide({ commit }, status) { + commit('SET_SIDEBAR_HIDE', status) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/ruoyi-ui/src/store/modules/dict.js b/ruoyi-ui/src/store/modules/dict.js new file mode 100644 index 0000000..7a1b2f0 --- /dev/null +++ b/ruoyi-ui/src/store/modules/dict.js @@ -0,0 +1,50 @@ +const state = { + dict: new Array() +} +const mutations = { + SET_DICT: (state, { key, value }) => { + if (key !== null && key !== "") { + state.dict.push({ + key: key, + value: value + }) + } + }, + REMOVE_DICT: (state, key) => { + try { + for (let i = 0; i < state.dict.length; i++) { + if (state.dict[i].key == key) { + state.dict.splice(i, 1) + return true + } + } + } catch (e) { + } + }, + CLEAN_DICT: (state) => { + state.dict = new Array() + } +} + +const actions = { + // 设置字典 + setDict({ commit }, data) { + commit('SET_DICT', data) + }, + // 删除字典 + removeDict({ commit }, key) { + commit('REMOVE_DICT', key) + }, + // 清空字典 + cleanDict({ commit }) { + commit('CLEAN_DICT') + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js new file mode 100644 index 0000000..2287665 --- /dev/null +++ b/ruoyi-ui/src/store/modules/permission.js @@ -0,0 +1,133 @@ +import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' +import { getRouters } from '@/api/menu' +import Layout from '@/layout/index' +import ParentView from '@/components/ParentView' +import InnerLink from '@/layout/components/InnerLink' + +const permission = { + state: { + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [] + }, + mutations: { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes + state.routes = constantRoutes.concat(routes) + }, + SET_DEFAULT_ROUTES: (state, routes) => { + state.defaultRoutes = constantRoutes.concat(routes) + }, + SET_TOPBAR_ROUTES: (state, routes) => { + state.topbarRouters = routes + }, + SET_SIDEBAR_ROUTERS: (state, routes) => { + state.sidebarRouters = routes + }, + }, + actions: { + // 生成路由 + GenerateRoutes({ commit }) { + return new Promise(resolve => { + // 向后端请求路由数据 + getRouters().then(res => { + const sdata = JSON.parse(JSON.stringify(res.data)) + const rdata = JSON.parse(JSON.stringify(res.data)) + const sidebarRoutes = filterAsyncRouter(sdata) + const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes); + rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) + router.addRoutes(asyncRoutes); + commit('SET_ROUTES', rewriteRoutes) + commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) + commit('SET_DEFAULT_ROUTES', sidebarRoutes) + commit('SET_TOPBAR_ROUTES', sidebarRoutes) + resolve(rewriteRoutes) + }) + }) + } + } +} + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { + return asyncRouterMap.filter(route => { + if (type && route.children) { + route.children = filterChildren(route.children) + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout + } else if (route.component === 'ParentView') { + route.component = ParentView + } else if (route.component === 'InnerLink') { + route.component = InnerLink + } else { + route.component = loadView(route.component) + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type) + } else { + delete route['children'] + delete route['redirect'] + } + return true + }) +} + +function filterChildren(childrenMap, lastRouter = false) { + var children = [] + childrenMap.forEach((el, index) => { + if (el.children && el.children.length) { + if (el.component === 'ParentView' && !lastRouter) { + el.children.forEach(c => { + c.path = el.path + '/' + c.path + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)) + return + } + children.push(c) + }) + return + } + } + if (lastRouter) { + el.path = lastRouter.path + '/' + el.path + } + children = children.concat(el) + }) + return children +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = [] + routes.forEach(route => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route) + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route) + } + } + }) + return res +} + +export const loadView = (view) => { + if (process.env.NODE_ENV === 'development') { + return (resolve) => require([`@/views/${view}`], resolve) + } else { + // 使用 import 实现生产环境的路由懒加载 + return () => import(`@/views/${view}`) + } +} + +export default permission diff --git a/ruoyi-ui/src/store/modules/settings.js b/ruoyi-ui/src/store/modules/settings.js new file mode 100644 index 0000000..2455a1e --- /dev/null +++ b/ruoyi-ui/src/store/modules/settings.js @@ -0,0 +1,42 @@ +import defaultSettings from '@/settings' + +const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings + +const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' +const state = { + title: '', + theme: storageSetting.theme || '#409EFF', + sideTheme: storageSetting.sideTheme || sideTheme, + showSettings: showSettings, + topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, + tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, + fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, + sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, + dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle +} +const mutations = { + CHANGE_SETTING: (state, { key, value }) => { + if (state.hasOwnProperty(key)) { + state[key] = value + } + } +} + +const actions = { + // 修改布局设置 + changeSetting({ commit }, data) { + commit('CHANGE_SETTING', data) + }, + // 设置网页标题 + setTitle({ commit }, title) { + state.title = title + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/ruoyi-ui/src/store/modules/tagsView.js b/ruoyi-ui/src/store/modules/tagsView.js new file mode 100644 index 0000000..5fc011c --- /dev/null +++ b/ruoyi-ui/src/store/modules/tagsView.js @@ -0,0 +1,228 @@ +const state = { + visitedViews: [], + cachedViews: [], + iframeViews: [] +} + +const mutations = { + ADD_IFRAME_VIEW: (state, view) => { + if (state.iframeViews.some(v => v.path === view.path)) return + state.iframeViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + ADD_VISITED_VIEW: (state, view) => { + if (state.visitedViews.some(v => v.path === view.path)) return + state.visitedViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + ADD_CACHED_VIEW: (state, view) => { + if (state.cachedViews.includes(view.name)) return + if (view.meta && !view.meta.noCache) { + state.cachedViews.push(view.name) + } + }, + DEL_VISITED_VIEW: (state, view) => { + for (const [i, v] of state.visitedViews.entries()) { + if (v.path === view.path) { + state.visitedViews.splice(i, 1) + break + } + } + state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) + }, + DEL_IFRAME_VIEW: (state, view) => { + state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) + }, + DEL_CACHED_VIEW: (state, view) => { + const index = state.cachedViews.indexOf(view.name) + index > -1 && state.cachedViews.splice(index, 1) + }, + + DEL_OTHERS_VISITED_VIEWS: (state, view) => { + state.visitedViews = state.visitedViews.filter(v => { + return v.meta.affix || v.path === view.path + }) + state.iframeViews = state.iframeViews.filter(item => item.path === view.path) + }, + DEL_OTHERS_CACHED_VIEWS: (state, view) => { + const index = state.cachedViews.indexOf(view.name) + if (index > -1) { + state.cachedViews = state.cachedViews.slice(index, index + 1) + } else { + state.cachedViews = [] + } + }, + DEL_ALL_VISITED_VIEWS: state => { + // keep affix tags + const affixTags = state.visitedViews.filter(tag => tag.meta.affix) + state.visitedViews = affixTags + state.iframeViews = [] + }, + DEL_ALL_CACHED_VIEWS: state => { + state.cachedViews = [] + }, + UPDATE_VISITED_VIEW: (state, view) => { + for (let v of state.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + }, + DEL_RIGHT_VIEWS: (state, view) => { + const index = state.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + state.visitedViews = state.visitedViews.filter((item, idx) => { + if (idx <= index || (item.meta && item.meta.affix)) { + return true + } + const i = state.cachedViews.indexOf(item.name) + if (i > -1) { + state.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = state.iframeViews.findIndex(v => v.path === item.path) + state.iframeViews.splice(fi, 1) + } + return false + }) + }, + DEL_LEFT_VIEWS: (state, view) => { + const index = state.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + state.visitedViews = state.visitedViews.filter((item, idx) => { + if (idx >= index || (item.meta && item.meta.affix)) { + return true + } + const i = state.cachedViews.indexOf(item.name) + if (i > -1) { + state.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = state.iframeViews.findIndex(v => v.path === item.path) + state.iframeViews.splice(fi, 1) + } + return false + }) + } +} + +const actions = { + addView({ dispatch }, view) { + dispatch('addVisitedView', view) + dispatch('addCachedView', view) + }, + addIframeView({ commit }, view) { + commit('ADD_IFRAME_VIEW', view) + }, + addVisitedView({ commit }, view) { + commit('ADD_VISITED_VIEW', view) + }, + addCachedView({ commit }, view) { + commit('ADD_CACHED_VIEW', view) + }, + delView({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delVisitedView', view) + dispatch('delCachedView', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delVisitedView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_VISITED_VIEW', view) + resolve([...state.visitedViews]) + }) + }, + delIframeView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_IFRAME_VIEW', view) + resolve([...state.iframeViews]) + }) + }, + delCachedView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_CACHED_VIEW', view) + resolve([...state.cachedViews]) + }) + }, + delOthersViews({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delOthersVisitedViews', view) + dispatch('delOthersCachedViews', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delOthersVisitedViews({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_OTHERS_VISITED_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delOthersCachedViews({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_OTHERS_CACHED_VIEWS', view) + resolve([...state.cachedViews]) + }) + }, + delAllViews({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delAllVisitedViews', view) + dispatch('delAllCachedViews', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delAllVisitedViews({ commit, state }) { + return new Promise(resolve => { + commit('DEL_ALL_VISITED_VIEWS') + resolve([...state.visitedViews]) + }) + }, + delAllCachedViews({ commit, state }) { + return new Promise(resolve => { + commit('DEL_ALL_CACHED_VIEWS') + resolve([...state.cachedViews]) + }) + }, + updateVisitedView({ commit }, view) { + commit('UPDATE_VISITED_VIEW', view) + }, + delRightTags({ commit }, view) { + return new Promise(resolve => { + commit('DEL_RIGHT_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delLeftTags({ commit }, view) { + return new Promise(resolve => { + commit('DEL_LEFT_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js new file mode 100644 index 0000000..cdbab1e --- /dev/null +++ b/ruoyi-ui/src/store/modules/user.js @@ -0,0 +1,101 @@ +import { login, logout, getInfo } from '@/api/login' +import { getToken, setToken, removeToken } from '@/utils/auth' + +const user = { + state: { + token: getToken(), + id: '', + name: '', + avatar: '', + roles: [], + permissions: [] + }, + + mutations: { + SET_TOKEN: (state, token) => { + state.token = token + }, + SET_ID: (state, id) => { + state.id = id + }, + SET_NAME: (state, name) => { + state.name = name + }, + SET_AVATAR: (state, avatar) => { + state.avatar = avatar + }, + SET_ROLES: (state, roles) => { + state.roles = roles + }, + SET_PERMISSIONS: (state, permissions) => { + state.permissions = permissions + } + }, + + actions: { + // 登录 + Login({ commit }, userInfo) { + const username = userInfo.username.trim() + const password = userInfo.password + const code = userInfo.code + const uuid = userInfo.uuid + return new Promise((resolve, reject) => { + login(username, password, code, uuid).then(res => { + setToken(res.token) + commit('SET_TOKEN', res.token) + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 获取用户信息 + GetInfo({ commit, state }) { + return new Promise((resolve, reject) => { + getInfo().then(res => { + const user = res.user + const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; + if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 + commit('SET_ROLES', res.roles) + commit('SET_PERMISSIONS', res.permissions) + } else { + commit('SET_ROLES', ['ROLE_DEFAULT']) + } + commit('SET_ID', user.userId) + commit('SET_NAME', user.userName) + commit('SET_AVATAR', avatar) + resolve(res) + }).catch(error => { + reject(error) + }) + }) + }, + + // 退出系统 + LogOut({ commit, state }) { + return new Promise((resolve, reject) => { + logout(state.token).then(() => { + commit('SET_TOKEN', '') + commit('SET_ROLES', []) + commit('SET_PERMISSIONS', []) + removeToken() + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 前端 登出 + FedLogOut({ commit }) { + return new Promise(resolve => { + commit('SET_TOKEN', '') + removeToken() + resolve() + }) + } + } +} + +export default user diff --git a/ruoyi-ui/src/utils/auth.js b/ruoyi-ui/src/utils/auth.js new file mode 100644 index 0000000..08a43d6 --- /dev/null +++ b/ruoyi-ui/src/utils/auth.js @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Admin-Token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} diff --git a/ruoyi-ui/src/utils/dict/Dict.js b/ruoyi-ui/src/utils/dict/Dict.js new file mode 100644 index 0000000..104bd6e --- /dev/null +++ b/ruoyi-ui/src/utils/dict/Dict.js @@ -0,0 +1,82 @@ +import Vue from 'vue' +import { mergeRecursive } from "@/utils/ruoyi"; +import DictMeta from './DictMeta' +import DictData from './DictData' + +const DEFAULT_DICT_OPTIONS = { + types: [], +} + +/** + * @classdesc 字典 + * @property {Object} label 标签对象,内部属性名为字典类型名称 + * @property {Object} dict 字段数组,内部属性名为字典类型名称 + * @property {Array.<DictMeta>} _dictMetas 字典元数据数组 + */ +export default class Dict { + constructor() { + this.owner = null + this.label = {} + this.type = {} + } + + init(options) { + if (options instanceof Array) { + options = { types: options } + } + const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options) + if (opts.types === undefined) { + throw new Error('need dict types') + } + const ps = [] + this._dictMetas = opts.types.map(t => DictMeta.parse(t)) + this._dictMetas.forEach(dictMeta => { + const type = dictMeta.type + Vue.set(this.label, type, {}) + Vue.set(this.type, type, []) + if (dictMeta.lazy) { + return + } + ps.push(loadDict(this, dictMeta)) + }) + return Promise.all(ps) + } + + /** + * 重新加载字典 + * @param {String} type 字典类型 + */ + reloadDict(type) { + const dictMeta = this._dictMetas.find(e => e.type === type) + if (dictMeta === undefined) { + return Promise.reject(`the dict meta of ${type} was not found`) + } + return loadDict(this, dictMeta) + } +} + +/** + * 加载字典 + * @param {Dict} dict 字典 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {Promise} + */ +function loadDict(dict, dictMeta) { + return dictMeta.request(dictMeta) + .then(response => { + const type = dictMeta.type + let dicts = dictMeta.responseConverter(response, dictMeta) + if (!(dicts instanceof Array)) { + console.error('the return of responseConverter must be Array.<DictData>') + dicts = [] + } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) { + console.error('the type of elements in dicts must be DictData') + dicts = [] + } + dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts) + dicts.forEach(d => { + Vue.set(dict.label[type], d.value, d.label) + }) + return dicts + }) +} diff --git a/ruoyi-ui/src/utils/dict/DictConverter.js b/ruoyi-ui/src/utils/dict/DictConverter.js new file mode 100644 index 0000000..0cf5df8 --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictConverter.js @@ -0,0 +1,17 @@ +import DictOptions from './DictOptions' +import DictData from './DictData' + +export default function(dict, dictMeta) { + const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS) + const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS) + return new DictData(dict[label], dict[value], dict) +} + +/** + * 确定字典字段 + * @param {DictData} dict + * @param {...String} fields + */ +function determineDictField(dict, ...fields) { + return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f)) +} diff --git a/ruoyi-ui/src/utils/dict/DictData.js b/ruoyi-ui/src/utils/dict/DictData.js new file mode 100644 index 0000000..afc763e --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictData.js @@ -0,0 +1,13 @@ +/** + * @classdesc 字典数据 + * @property {String} label 标签 + * @property {*} value 标签 + * @property {Object} raw 原始数据 + */ +export default class DictData { + constructor(label, value, raw) { + this.label = label + this.value = value + this.raw = raw + } +} diff --git a/ruoyi-ui/src/utils/dict/DictMeta.js b/ruoyi-ui/src/utils/dict/DictMeta.js new file mode 100644 index 0000000..9779daa --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictMeta.js @@ -0,0 +1,38 @@ +import { mergeRecursive } from "@/utils/ruoyi"; +import DictOptions from './DictOptions' + +/** + * @classdesc 字典元数据 + * @property {String} type 类型 + * @property {Function} request 请求 + * @property {String} label 标签字段 + * @property {String} value 值字段 + */ +export default class DictMeta { + constructor(options) { + this.type = options.type + this.request = options.request + this.responseConverter = options.responseConverter + this.labelField = options.labelField + this.valueField = options.valueField + this.lazy = options.lazy === true + } +} + + +/** + * 解析字典元数据 + * @param {Object} options + * @returns {DictMeta} + */ +DictMeta.parse= function(options) { + let opts = null + if (typeof options === 'string') { + opts = DictOptions.metas[options] || {} + opts.type = options + } else if (typeof options === 'object') { + opts = options + } + opts = mergeRecursive(DictOptions.metas['*'], opts) + return new DictMeta(opts) +} diff --git a/ruoyi-ui/src/utils/dict/DictOptions.js b/ruoyi-ui/src/utils/dict/DictOptions.js new file mode 100644 index 0000000..338a94e --- /dev/null +++ b/ruoyi-ui/src/utils/dict/DictOptions.js @@ -0,0 +1,51 @@ +import { mergeRecursive } from "@/utils/ruoyi"; +import dictConverter from './DictConverter' + +export const options = { + metas: { + '*': { + /** + * 字典请求,方法签名为function(dictMeta: DictMeta): Promise + */ + request: (dictMeta) => { + console.log(`load dict ${dictMeta.type}`) + return Promise.resolve([]) + }, + /** + * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData + */ + responseConverter, + labelField: 'label', + valueField: 'value', + }, + }, + /** + * 默认标签字段 + */ + DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'], + /** + * 默认值字段 + */ + DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'], +} + +/** + * 映射字典 + * @param {Object} response 字典数据 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {DictData} + */ +function responseConverter(response, dictMeta) { + const dicts = response.content instanceof Array ? response.content : response + if (dicts === undefined) { + console.warn(`no dict data of "${dictMeta.type}" found in the response`) + return [] + } + return dicts.map(d => dictConverter(d, dictMeta)) +} + +export function mergeOptions(src) { + mergeRecursive(options, src) +} + +export default options diff --git a/ruoyi-ui/src/utils/dict/index.js b/ruoyi-ui/src/utils/dict/index.js new file mode 100644 index 0000000..215eb9e --- /dev/null +++ b/ruoyi-ui/src/utils/dict/index.js @@ -0,0 +1,33 @@ +import Dict from './Dict' +import { mergeOptions } from './DictOptions' + +export default function(Vue, options) { + mergeOptions(options) + Vue.mixin({ + data() { + if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) { + return {} + } + const dict = new Dict() + dict.owner = this + return { + dict + } + }, + created() { + if (!(this.dict instanceof Dict)) { + return + } + options.onCreated && options.onCreated(this.dict) + this.dict.init(this.$options.dicts).then(() => { + options.onReady && options.onReady(this.dict) + this.$nextTick(() => { + this.$emit('dictReady', this.dict) + if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) { + this.$options.methods.onDictReady.call(this, this.dict) + } + }) + }) + }, + }) +} diff --git a/ruoyi-ui/src/utils/errorCode.js b/ruoyi-ui/src/utils/errorCode.js new file mode 100644 index 0000000..d2111ee --- /dev/null +++ b/ruoyi-ui/src/utils/errorCode.js @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} diff --git a/ruoyi-ui/src/utils/generator/config.js b/ruoyi-ui/src/utils/generator/config.js new file mode 100644 index 0000000..7abf227 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/config.js @@ -0,0 +1,438 @@ +export const formConf = { + formRef: 'elForm', + formModel: 'formData', + size: 'medium', + labelPosition: 'right', + labelWidth: 100, + formRules: 'rules', + gutter: 15, + disabled: false, + span: 24, + formBtns: true +} + +export const inputComponents = [ + { + label: '单行文本', + tag: 'el-input', + tagIcon: 'input', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '多行文本', + tag: 'el-input', + tagIcon: 'textarea', + type: 'textarea', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + autosize: { + minRows: 4, + maxRows: 4 + }, + style: { width: '100%' }, + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '密码', + tag: 'el-input', + tagIcon: 'password', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + 'show-password': true, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '计数器', + tag: 'el-input-number', + tagIcon: 'number', + placeholder: '', + defaultValue: undefined, + span: 24, + labelWidth: null, + min: undefined, + max: undefined, + step: undefined, + 'step-strictly': false, + precision: undefined, + 'controls-position': '', + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input-number' + } +] + +export const selectComponents = [ + { + label: '下拉选择', + tag: 'el-select', + tagIcon: 'select', + placeholder: '请选择', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + disabled: false, + required: true, + filterable: false, + multiple: false, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/select' + }, + { + label: '级联选择', + tag: 'el-cascader', + tagIcon: 'cascader', + placeholder: '请选择', + defaultValue: [], + span: 24, + labelWidth: null, + style: { width: '100%' }, + props: { + props: { + multiple: false + } + }, + 'show-all-levels': true, + disabled: false, + clearable: true, + filterable: false, + required: true, + options: [{ + id: 1, + value: 1, + label: '选项1', + children: [{ + id: 2, + value: 2, + label: '选项1-1' + }] + }], + dataType: 'dynamic', + labelKey: 'label', + valueKey: 'value', + childrenKey: 'children', + separator: '/', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/cascader' + }, + { + label: '单选框组', + tag: 'el-radio-group', + tagIcon: 'radio', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'medium', + disabled: false, + required: true, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/radio' + }, + { + label: '多选框组', + tag: 'el-checkbox-group', + tagIcon: 'checkbox', + defaultValue: [], + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'medium', + disabled: false, + required: true, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/checkbox' + }, + { + label: '开关', + tag: 'el-switch', + tagIcon: 'switch', + defaultValue: false, + span: 24, + labelWidth: null, + style: {}, + disabled: false, + required: true, + 'active-text': '', + 'inactive-text': '', + 'active-color': null, + 'inactive-color': null, + 'active-value': true, + 'inactive-value': false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/switch' + }, + { + label: '滑块', + tag: 'el-slider', + tagIcon: 'slider', + defaultValue: null, + span: 24, + labelWidth: null, + disabled: false, + required: true, + min: 0, + max: 100, + step: 1, + 'show-stops': false, + range: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/slider' + }, + { + label: '时间选择', + tag: 'el-time-picker', + tagIcon: 'time', + placeholder: '请选择', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'picker-options': { + selectableRange: '00:00:00-23:59:59' + }, + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' + }, + { + label: '时间范围', + tag: 'el-time-picker', + tagIcon: 'time-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'is-range': true, + 'range-separator': '至', + 'start-placeholder': '开始时间', + 'end-placeholder': '结束时间', + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' + }, + { + label: '日期选择', + tag: 'el-date-picker', + tagIcon: 'date', + placeholder: '请选择', + defaultValue: null, + type: 'date', + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + format: 'yyyy-MM-dd', + 'value-format': 'yyyy-MM-dd', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' + }, + { + label: '日期范围', + tag: 'el-date-picker', + tagIcon: 'date-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + type: 'daterange', + 'range-separator': '至', + 'start-placeholder': '开始日期', + 'end-placeholder': '结束日期', + disabled: false, + clearable: true, + required: true, + format: 'yyyy-MM-dd', + 'value-format': 'yyyy-MM-dd', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' + }, + { + label: '评分', + tag: 'el-rate', + tagIcon: 'rate', + defaultValue: 0, + span: 24, + labelWidth: null, + style: {}, + max: 5, + 'allow-half': false, + 'show-text': false, + 'show-score': false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/rate' + }, + { + label: '颜色选择', + tag: 'el-color-picker', + tagIcon: 'color', + defaultValue: null, + labelWidth: null, + 'show-alpha': false, + 'color-format': '', + disabled: false, + required: true, + size: 'medium', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/color-picker' + }, + { + label: '上传', + tag: 'el-upload', + tagIcon: 'upload', + action: 'https://jsonplaceholder.typicode.com/posts/', + defaultValue: null, + labelWidth: null, + disabled: false, + required: true, + accept: '', + name: 'file', + 'auto-upload': true, + showTip: false, + buttonText: '点击上传', + fileSize: 2, + sizeUnit: 'MB', + 'list-type': 'text', + multiple: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/upload' + } +] + +export const layoutComponents = [ + { + layout: 'rowFormItem', + tagIcon: 'row', + type: 'default', + justify: 'start', + align: 'top', + label: '行容器', + layoutTree: true, + children: [], + document: 'https://element.eleme.cn/#/zh-CN/component/layout' + }, + { + layout: 'colFormItem', + label: '按钮', + changeTag: true, + labelWidth: null, + tag: 'el-button', + tagIcon: 'button', + span: 24, + default: '主要按钮', + type: 'primary', + icon: 'el-icon-search', + size: 'medium', + disabled: false, + document: 'https://element.eleme.cn/#/zh-CN/component/button' + } +] + +// 组件rule的触发方式,无触发方式的组件不生成rule +export const trigger = { + 'el-input': 'blur', + 'el-input-number': 'blur', + 'el-select': 'change', + 'el-radio-group': 'change', + 'el-checkbox-group': 'change', + 'el-cascader': 'change', + 'el-time-picker': 'change', + 'el-date-picker': 'change', + 'el-rate': 'change' +} diff --git a/ruoyi-ui/src/utils/generator/css.js b/ruoyi-ui/src/utils/generator/css.js new file mode 100644 index 0000000..c1c62e6 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/css.js @@ -0,0 +1,18 @@ +const styles = { + 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', + 'el-upload': '.el-upload__tip{line-height: 1.2;}' +} + +function addCss(cssList, el) { + const css = styles[el.tag] + css && cssList.indexOf(css) === -1 && cssList.push(css) + if (el.children) { + el.children.forEach(el2 => addCss(cssList, el2)) + } +} + +export function makeUpCss(conf) { + const cssList = [] + conf.fields.forEach(el => addCss(cssList, el)) + return cssList.join('\n') +} diff --git a/ruoyi-ui/src/utils/generator/drawingDefault.js b/ruoyi-ui/src/utils/generator/drawingDefault.js new file mode 100644 index 0000000..09f133c --- /dev/null +++ b/ruoyi-ui/src/utils/generator/drawingDefault.js @@ -0,0 +1,29 @@ +export default [ + { + layout: 'colFormItem', + tagIcon: 'input', + label: '手机号', + vModel: 'mobile', + formId: 6, + tag: 'el-input', + placeholder: '请输入手机号', + defaultValue: '', + span: 24, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': 'el-icon-mobile', + 'suffix-icon': '', + maxlength: 11, + 'show-word-limit': true, + readonly: false, + disabled: false, + required: true, + changeTag: true, + regList: [{ + pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', + message: '手机号格式错误' + }] + } +] diff --git a/ruoyi-ui/src/utils/generator/html.js b/ruoyi-ui/src/utils/generator/html.js new file mode 100644 index 0000000..9bcc536 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/html.js @@ -0,0 +1,359 @@ +/* eslint-disable max-len */ +import { trigger } from './config' + +let confGlobal +let someSpanIsNot24 + +export function dialogWrapper(str) { + return `<el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" title="Dialog Title"> + ${str} + <div slot="footer"> + <el-button @click="close">取消</el-button> + <el-button type="primary" @click="handleConfirm">确定</el-button> + </div> + </el-dialog>` +} + +export function vueTemplate(str) { + return `<template> + <div> + ${str} + </div> + </template>` +} + +export function vueScript(str) { + return `<script> + ${str} + </script>` +} + +export function cssStyle(cssStr) { + return `<style> + ${cssStr} + </style>` +} + +function buildFormTemplate(conf, child, type) { + let labelPosition = '' + if (conf.labelPosition !== 'right') { + labelPosition = `label-position="${conf.labelPosition}"` + } + const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '' + let str = `<el-form ref="${conf.formRef}" :model="${conf.formModel}" :rules="${conf.formRules}" size="${conf.size}" ${disabled} label-width="${conf.labelWidth}px" ${labelPosition}> + ${child} + ${buildFromBtns(conf, type)} + </el-form>` + if (someSpanIsNot24) { + str = `<el-row :gutter="${conf.gutter}"> + ${str} + </el-row>` + } + return str +} + +function buildFromBtns(conf, type) { + let str = '' + if (conf.formBtns && type === 'file') { + str = `<el-form-item size="large"> + <el-button type="primary" @click="submitForm">提交</el-button> + <el-button @click="resetForm">重置</el-button> + </el-form-item>` + if (someSpanIsNot24) { + str = `<el-col :span="24"> + ${str} + </el-col>` + } + } + return str +} + +// span不为24的用el-col包裹 +function colWrapper(element, str) { + if (someSpanIsNot24 || element.span !== 24) { + return `<el-col :span="${element.span}"> + ${str} + </el-col>` + } + return str +} + +const layouts = { + colFormItem(element) { + let labelWidth = '' + if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) { + labelWidth = `label-width="${element.labelWidth}px"` + } + const required = !trigger[element.tag] && element.required ? 'required' : '' + const tagDom = tags[element.tag] ? tags[element.tag](element) : null + let str = `<el-form-item ${labelWidth} label="${element.label}" prop="${element.vModel}" ${required}> + ${tagDom} + </el-form-item>` + str = colWrapper(element, str) + return str + }, + rowFormItem(element) { + const type = element.type === 'default' ? '' : `type="${element.type}"` + const justify = element.type === 'default' ? '' : `justify="${element.justify}"` + const align = element.type === 'default' ? '' : `align="${element.align}"` + const gutter = element.gutter ? `gutter="${element.gutter}"` : '' + const children = element.children.map(el => layouts[el.layout](el)) + let str = `<el-row ${type} ${justify} ${align} ${gutter}> + ${children.join('\n')} + </el-row>` + str = colWrapper(element, str) + return str + } +} + +const tags = { + 'el-button': el => { + const { + tag, disabled + } = attrBuilder(el) + const type = el.type ? `type="${el.type}"` : '' + const icon = el.icon ? `icon="${el.icon}"` : '' + const size = el.size ? `size="${el.size}"` : '' + let child = buildElButtonChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}</${el.tag}>` + }, + 'el-input': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '' + const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '' + const readonly = el.readonly ? 'readonly' : '' + const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : '' + const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : '' + const showPassword = el['show-password'] ? 'show-password' : '' + const type = el.type ? `type="${el.type}"` : '' + const autosize = el.autosize && el.autosize.minRows + ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"` + : '' + let child = buildElInputChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${el.tag}>` + }, + 'el-input-number': el => { + const { disabled, vModel, placeholder } = attrBuilder(el) + const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : '' + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const stepStrictly = el['step-strictly'] ? 'step-strictly' : '' + const precision = el.precision ? `:precision='${el.precision}'` : '' + + return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${el.tag}>` + }, + 'el-select': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const filterable = el.filterable ? 'filterable' : '' + const multiple = el.multiple ? 'multiple' : '' + let child = buildElSelectChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${el.tag}>` + }, + 'el-radio-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + let child = buildElRadioGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${size} ${disabled}>${child}</${el.tag}>` + }, + 'el-checkbox-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const min = el.min ? `:min="${el.min}"` : '' + const max = el.max ? `:max="${el.max}"` : '' + let child = buildElCheckboxGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${el.tag}>` + }, + 'el-switch': el => { + const { disabled, vModel } = attrBuilder(el) + const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : '' + const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : '' + const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : '' + const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : '' + const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : '' + const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : '' + + return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${el.tag}>` + }, + 'el-cascader': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const options = el.options ? `:options="${el.vModel}Options"` : '' + const props = el.props ? `:props="${el.vModel}Props"` : '' + const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"' + const filterable = el.filterable ? 'filterable' : '' + const separator = el.separator === '/' ? '' : `separator="${el.separator}"` + + return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${el.tag}>` + }, + 'el-slider': el => { + const { disabled, vModel } = attrBuilder(el) + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const range = el.range ? 'range' : '' + const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : '' + + return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${el.tag}>` + }, + 'el-time-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const isRange = el['is-range'] ? 'is-range' : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : '' + + return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${el.tag}>` + }, + 'el-date-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const type = el.type === 'date' ? '' : `type="${el.type}"` + const readonly = el.readonly ? 'readonly' : '' + + return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${el.tag}>` + }, + 'el-rate': el => { + const { disabled, vModel } = attrBuilder(el) + const max = el.max ? `:max='${el.max}'` : '' + const allowHalf = el['allow-half'] ? 'allow-half' : '' + const showText = el['show-text'] ? 'show-text' : '' + const showScore = el['show-score'] ? 'show-score' : '' + + return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}></${el.tag}>` + }, + 'el-color-picker': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const showAlpha = el['show-alpha'] ? 'show-alpha' : '' + const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : '' + + return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${el.tag}>` + }, + 'el-upload': el => { + const disabled = el.disabled ? ':disabled=\'true\'' : '' + const action = el.action ? `:action="${el.vModel}Action"` : '' + const multiple = el.multiple ? 'multiple' : '' + const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '' + const accept = el.accept ? `accept="${el.accept}"` : '' + const name = el.name !== 'file' ? `name="${el.name}"` : '' + const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : '' + const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"` + const fileList = `:file-list="${el.vModel}fileList"` + const ref = `ref="${el.vModel}"` + let child = buildElUploadChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${el.tag}>` + } +} + +function attrBuilder(el) { + return { + vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`, + clearable: el.clearable ? 'clearable' : '', + placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '', + width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '', + disabled: el.disabled ? ':disabled=\'true\'' : '' + } +} + +// el-buttin 子级 +function buildElButtonChild(conf) { + const children = [] + if (conf.default) { + children.push(conf.default) + } + return children.join('\n') +} + +// el-input innerHTML +function buildElInputChild(conf) { + const children = [] + if (conf.prepend) { + children.push(`<template slot="prepend">${conf.prepend}</template>`) + } + if (conf.append) { + children.push(`<template slot="append">${conf.append}</template>`) + } + return children.join('\n') +} + +function buildElSelectChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + children.push(`<el-option v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`) + } + return children.join('\n') +} + +function buildElRadioGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`) + } + return children.join('\n') +} + +function buildElCheckboxGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`) + } + return children.join('\n') +} + +function buildElUploadChild(conf) { + const list = [] + if (conf['list-type'] === 'picture-card') list.push('<i class="el-icon-plus"></i>') + else list.push(`<el-button size="small" type="primary" icon="el-icon-upload">${conf.buttonText}</el-button>`) + if (conf.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件</div>`) + return list.join('\n') +} + +export function makeUpHtml(conf, type) { + const htmlList = [] + confGlobal = conf + someSpanIsNot24 = conf.fields.some(item => item.span !== 24) + conf.fields.forEach(el => { + htmlList.push(layouts[el.layout](el)) + }) + const htmlStr = htmlList.join('\n') + + let temp = buildFormTemplate(conf, htmlStr, type) + if (type === 'dialog') { + temp = dialogWrapper(temp) + } + confGlobal = null + return temp +} diff --git a/ruoyi-ui/src/utils/generator/icon.json b/ruoyi-ui/src/utils/generator/icon.json new file mode 100644 index 0000000..2d9999a --- /dev/null +++ b/ruoyi-ui/src/utils/generator/icon.json @@ -0,0 +1 @@ +["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] \ No newline at end of file diff --git a/ruoyi-ui/src/utils/generator/js.js b/ruoyi-ui/src/utils/generator/js.js new file mode 100644 index 0000000..ee8668d --- /dev/null +++ b/ruoyi-ui/src/utils/generator/js.js @@ -0,0 +1,235 @@ +import { exportDefault, titleCase } from '@/utils/index' +import { trigger } from './config' + +const units = { + KB: '1024', + MB: '1024 / 1024', + GB: '1024 / 1024 / 1024' +} +let confGlobal +const inheritAttrs = { + file: '', + dialog: 'inheritAttrs: false,' +} + + +export function makeUpJs(conf, type) { + confGlobal = conf = JSON.parse(JSON.stringify(conf)) + const dataList = [] + const ruleList = [] + const optionsList = [] + const propsList = [] + const methodList = mixinMethod(type) + const uploadVarList = [] + + conf.fields.forEach(el => { + buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) + }) + + const script = buildexport( + conf, + type, + dataList.join('\n'), + ruleList.join('\n'), + optionsList.join('\n'), + uploadVarList.join('\n'), + propsList.join('\n'), + methodList.join('\n') + ) + confGlobal = null + return script +} + +function buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) { + buildData(el, dataList) + buildRules(el, ruleList) + + if (el.options && el.options.length) { + buildOptions(el, optionsList) + if (el.dataType === 'dynamic') { + const model = `${el.vModel}Options` + const options = titleCase(model) + buildOptionMethod(`get${options}`, model, methodList) + } + } + + if (el.props && el.props.props) { + buildProps(el, propsList) + } + + if (el.action && el.tag === 'el-upload') { + uploadVarList.push( + `${el.vModel}Action: '${el.action}', + ${el.vModel}fileList: [],` + ) + methodList.push(buildBeforeUpload(el)) + if (!el['auto-upload']) { + methodList.push(buildSubmitUpload(el)) + } + } + + if (el.children) { + el.children.forEach(el2 => { + buildAttributes(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) + }) + } +} + +function mixinMethod(type) { + const list = []; const + minxins = { + file: confGlobal.formBtns ? { + submitForm: `submitForm() { + this.$refs['${confGlobal.formRef}'].validate(valid => { + if(!valid) return + // TODO 提交表单 + }) + },`, + resetForm: `resetForm() { + this.$refs['${confGlobal.formRef}'].resetFields() + },` + } : null, + dialog: { + onOpen: 'onOpen() {},', + onClose: `onClose() { + this.$refs['${confGlobal.formRef}'].resetFields() + },`, + close: `close() { + this.$emit('update:visible', false) + },`, + handleConfirm: `handleConfirm() { + this.$refs['${confGlobal.formRef}'].validate(valid => { + if(!valid) return + this.close() + }) + },` + } + } + + const methods = minxins[type] + if (methods) { + Object.keys(methods).forEach(key => { + list.push(methods[key]) + }) + } + + return list +} + +function buildData(conf, dataList) { + if (conf.vModel === undefined) return + let defaultValue + if (typeof (conf.defaultValue) === 'string' && !conf.multiple) { + defaultValue = `'${conf.defaultValue}'` + } else { + defaultValue = `${JSON.stringify(conf.defaultValue)}` + } + dataList.push(`${conf.vModel}: ${defaultValue},`) +} + +function buildRules(conf, ruleList) { + if (conf.vModel === undefined) return + const rules = [] + if (trigger[conf.tag]) { + if (conf.required) { + const type = Array.isArray(conf.defaultValue) ? 'type: \'array\',' : '' + let message = Array.isArray(conf.defaultValue) ? `请至少选择一个${conf.vModel}` : conf.placeholder + if (message === undefined) message = `${conf.label}不能为空` + rules.push(`{ required: true, ${type} message: '${message}', trigger: '${trigger[conf.tag]}' }`) + } + if (conf.regList && Array.isArray(conf.regList)) { + conf.regList.forEach(item => { + if (item.pattern) { + rules.push(`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${trigger[conf.tag]}' }`) + } + }) + } + ruleList.push(`${conf.vModel}: [${rules.join(',')}],`) + } +} + +function buildOptions(conf, optionsList) { + if (conf.vModel === undefined) return + if (conf.dataType === 'dynamic') { conf.options = [] } + const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},` + optionsList.push(str) +} + +function buildProps(conf, propsList) { + if (conf.dataType === 'dynamic') { + conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey) + conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey) + conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey) + } + const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},` + propsList.push(str) +} + +function buildBeforeUpload(conf) { + const unitNum = units[conf.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const + returnList = [] + if (conf.fileSize) { + rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize} + if(!isRightSize){ + this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}') + }` + returnList.push('isRightSize') + } + if (conf.accept) { + acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type) + if(!isAccept){ + this.$message.error('应该选择${conf.accept}类型的文件') + }` + returnList.push('isAccept') + } + const str = `${conf.vModel}BeforeUpload(file) { + ${rightSizeCode} + ${acceptCode} + return ${returnList.join('&&')} + },` + return returnList.length ? str : '' +} + +function buildSubmitUpload(conf) { + const str = `submitUpload() { + this.$refs['${conf.vModel}'].submit() + },` + return str +} + +function buildOptionMethod(methodName, model, methodList) { + const str = `${methodName}() { + // TODO 发起请求获取数据 + this.${model} + },` + methodList.push(str) +} + +function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods) { + const str = `${exportDefault}{ + ${inheritAttrs[type]} + components: {}, + props: [], + data () { + return { + ${conf.formModel}: { + ${data} + }, + ${conf.formRules}: { + ${rules} + }, + ${uploadVar} + ${selectOptions} + ${props} + } + }, + computed: {}, + watch: {}, + created () {}, + mounted () {}, + methods: { + ${methods} + } +}` + return str +} diff --git a/ruoyi-ui/src/utils/generator/render.js b/ruoyi-ui/src/utils/generator/render.js new file mode 100644 index 0000000..e8640f0 --- /dev/null +++ b/ruoyi-ui/src/utils/generator/render.js @@ -0,0 +1,126 @@ +import { makeMap } from '@/utils/index' + +// 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js +const isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' +) + +function vModel(self, dataObject, defaultValue) { + dataObject.props.value = defaultValue + + dataObject.on.input = val => { + self.$emit('input', val) + } +} + +const componentChild = { + 'el-button': { + default(h, conf, key) { + return conf[key] + }, + }, + 'el-input': { + prepend(h, conf, key) { + return <template slot="prepend">{conf[key]}</template> + }, + append(h, conf, key) { + return <template slot="append">{conf[key]}</template> + } + }, + 'el-select': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + list.push(<el-option label={item.label} value={item.value} disabled={item.disabled}></el-option>) + }) + return list + } + }, + 'el-radio-group': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + if (conf.optionType === 'button') list.push(<el-radio-button label={item.value}>{item.label}</el-radio-button>) + else list.push(<el-radio label={item.value} border={conf.border}>{item.label}</el-radio>) + }) + return list + } + }, + 'el-checkbox-group': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + if (conf.optionType === 'button') { + list.push(<el-checkbox-button label={item.value}>{item.label}</el-checkbox-button>) + } else { + list.push(<el-checkbox label={item.value} border={conf.border}>{item.label}</el-checkbox>) + } + }) + return list + } + }, + 'el-upload': { + 'list-type': (h, conf, key) => { + const list = [] + if (conf['list-type'] === 'picture-card') { + list.push(<i class="el-icon-plus"></i>) + } else { + list.push(<el-button size="small" type="primary" icon="el-icon-upload">{conf.buttonText}</el-button>) + } + if (conf.showTip) { + list.push(<div slot="tip" class="el-upload__tip">只能上传不超过 {conf.fileSize}{conf.sizeUnit} 的{conf.accept}文件</div>) + } + return list + } + } +} + +export default { + render(h) { + const dataObject = { + attrs: {}, + props: {}, + on: {}, + style: {} + } + const confClone = JSON.parse(JSON.stringify(this.conf)) + const children = [] + + const childObjs = componentChild[confClone.tag] + if (childObjs) { + Object.keys(childObjs).forEach(key => { + const childFunc = childObjs[key] + if (confClone[key]) { + children.push(childFunc(h, confClone, key)) + } + }) + } + + Object.keys(confClone).forEach(key => { + const val = confClone[key] + if (key === 'vModel') { + vModel(this, dataObject, confClone.defaultValue) + } else if (dataObject[key]) { + dataObject[key] = val + } else if (!isAttr(key)) { + dataObject.props[key] = val + } else { + dataObject.attrs[key] = val + } + }) + return h(this.conf.tag, dataObject, children) + }, + props: ['conf'] +} diff --git a/ruoyi-ui/src/utils/index.js b/ruoyi-ui/src/utils/index.js new file mode 100644 index 0000000..4e65504 --- /dev/null +++ b/ruoyi-ui/src/utils/index.js @@ -0,0 +1,390 @@ +import { parseTime } from './ruoyi' + +/** + * 表格时间格式化 + */ +export function formatDate(cellValue) { + if (cellValue == null || cellValue == "") return ""; + var date = new Date(cellValue) + var year = date.getFullYear() + var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function makeMap(str, expectsLowerCase) { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} + +export const exportDefault = 'export default ' + +export const beautifierConf = { + html: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'separate', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: false, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + }, + js: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'normal', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: true, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + } +} + +// 首字母大小 +export function titleCase(str) { + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) +} + +// 下划转驼峰 +export function camelCase(str) { + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) +} + +export function isNumberStr(str) { + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) +} + diff --git a/ruoyi-ui/src/utils/jsencrypt.js b/ruoyi-ui/src/utils/jsencrypt.js new file mode 100644 index 0000000..78d9523 --- /dev/null +++ b/ruoyi-ui/src/utils/jsencrypt.js @@ -0,0 +1,30 @@ +import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' + +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' + +const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + + 'UP8iWi1Qw0Y=' + +// 加密 +export function encrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +export function decrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} + diff --git a/ruoyi-ui/src/utils/permission.js b/ruoyi-ui/src/utils/permission.js new file mode 100644 index 0000000..1730e33 --- /dev/null +++ b/ruoyi-ui/src/utils/permission.js @@ -0,0 +1,51 @@ +import store from '@/store' + +/** + * 字符权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkPermi(value) { + if (value && value instanceof Array && value.length > 0) { + const permissions = store.getters && store.getters.permissions + const permissionDatas = value + const all_permission = "*:*:*"; + + const hasPermission = permissions.some(permission => { + return all_permission === permission || permissionDatas.includes(permission) + }) + + if (!hasPermission) { + return false + } + return true + } else { + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) + return false + } +} + +/** + * 角色权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkRole(value) { + if (value && value instanceof Array && value.length > 0) { + const roles = store.getters && store.getters.roles + const permissionRoles = value + const super_admin = "admin"; + + const hasRole = roles.some(role => { + return super_admin === role || permissionRoles.includes(role) + }) + + if (!hasRole) { + return false + } + return true + } else { + console.error(`need roles! Like checkRole="['admin','editor']"`) + return false + } +} \ No newline at end of file diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js new file mode 100644 index 0000000..ffb0d21 --- /dev/null +++ b/ruoyi-ui/src/utils/request.js @@ -0,0 +1,152 @@ +import axios from 'axios' +import { Notification, MessageBox, Message, Loading } from 'element-ui' +import store from '@/store' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from "@/utils/ruoyi"; +import cache from '@/plugins/cache' +import { saveAs } from 'file-saver' + +let downloadLoadingInstance; +// 是否显示重新登录 +export let isRelogin = { show: false }; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: process.env.VUE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 + const limitSize = 5 * 1024 * 1024; // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config; + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } + return config +}, error => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + // 未设置状态码则默认成功状态 + const code = res.data.code || 200; + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code === 401) { + if (!isRelogin.show) { + isRelogin.show = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false; + store.dispatch('LogOut').then(() => { + location.href = '/index'; + }) + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + } else if (code === 500) { + Message({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + Message({ message: msg, type: 'warning' }) + return Promise.reject('error') + } else if (code !== 200) { + Notification.error({ title: msg }) + return Promise.reject('error') + } else { + return res.data + } + }, + error => { + console.log('err' + error) + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + Message({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url, params, filename, config) { + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob', + ...config + }).then(async (data) => { + const isBlob = blobValidate(data); + if (isBlob) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) +} + +export default service diff --git a/ruoyi-ui/src/utils/ruoyi.js b/ruoyi-ui/src/utils/ruoyi.js new file mode 100644 index 0000000..44bf9c4 --- /dev/null +++ b/ruoyi-ui/src/utils/ruoyi.js @@ -0,0 +1,233 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 表单重置 +export function resetForm(refName) { + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } +} + +// 添加日期范围 +export function addDateRange(params, dateRange, propName) { + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; +} + +// 回显数据字典 +export function selectDictLabel(datas, value) { + if (value === undefined) { + return ""; + } + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); +} + +// 回显数据字典(字符串、数组) +export function selectDictLabels(datas, value, separator) { + if (value === undefined || value.length ===0) { + return ""; + } + if (Array.isArray(value)) { + value = value.join(","); + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + var match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }) + return actions.join('').substring(0, actions.join('').length - 1); +} + +// 字符串格式化(%s ) +export function sprintf(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; + }); + return flag ? str : ''; +} + +// 转换字符串,undefined,null等转化为"" +export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; +} + +// 数据合并 +export function mergeRecursive(source, target) { + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export function handleTree(data, id, parentId, children) { + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; + + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (let t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; +} + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} + +// 验证是否为blob格式 +export function blobValidate(data) { + return data.type !== 'application/json' +} diff --git a/ruoyi-ui/src/utils/scroll-to.js b/ruoyi-ui/src/utils/scroll-to.js new file mode 100644 index 0000000..c5d8e04 --- /dev/null +++ b/ruoyi-ui/src/utils/scroll-to.js @@ -0,0 +1,58 @@ +Math.easeInOutQuad = function(t, b, c, d) { + t /= d / 2 + if (t < 1) { + return c / 2 * t * t + b + } + t-- + return -c / 2 * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +var requestAnimFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +function move(amount) { + document.documentElement.scrollTop = amount + document.body.parentNode.scrollTop = amount + document.body.scrollTop = amount +} + +function position() { + return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop +} + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export function scrollTo(to, duration, callback) { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = (typeof (duration) === 'undefined') ? 500 : duration + var animateScroll = function() { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + var val = Math.easeInOutQuad(currentTime, start, change, duration) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof (callback) === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/ruoyi-ui/src/utils/validate.js b/ruoyi-ui/src/utils/validate.js new file mode 100644 index 0000000..adfa254 --- /dev/null +++ b/ruoyi-ui/src/utils/validate.js @@ -0,0 +1,83 @@ +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUsername(str) { + const valid_map = ['admin', 'editor'] + return valid_map.indexOf(str.trim()) >= 0 +} + +/** + * @param {string} url + * @returns {Boolean} + */ +export function validURL(url) { + const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ + return reg.test(url) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validLowerCase(str) { + const reg = /^[a-z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUpperCase(str) { + const reg = /^[A-Z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validAlphabets(str) { + const reg = /^[A-Za-z]+$/ + return reg.test(str) +} + +/** + * @param {string} email + * @returns {Boolean} + */ +export function validEmail(email) { + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return reg.test(email) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function isString(str) { + if (typeof str === 'string' || str instanceof String) { + return true + } + return false +} + +/** + * @param {Array} arg + * @returns {Boolean} + */ +export function isArray(arg) { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]' + } + return Array.isArray(arg) +} diff --git a/ruoyi-ui/src/views/components/icons/element-icons.js b/ruoyi-ui/src/views/components/icons/element-icons.js new file mode 100644 index 0000000..9ea4d63 --- /dev/null +++ b/ruoyi-ui/src/views/components/icons/element-icons.js @@ -0,0 +1,3 @@ +const elementIcons = ['platform-eleme', 'eleme', 'delete-solid', 'delete', 's-tools', 'setting', 'user-solid', 'user', 'phone', 'phone-outline', 'more', 'more-outline', 'star-on', 'star-off', 's-goods', 'goods', 'warning', 'warning-outline', 'question', 'info', 'remove', 'circle-plus', 'success', 'error', 'zoom-in', 'zoom-out', 'remove-outline', 'circle-plus-outline', 'circle-check', 'circle-close', 's-help', 'help', 'minus', 'plus', 'check', 'close', 'picture', 'picture-outline', 'picture-outline-round', 'upload', 'upload2', 'download', 'camera-solid', 'camera', 'video-camera-solid', 'video-camera', 'message-solid', 'bell', 's-cooperation', 's-order', 's-platform', 's-fold', 's-unfold', 's-operation', 's-promotion', 's-home', 's-release', 's-ticket', 's-management', 's-open', 's-shop', 's-marketing', 's-flag', 's-comment', 's-finance', 's-claim', 's-custom', 's-opportunity', 's-data', 's-check', 's-grid', 'menu', 'share', 'd-caret', 'caret-left', 'caret-right', 'caret-bottom', 'caret-top', 'bottom-left', 'bottom-right', 'back', 'right', 'bottom', 'top', 'top-left', 'top-right', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'd-arrow-left', 'd-arrow-right', 'video-pause', 'video-play', 'refresh', 'refresh-right', 'refresh-left', 'finished', 'sort', 'sort-up', 'sort-down', 'rank', 'loading', 'view', 'c-scale-to-original', 'date', 'edit', 'edit-outline', 'folder', 'folder-opened', 'folder-add', 'folder-remove', 'folder-delete', 'folder-checked', 'tickets', 'document-remove', 'document-delete', 'document-copy', 'document-checked', 'document', 'document-add', 'printer', 'paperclip', 'takeaway-box', 'search', 'monitor', 'attract', 'mobile', 'scissors', 'umbrella', 'headset', 'brush', 'mouse', 'coordinate', 'magic-stick', 'reading', 'data-line', 'data-board', 'pie-chart', 'data-analysis', 'collection-tag', 'film', 'suitcase', 'suitcase-1', 'receiving', 'collection', 'files', 'notebook-1', 'notebook-2', 'toilet-paper', 'office-building', 'school', 'table-lamp', 'house', 'no-smoking', 'smoking', 'shopping-cart-full', 'shopping-cart-1', 'shopping-cart-2', 'shopping-bag-1', 'shopping-bag-2', 'sold-out', 'sell', 'present', 'box', 'bank-card', 'money', 'coin', 'wallet', 'discount', 'price-tag', 'news', 'guide', 'male', 'female', 'thumb', 'cpu', 'link', 'connection', 'open', 'turn-off', 'set-up', 'chat-round', 'chat-line-round', 'chat-square', 'chat-dot-round', 'chat-dot-square', 'chat-line-square', 'message', 'postcard', 'position', 'turn-off-microphone', 'microphone', 'close-notification', 'bangzhu', 'time', 'odometer', 'crop', 'aim', 'switch-button', 'full-screen', 'copy-document', 'mic', 'stopwatch', 'medal-1', 'medal', 'trophy', 'trophy-1', 'first-aid-kit', 'discover', 'place', 'location', 'location-outline', 'location-information', 'add-location', 'delete-location', 'map-location', 'alarm-clock', 'timer', 'watch-1', 'watch', 'lock', 'unlock', 'key', 'service', 'mobile-phone', 'bicycle', 'truck', 'ship', 'basketball', 'football', 'soccer', 'baseball', 'wind-power', 'light-rain', 'lightning', 'heavy-rain', 'sunrise', 'sunrise-1', 'sunset', 'sunny', 'cloudy', 'partly-cloudy', 'cloudy-and-sunny', 'moon', 'moon-night', 'dish', 'dish-1', 'food', 'chicken', 'fork-spoon', 'knife-fork', 'burger', 'tableware', 'sugar', 'dessert', 'ice-cream', 'hot-water', 'water-cup', 'coffee-cup', 'cold-drink', 'goblet', 'goblet-full', 'goblet-square', 'goblet-square-full', 'refrigerator', 'grape', 'watermelon', 'cherry', 'apple', 'pear', 'orange', 'coffee', 'ice-tea', 'ice-drink', 'milk-tea', 'potato-strips', 'lollipop', 'ice-cream-square', 'ice-cream-round'] + +export default elementIcons diff --git a/ruoyi-ui/src/views/components/icons/index.vue b/ruoyi-ui/src/views/components/icons/index.vue new file mode 100644 index 0000000..d3c9a71 --- /dev/null +++ b/ruoyi-ui/src/views/components/icons/index.vue @@ -0,0 +1,87 @@ +<template> + <div class="icons-container"> + <aside> + <a href="#" target="_blank">Add and use + </a> + </aside> + <el-tabs type="border-card"> + <el-tab-pane label="Icons"> + <div v-for="item of svgIcons" :key="item"> + <el-tooltip placement="top"> + <div slot="content"> + {{ generateIconCode(item) }} + </div> + <div class="icon-item"> + <svg-icon :icon-class="item" class-name="disabled" /> + <span>{{ item }}</span> + </div> + </el-tooltip> + </div> + </el-tab-pane> + <el-tab-pane label="Element-UI Icons"> + <div v-for="item of elementIcons" :key="item"> + <el-tooltip placement="top"> + <div slot="content"> + {{ generateElementIconCode(item) }} + </div> + <div class="icon-item"> + <i :class="'el-icon-' + item" /> + <span>{{ item }}</span> + </div> + </el-tooltip> + </div> + </el-tab-pane> + </el-tabs> + </div> +</template> + +<script> +import svgIcons from './svg-icons' +import elementIcons from './element-icons' + +export default { + name: 'Icons', + data() { + return { + svgIcons, + elementIcons + } + }, + methods: { + generateIconCode(symbol) { + return `<svg-icon icon-class="${symbol}" />` + }, + generateElementIconCode(symbol) { + return `<i class="el-icon-${symbol}" />` + } + } +} +</script> + +<style lang="scss" scoped> +.icons-container { + margin: 10px 20px 0; + overflow: hidden; + + .icon-item { + margin: 20px; + height: 85px; + text-align: center; + width: 100px; + float: left; + font-size: 30px; + color: #24292e; + cursor: pointer; + } + + span { + display: block; + font-size: 16px; + margin-top: 10px; + } + + .disabled { + pointer-events: none; + } +} +</style> diff --git a/ruoyi-ui/src/views/components/icons/svg-icons.js b/ruoyi-ui/src/views/components/icons/svg-icons.js new file mode 100644 index 0000000..724cd8e --- /dev/null +++ b/ruoyi-ui/src/views/components/icons/svg-icons.js @@ -0,0 +1,10 @@ +const req = require.context('../../../assets/icons/svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys() + +const re = /\.\/(.*)\.svg/ + +const svgIcons = requireAll(req).map(i => { + return i.match(re)[1] +}) + +export default svgIcons diff --git a/ruoyi-ui/src/views/dashboard/BarChart.vue b/ruoyi-ui/src/views/dashboard/BarChart.vue new file mode 100644 index 0000000..cd33d2d --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/BarChart.vue @@ -0,0 +1,102 @@ +<template> + <div :class="className" :style="{height:height,width:width}" /> +</template> + +<script> +import * as echarts from 'echarts'; +require('echarts/theme/macarons') // echarts theme +import resize from './mixins/resize' + +const animationDuration = 6000 + +export default { + mixins: [resize], + props: { + className: { + type: String, + default: 'chart' + }, + width: { + type: String, + default: '100%' + }, + height: { + type: String, + default: '300px' + } + }, + data() { + return { + chart: null + } + }, + mounted() { + this.$nextTick(() => { + this.initChart() + }) + }, + beforeDestroy() { + if (!this.chart) { + return + } + this.chart.dispose() + this.chart = null + }, + methods: { + initChart() { + this.chart = echarts.init(this.$el, 'macarons') + + this.chart.setOption({ + tooltip: { + trigger: 'axis', + axisPointer: { // 坐标轴指示器,坐标轴触发有效 + type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' + } + }, + grid: { + top: 10, + left: '2%', + right: '2%', + bottom: '3%', + containLabel: true + }, + xAxis: [{ + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + axisTick: { + alignWithLabel: true + } + }], + yAxis: [{ + type: 'value', + axisTick: { + show: false + } + }], + series: [{ + name: 'pageA', + type: 'bar', + stack: 'vistors', + barWidth: '60%', + data: [79, 52, 200, 334, 390, 330, 220], + animationDuration + }, { + name: 'pageB', + type: 'bar', + stack: 'vistors', + barWidth: '60%', + data: [80, 52, 200, 334, 390, 330, 220], + animationDuration + }, { + name: 'pageC', + type: 'bar', + stack: 'vistors', + barWidth: '60%', + data: [30, 52, 200, 334, 390, 330, 220], + animationDuration + }] + }) + } + } +} +</script> diff --git a/ruoyi-ui/src/views/dashboard/LineChart.vue b/ruoyi-ui/src/views/dashboard/LineChart.vue new file mode 100644 index 0000000..ddd1063 --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/LineChart.vue @@ -0,0 +1,135 @@ +<template> + <div :class="className" :style="{height:height,width:width}" /> +</template> + +<script> +import * as echarts from 'echarts'; +require('echarts/theme/macarons') // echarts theme +import resize from './mixins/resize' + +export default { + mixins: [resize], + props: { + className: { + type: String, + default: 'chart' + }, + width: { + type: String, + default: '100%' + }, + height: { + type: String, + default: '350px' + }, + autoResize: { + type: Boolean, + default: true + }, + chartData: { + type: Object, + required: true + } + }, + data() { + return { + chart: null + } + }, + watch: { + chartData: { + deep: true, + handler(val) { + this.setOptions(val) + } + } + }, + mounted() { + this.$nextTick(() => { + this.initChart() + }) + }, + beforeDestroy() { + if (!this.chart) { + return + } + this.chart.dispose() + this.chart = null + }, + methods: { + initChart() { + this.chart = echarts.init(this.$el, 'macarons') + this.setOptions(this.chartData) + }, + setOptions({ expectedData, actualData } = {}) { + this.chart.setOption({ + xAxis: { + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + boundaryGap: false, + axisTick: { + show: false + } + }, + grid: { + left: 10, + right: 10, + bottom: 20, + top: 30, + containLabel: true + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + padding: [5, 10] + }, + yAxis: { + axisTick: { + show: false + } + }, + legend: { + data: ['expected', 'actual'] + }, + series: [{ + name: 'expected', itemStyle: { + normal: { + color: '#FF005A', + lineStyle: { + color: '#FF005A', + width: 2 + } + } + }, + smooth: true, + type: 'line', + data: expectedData, + animationDuration: 2800, + animationEasing: 'cubicInOut' + }, + { + name: 'actual', + smooth: true, + type: 'line', + itemStyle: { + normal: { + color: '#3888fa', + lineStyle: { + color: '#3888fa', + width: 2 + }, + areaStyle: { + color: '#f3f8ff' + } + } + }, + data: actualData, + animationDuration: 2800, + animationEasing: 'quadraticOut' + }] + }) + } + } +} +</script> diff --git a/ruoyi-ui/src/views/dashboard/PanelGroup.vue b/ruoyi-ui/src/views/dashboard/PanelGroup.vue new file mode 100644 index 0000000..1a1081f --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/PanelGroup.vue @@ -0,0 +1,181 @@ +<template> + <el-row :gutter="40" class="panel-group"> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('newVisitis')"> + <div class="card-panel-icon-wrapper icon-people"> + <svg-icon icon-class="peoples" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + 访客 + </div> + <count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" /> + </div> + </div> + </el-col> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('messages')"> + <div class="card-panel-icon-wrapper icon-message"> + <svg-icon icon-class="message" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + 消息 + </div> + <count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" /> + </div> + </div> + </el-col> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('purchases')"> + <div class="card-panel-icon-wrapper icon-money"> + <svg-icon icon-class="money" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + 金额 + </div> + <count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" /> + </div> + </div> + </el-col> + <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col"> + <div class="card-panel" @click="handleSetLineChartData('shoppings')"> + <div class="card-panel-icon-wrapper icon-shopping"> + <svg-icon icon-class="shopping" class-name="card-panel-icon" /> + </div> + <div class="card-panel-description"> + <div class="card-panel-text"> + 订单 + </div> + <count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" /> + </div> + </div> + </el-col> + </el-row> +</template> + +<script> +import CountTo from 'vue-count-to' + +export default { + components: { + CountTo + }, + methods: { + handleSetLineChartData(type) { + this.$emit('handleSetLineChartData', type) + } + } +} +</script> + +<style lang="scss" scoped> +.panel-group { + margin-top: 18px; + + .card-panel-col { + margin-bottom: 32px; + } + + .card-panel { + height: 108px; + cursor: pointer; + font-size: 12px; + position: relative; + overflow: hidden; + color: #666; + background: #fff; + box-shadow: 4px 4px 40px rgba(0, 0, 0, .05); + border-color: rgba(0, 0, 0, .05); + + &:hover { + .card-panel-icon-wrapper { + color: #fff; + } + + .icon-people { + background: #40c9c6; + } + + .icon-message { + background: #36a3f7; + } + + .icon-money { + background: #f4516c; + } + + .icon-shopping { + background: #34bfa3 + } + } + + .icon-people { + color: #40c9c6; + } + + .icon-message { + color: #36a3f7; + } + + .icon-money { + color: #f4516c; + } + + .icon-shopping { + color: #34bfa3 + } + + .card-panel-icon-wrapper { + float: left; + margin: 14px 0 0 14px; + padding: 16px; + transition: all 0.38s ease-out; + border-radius: 6px; + } + + .card-panel-icon { + float: left; + font-size: 48px; + } + + .card-panel-description { + float: right; + font-weight: bold; + margin: 26px; + margin-left: 0px; + + .card-panel-text { + line-height: 18px; + color: rgba(0, 0, 0, 0.45); + font-size: 16px; + margin-bottom: 12px; + } + + .card-panel-num { + font-size: 20px; + } + } + } +} + +@media (max-width:550px) { + .card-panel-description { + display: none; + } + + .card-panel-icon-wrapper { + float: none !important; + width: 100%; + height: 100%; + margin: 0 !important; + + .svg-icon { + display: block; + margin: 14px auto !important; + float: none !important; + } + } +} +</style> diff --git a/ruoyi-ui/src/views/dashboard/PieChart.vue b/ruoyi-ui/src/views/dashboard/PieChart.vue new file mode 100644 index 0000000..c360057 --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/PieChart.vue @@ -0,0 +1,79 @@ +<template> + <div :class="className" :style="{height:height,width:width}" /> +</template> + +<script> +import * as echarts from 'echarts'; +require('echarts/theme/macarons') // echarts theme +import resize from './mixins/resize' + +export default { + mixins: [resize], + props: { + className: { + type: String, + default: 'chart' + }, + width: { + type: String, + default: '100%' + }, + height: { + type: String, + default: '300px' + } + }, + data() { + return { + chart: null + } + }, + mounted() { + this.$nextTick(() => { + this.initChart() + }) + }, + beforeDestroy() { + if (!this.chart) { + return + } + this.chart.dispose() + this.chart = null + }, + methods: { + initChart() { + this.chart = echarts.init(this.$el, 'macarons') + + this.chart.setOption({ + tooltip: { + trigger: 'item', + formatter: '{a} <br/>{b} : {c} ({d}%)' + }, + legend: { + left: 'center', + bottom: '10', + data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts'] + }, + series: [ + { + name: 'WEEKLY WRITE ARTICLES', + type: 'pie', + roseType: 'radius', + radius: [15, 95], + center: ['50%', '38%'], + data: [ + { value: 320, name: 'Industries' }, + { value: 240, name: 'Technology' }, + { value: 149, name: 'Forex' }, + { value: 100, name: 'Gold' }, + { value: 59, name: 'Forecasts' } + ], + animationEasing: 'cubicInOut', + animationDuration: 2600 + } + ] + }) + } + } +} +</script> diff --git a/ruoyi-ui/src/views/dashboard/RaddarChart.vue b/ruoyi-ui/src/views/dashboard/RaddarChart.vue new file mode 100644 index 0000000..b1790ca --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/RaddarChart.vue @@ -0,0 +1,116 @@ +<template> + <div :class="className" :style="{height:height,width:width}" /> +</template> + +<script> +import * as echarts from 'echarts'; +require('echarts/theme/macarons') // echarts theme +import resize from './mixins/resize' + +const animationDuration = 3000 + +export default { + mixins: [resize], + props: { + className: { + type: String, + default: 'chart' + }, + width: { + type: String, + default: '100%' + }, + height: { + type: String, + default: '300px' + } + }, + data() { + return { + chart: null + } + }, + mounted() { + this.$nextTick(() => { + this.initChart() + }) + }, + beforeDestroy() { + if (!this.chart) { + return + } + this.chart.dispose() + this.chart = null + }, + methods: { + initChart() { + this.chart = echarts.init(this.$el, 'macarons') + + this.chart.setOption({ + tooltip: { + trigger: 'axis', + axisPointer: { // 坐标轴指示器,坐标轴触发有效 + type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' + } + }, + radar: { + radius: '66%', + center: ['50%', '42%'], + splitNumber: 8, + splitArea: { + areaStyle: { + color: 'rgba(127,95,132,.3)', + opacity: 1, + shadowBlur: 45, + shadowColor: 'rgba(0,0,0,.5)', + shadowOffsetX: 0, + shadowOffsetY: 15 + } + }, + indicator: [ + { name: 'Sales', max: 10000 }, + { name: 'Administration', max: 20000 }, + { name: 'Information Techology', max: 20000 }, + { name: 'Customer Support', max: 20000 }, + { name: 'Development', max: 20000 }, + { name: 'Marketing', max: 20000 } + ] + }, + legend: { + left: 'center', + bottom: '10', + data: ['Allocated Budget', 'Expected Spending', 'Actual Spending'] + }, + series: [{ + type: 'radar', + symbolSize: 0, + areaStyle: { + normal: { + shadowBlur: 13, + shadowColor: 'rgba(0,0,0,.2)', + shadowOffsetX: 0, + shadowOffsetY: 10, + opacity: 1 + } + }, + data: [ + { + value: [5000, 7000, 12000, 11000, 15000, 14000], + name: 'Allocated Budget' + }, + { + value: [4000, 9000, 15000, 15000, 13000, 11000], + name: 'Expected Spending' + }, + { + value: [5500, 11000, 12000, 15000, 12000, 12000], + name: 'Actual Spending' + } + ], + animationDuration: animationDuration + }] + }) + } + } +} +</script> diff --git a/ruoyi-ui/src/views/dashboard/mixins/resize.js b/ruoyi-ui/src/views/dashboard/mixins/resize.js new file mode 100644 index 0000000..b1e76e9 --- /dev/null +++ b/ruoyi-ui/src/views/dashboard/mixins/resize.js @@ -0,0 +1,56 @@ +import { debounce } from '@/utils' + +export default { + data() { + return { + $_sidebarElm: null, + $_resizeHandler: null + } + }, + mounted() { + this.initListener() + }, + activated() { + if (!this.$_resizeHandler) { + // avoid duplication init + this.initListener() + } + + // when keep-alive chart activated, auto resize + this.resize() + }, + beforeDestroy() { + this.destroyListener() + }, + deactivated() { + this.destroyListener() + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_sidebarResizeHandler(e) { + if (e.propertyName === 'width') { + this.$_resizeHandler() + } + }, + initListener() { + this.$_resizeHandler = debounce(() => { + this.resize() + }, 100) + window.addEventListener('resize', this.$_resizeHandler) + + this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] + this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler) + }, + destroyListener() { + window.removeEventListener('resize', this.$_resizeHandler) + this.$_resizeHandler = null + + this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler) + }, + resize() { + const { chart } = this + chart && chart.resize() + } + } +} diff --git a/ruoyi-ui/src/views/error/401.vue b/ruoyi-ui/src/views/error/401.vue new file mode 100644 index 0000000..448b6ec --- /dev/null +++ b/ruoyi-ui/src/views/error/401.vue @@ -0,0 +1,88 @@ +<template> + <div class="errPage-container"> + <el-button icon="arrow-left" class="pan-back-btn" @click="back"> + 返回 + </el-button> + <el-row> + <el-col :span="12"> + <h1 class="text-jumbo text-ginormous"> + 401错误! + </h1> + <h2>您没有访问权限!</h2> + <h6>对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面</h6> + <ul class="list-unstyled"> + <li class="link-type"> + <router-link to="/"> + 回首页 + </router-link> + </li> + </ul> + </el-col> + <el-col :span="12"> + <img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream."> + </el-col> + </el-row> + </div> +</template> + +<script> +import errGif from '@/assets/401_images/401.gif' + +export default { + name: 'Page401', + data() { + return { + errGif: errGif + '?' + +new Date() + } + }, + methods: { + back() { + if (this.$route.query.noGoBack) { + this.$router.push({ path: '/' }) + } else { + this.$router.go(-1) + } + } + } +} +</script> + +<style lang="scss" scoped> + .errPage-container { + width: 800px; + max-width: 100%; + margin: 100px auto; + .pan-back-btn { + background: #008489; + color: #fff; + border: none!important; + } + .pan-gif { + margin: 0 auto; + display: block; + } + .pan-img { + display: block; + margin: 0 auto; + width: 100%; + } + .text-jumbo { + font-size: 60px; + font-weight: 700; + color: #484848; + } + .list-unstyled { + font-size: 14px; + li { + padding-bottom: 5px; + } + a { + color: #008489; + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + } +</style> diff --git a/ruoyi-ui/src/views/error/404.vue b/ruoyi-ui/src/views/error/404.vue new file mode 100644 index 0000000..96f075c --- /dev/null +++ b/ruoyi-ui/src/views/error/404.vue @@ -0,0 +1,233 @@ +<template> + <div class="wscn-http404-container"> + <div class="wscn-http404"> + <div class="pic-404"> + <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404"> + <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404"> + <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404"> + <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404"> + </div> + <div class="bullshit"> + <div class="bullshit__oops"> + 404错误! + </div> + <div class="bullshit__headline"> + {{ message }} + </div> + <div class="bullshit__info"> + 对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。 + </div> + <router-link to="/" class="bullshit__return-home"> + 返回首页 + </router-link> + </div> + </div> + </div> +</template> + +<script> + +export default { + name: 'Page404', + computed: { + message() { + return '找不到网页!' + } + } +} +</script> + +<style lang="scss" scoped> +.wscn-http404-container{ + transform: translate(-50%,-50%); + position: absolute; + top: 40%; + left: 50%; +} +.wscn-http404 { + position: relative; + width: 1200px; + padding: 0 50px; + overflow: hidden; + .pic-404 { + position: relative; + float: left; + width: 600px; + overflow: hidden; + &__parent { + width: 100%; + } + &__child { + position: absolute; + &.left { + width: 80px; + top: 17px; + left: 220px; + opacity: 0; + animation-name: cloudLeft; + animation-duration: 2s; + animation-timing-function: linear; + animation-fill-mode: forwards; + animation-delay: 1s; + } + &.mid { + width: 46px; + top: 10px; + left: 420px; + opacity: 0; + animation-name: cloudMid; + animation-duration: 2s; + animation-timing-function: linear; + animation-fill-mode: forwards; + animation-delay: 1.2s; + } + &.right { + width: 62px; + top: 100px; + left: 500px; + opacity: 0; + animation-name: cloudRight; + animation-duration: 2s; + animation-timing-function: linear; + animation-fill-mode: forwards; + animation-delay: 1s; + } + @keyframes cloudLeft { + 0% { + top: 17px; + left: 220px; + opacity: 0; + } + 20% { + top: 33px; + left: 188px; + opacity: 1; + } + 80% { + top: 81px; + left: 92px; + opacity: 1; + } + 100% { + top: 97px; + left: 60px; + opacity: 0; + } + } + @keyframes cloudMid { + 0% { + top: 10px; + left: 420px; + opacity: 0; + } + 20% { + top: 40px; + left: 360px; + opacity: 1; + } + 70% { + top: 130px; + left: 180px; + opacity: 1; + } + 100% { + top: 160px; + left: 120px; + opacity: 0; + } + } + @keyframes cloudRight { + 0% { + top: 100px; + left: 500px; + opacity: 0; + } + 20% { + top: 120px; + left: 460px; + opacity: 1; + } + 80% { + top: 180px; + left: 340px; + opacity: 1; + } + 100% { + top: 200px; + left: 300px; + opacity: 0; + } + } + } + } + .bullshit { + position: relative; + float: left; + width: 300px; + padding: 30px 0; + overflow: hidden; + &__oops { + font-size: 32px; + font-weight: bold; + line-height: 40px; + color: #1482f0; + opacity: 0; + margin-bottom: 20px; + animation-name: slideUp; + animation-duration: 0.5s; + animation-fill-mode: forwards; + } + &__headline { + font-size: 20px; + line-height: 24px; + color: #222; + font-weight: bold; + opacity: 0; + margin-bottom: 10px; + animation-name: slideUp; + animation-duration: 0.5s; + animation-delay: 0.1s; + animation-fill-mode: forwards; + } + &__info { + font-size: 13px; + line-height: 21px; + color: grey; + opacity: 0; + margin-bottom: 30px; + animation-name: slideUp; + animation-duration: 0.5s; + animation-delay: 0.2s; + animation-fill-mode: forwards; + } + &__return-home { + display: block; + float: left; + width: 110px; + height: 36px; + background: #1482f0; + border-radius: 100px; + text-align: center; + color: #ffffff; + opacity: 0; + font-size: 14px; + line-height: 36px; + cursor: pointer; + animation-name: slideUp; + animation-duration: 0.5s; + animation-delay: 0.3s; + animation-fill-mode: forwards; + } + @keyframes slideUp { + 0% { + transform: translateY(60px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } + } + } +} +</style> diff --git a/ruoyi-ui/src/views/index.vue b/ruoyi-ui/src/views/index.vue new file mode 100644 index 0000000..663eb1d --- /dev/null +++ b/ruoyi-ui/src/views/index.vue @@ -0,0 +1,1023 @@ +<template> + <div class="app-container home"> + <el-row :gutter="20"> + <el-col :sm="24" :lg="24"> + <blockquote class="text-warning" style="font-size: 14px"> + 领取阿里云通用云产品1888优惠券 + <br /> + <el-link + href="https://www.aliyun.com/minisite/goods?userCode=brki8iof" + type="primary" + target="_blank" + >https://www.aliyun.com/minisite/goods?userCode=brki8iof</el-link + > + <br /> + 领取腾讯云通用云产品2860优惠券 + <br /> + <el-link + href="https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console" + type="primary" + target="_blank" + >https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console</el-link + > + <br /> + 阿里云服务器折扣区 + <el-link href="http://aly.ruoyi.vip" type="primary" target="_blank" + >>☛☛点我进入☚☚</el-link + > + 腾讯云服务器秒杀区 + <el-link href="http://txy.ruoyi.vip" type="primary" target="_blank" + >>☛☛点我进入☚☚</el-link + ><br /> + <h4 class="text-danger"> + 云产品通用红包,可叠加官网常规优惠使用。(仅限新用户) + </h4> + </blockquote> + + <hr /> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :sm="24" :lg="12" style="padding-left: 20px"> + <h2>若依后台管理框架</h2> + <p> + 一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了若依管理系统,她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 + </p> + <p> + <b>当前版本:</b> <span>v{{ version }}</span> + </p> + <p> + <el-tag type="danger">¥免费开源</el-tag> + </p> + <p> + <el-button + type="primary" + size="mini" + icon="el-icon-cloudy" + plain + @click="goTarget('https://gitee.com/y_project/RuoYi-Vue')" + >访问码云</el-button + > + <el-button + size="mini" + icon="el-icon-s-home" + plain + @click="goTarget('http://ruoyi.vip')" + >访问主页</el-button + > + </p> + </el-col> + + <el-col :sm="24" :lg="12" style="padding-left: 50px"> + <el-row> + <el-col :span="12"> + <h2>技术选型</h2> + </el-col> + </el-row> + <el-row> + <el-col :span="6"> + <h4>后端技术</h4> + <ul> + <li>SpringBoot</li> + <li>Spring Security</li> + <li>JWT</li> + <li>MyBatis</li> + <li>Druid</li> + <li>Fastjson</li> + <li>...</li> + </ul> + </el-col> + <el-col :span="6"> + <h4>前端技术</h4> + <ul> + <li>Vue</li> + <li>Vuex</li> + <li>Element-ui</li> + <li>Axios</li> + <li>Sass</li> + <li>Quill</li> + <li>...</li> + </ul> + </el-col> + </el-row> + </el-col> + </el-row> + <el-divider /> + <el-row :gutter="20"> + <el-col :xs="24" :sm="24" :md="12" :lg="8"> + <el-card class="update-log"> + <div slot="header" class="clearfix"> + <span>联系信息</span> + </div> + <div class="body"> + <p> + <i class="el-icon-s-promotion"></i> 官网:<el-link + href="http://www.ruoyi.vip" + target="_blank" + >http://www.ruoyi.vip</el-link + > + </p> + <p> + <i class="el-icon-user-solid"></i> QQ群:<s> 满937441 </s> <s> 满887144332 </s> + <s> 满180251782 </s> <s> 满104180207 </s> <s> 满186866453 </s> <s> 满201396349 </s> + <s> 满101456076 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s> + <s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s> + <s> 满101046199 </s> <s> 满136919097 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921" target="_blank">143961921</a> + </p> + <p> + <i class="el-icon-chat-dot-round"></i> 微信:<a + href="javascript:;" + >/ *若依</a + > + </p> + <p> + <i class="el-icon-money"></i> 支付宝:<a + href="javascript:;" + class="支付宝信息" + >/ *若依</a + > + </p> + </div> + </el-card> + </el-col> + <el-col :xs="24" :sm="24" :md="12" :lg="8"> + <el-card class="update-log"> + <div slot="header" class="clearfix"> + <span>更新日志</span> + </div> + <el-collapse accordion> + <el-collapse-item title="v3.8.6 - 2023-06-30"> + <ol> + <li>支持登录IP黑名单限制</li> + <li>新增监控页面图标显示</li> + <li>操作日志新增消耗时间属性</li> + <li>屏蔽定时任务bean违规的字符</li> + <li>日志管理使用索引提升查询性能</li> + <li>日志注解支持排除指定的请求参数</li> + <li>支持自定义隐藏属性列过滤子对象</li> + <li>升级oshi到最新版本6.4.3</li> + <li>升级druid到最新版本1.2.16</li> + <li>升级fastjson到最新版2.0.34</li> + <li>升级spring-boot到最新版本2.5.15</li> + <li>升级element-ui到最新版本2.15.13</li> + <li>移除apache/commons-fileupload依赖</li> + <li>修复页面切换时布局错乱的问题</li> + <li>修复匿名注解Anonymous空指针问题</li> + <li>修复路由跳转被阻止时内部产生报错信息问题</li> + <li>修复isMatchedIp的参数判断产生空指针的问题</li> + <li>修复用户多角色数据权限可能出现权限抬升的情况</li> + <li>修复开启TopNav后一级菜单路由参数设置无效问题</li> + <li>修复DictTag组件value没有匹配的值时则展示value</li> + <li>优化文件下载出现的异常</li> + <li>优化选择图标组件高亮回显</li> + <li>优化弹窗后导航栏偏移的问题</li> + <li>优化修改密码日志存储明文问题</li> + <li>优化页签栏关闭其他出现的异常问题</li> + <li>优化页签关闭左侧选项排除首页选项</li> + <li>优化关闭当前tab页跳转最右侧tab页</li> + <li>优化缓存列表清除操作提示不变的问题</li> + <li>优化字符未使用下划线不进行驼峰式处理</li> + <li>优化用户导入更新时需获取用户编号问题</li> + <li>优化侧边栏的平台标题与VUE_APP_TITLE保持同步</li> + <li>优化导出Excel时设置dictType属性重复查缓存问题</li> + <li>连接池Druid支持新的配置connectTimeout和socketTimeout</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.8.5 - 2023-01-01"> + <ol> + <li>定时任务违规的字符</li> + <li>重置时取消部门选中</li> + <li>新增返回警告消息提示</li> + <li>忽略不必要的属性数据返回</li> + <li>修改参数键名时移除前缓存配置</li> + <li>导入更新用户数据前校验数据权限</li> + <li>兼容Excel下拉框内容过多无法显示的问题</li> + <li>升级echarts到最新版本5.4.0</li> + <li>升级core-js到最新版本3.25.3</li> + <li>升级oshi到最新版本6.4.0</li> + <li>升级kaptcha到最新版2.3.3</li> + <li>升级druid到最新版本1.2.15</li> + <li>升级fastjson到最新版2.0.20</li> + <li>升级pagehelper到最新版1.4.6</li> + <li>优化弹窗内容过多展示不全问题</li> + <li>优化swagger-ui静态资源使用缓存</li> + <li>开启TopNav没有子菜单隐藏侧边栏</li> + <li>删除fuse无效选项maxPatternLength</li> + <li>优化导出对象的子列表为空会出现[]问题</li> + <li>优化编辑头像时透明部分会变成黑色问题</li> + <li>优化小屏幕上修改头像界面布局错位的问题</li> + <li>修复代码生成勾选属性无效问题</li> + <li>修复文件上传组件格式验证问题</li> + <li>修复回显数据字典数组异常问题</li> + <li>修复sheet超出最大行数异常问题</li> + <li>修复Log注解GET请求记录不到参数问题</li> + <li>修复调度日志点击多次数据不变化的问题</li> + <li>修复主题颜色在Drawer组件不会加载问题</li> + <li>修复文件名包含特殊字符的文件无法下载问题</li> + <li>修复table中更多按钮切换主题色未生效修复问题</li> + <li>修复某些特性的环境生成代码变乱码TXT文件问题</li> + <li>修复代码生成图片/文件/单选时选择必填无法校验问题</li> + <li>修复某些特性的情况用户编辑对话框中角色和部门无法修改问题</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.8.4 - 2022-09-26"> + <ol> + <li>数据逻辑删除不进行唯一验证</li> + <li>Excel注解支持导出对象的子列表方法</li> + <li>Excel注解支持自定义隐藏属性列</li> + <li>Excel注解支持backgroundColor属性设置背景色</li> + <li>支持配置密码最大错误次数/锁定时间</li> + <li>登录日志新增解锁账户功能</li> + <li>通用下载方法新增config配置选项</li> + <li>支持多权限字符匹配角色数据权限</li> + <li>页面内嵌iframe切换tab不刷新数据</li> + <li>操作日志记录支持排除敏感属性字段</li> + <li>修复多文件上传报错出现的异常问题</li> + <li>修复图片预览组件src属性为null值控制台报错问题</li> + <li>升级oshi到最新版本6.2.2</li> + <li>升级fastjson到最新版2.0.14</li> + <li>升级pagehelper到最新版1.4.3</li> + <li>升级core-js到最新版本3.25.2</li> + <li>升级element-ui到最新版本2.15.10</li> + <li>优化任务过期不执行调度</li> + <li>优化字典数据使用store存取</li> + <li>优化修改资料头像被覆盖的问题</li> + <li>优化修改用户登录账号重复验证</li> + <li>优化代码生成同步后值NULL问题</li> + <li>优化定时任务支持执行父类方法</li> + <li>优化用户个人信息接口防止修改部门</li> + <li>优化布局设置使用el-drawer抽屉显示</li> + <li>优化没有权限的用户编辑部门缺少数据</li> + <li>优化日志注解记录限制请求地址的长度</li> + <li>优化excel/scale属性导出单元格数值类型</li> + <li>优化日志操作中重置按钮时重复查询的问题</li> + <li>优化多个相同角色数据导致权限SQL重复问题</li> + <li>优化表格上右侧工具条(搜索按钮显隐&右侧样式凸出)</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.8.3 - 2022-06-27"> + <ol> + <li>新增缓存列表菜单功能</li> + <li>代码生成树表新增(展开/折叠)</li> + <li>Excel注解支持color字体颜色</li> + <li>新增Anonymous匿名访问不鉴权注解</li> + <li>用户头像上传限制只能为图片格式</li> + <li>接口使用泛型使其看到响应属性字段</li> + <li>检查定时任务bean所在包名是否为白名单配置</li> + <li>添加页签openPage支持传递参数</li> + <li>用户缓存信息添加部门ancestors祖级列表</li> + <li>升级element-ui到最新版本2.15.8</li> + <li>升级oshi到最新版本6.1.6</li> + <li>升级druid到最新版本1.2.11</li> + <li>升级fastjson到最新版2.0.8</li> + <li>升级spring-boot到最新版本2.5.14</li> + <li>降级jsencrypt版本兼容IE浏览器</li> + <li>删除多余的salt字段</li> + <li>新增获取不带后缀文件名称方法</li> + <li>新增获取配置文件中的属性值方法</li> + <li>新增内容编码/解码方便插件集成使用</li> + <li>字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)</li> + <li>优化设置分页参数默认值</li> + <li>优化对空字符串参数处理的过滤</li> + <li>优化显示顺序orderNum类型为整型</li> + <li>优化表单构建按钮不显示正则校验</li> + <li>优化字典数据回显样式下拉框显示值</li> + <li>优化R响应成功状态码与全局保持一致</li> + <li>优化druid开启wall过滤器出现的异常问题</li> + <li>优化用户管理左侧树型组件增加选中高亮保持</li> + <li>优化新增用户与角色信息&用户与岗位信息逻辑</li> + <li>优化默认不启用压缩文件缓存防止node_modules过大</li> + <li>修复字典数据显示不全问题</li> + <li>修复操作日志查询类型条件为0时会查到所有数据</li> + <li>修复Excel注解prompt/combo同时使用不生效问题</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.8.2 - 2022-04-01"> + <ol> + <li>前端支持设置是否需要防止数据重复提交</li> + <li>开启TopNav没有子菜单情况隐藏侧边栏</li> + <li>侧边栏菜单名称过长悬停显示标题</li> + <li>用户访问控制时校验数据权限,防止越权</li> + <li>导出Excel时屏蔽公式,防止CSV注入风险</li> + <li>组件ImagePreview支持多图预览显示</li> + <li>组件ImageUpload支持多图同时选择上传</li> + <li>组件FileUpload支持多文件同时选择上传</li> + <li>服务监控新增运行参数信息显示</li> + <li>定时任务目标字符串过滤特殊字符</li> + <li>定时任务目标字符串验证包名白名单</li> + <li>代码生成列表图片支持预览</li> + <li>代码生成编辑修改打开新页签</li> + <li>代码生成新增Java类型Boolean</li> + <li>代码生成子表支持日期/字典配置</li> + <li>代码生成同步保留必填/类型选项</li> + <li>升级oshi到最新版本6.1.2</li> + <li>升级fastjson到最新版1.2.80</li> + <li>升级pagehelper到最新版1.4.1</li> + <li>升级spring-boot到最新版本2.5.11</li> + <li>升级spring-boot-mybatis到最新版2.2.2</li> + <li>添加遗漏的分页参数合理化属性</li> + <li>修改npm即将过期的注册源地址</li> + <li>修复分页组件请求两次问题</li> + <li>修复通用文件下载接口跨域问题</li> + <li>修复Xss注解字段值为空时的异常问题</li> + <li>修复选项卡点击右键刷新丢失参数问题</li> + <li>修复表单清除元素位置未垂直居中问题</li> + <li>修复服务监控中运行参数显示条件错误</li> + <li>修复导入Excel时字典字段类型为Long转义为空问题</li> + <li>修复登录超时刷新页面跳转登录页面还提示重新登录问题</li> + <li>优化加载字典缓存数据</li> + <li>优化IP地址获取到多个的问题</li> + <li>优化任务队列满时任务拒绝策略</li> + <li>优化文件上传兼容Weblogic环境</li> + <li>优化定时任务默认保存到内存中执行</li> + <li>优化部门修改缩放后出现的错位问题</li> + <li>优化Excel格式化不同类型的日期对象</li> + <li>优化菜单表关键字导致的插件报错问题</li> + <li>优化Oracle用户头像列为空时不显示问题</li> + <li>优化页面若未匹配到字典标签则返回原字典值</li> + <li>优化修复登录失效后多次请求提示多次弹窗问题</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.8.1 - 2022-01-01"> + <ol> + <li>新增Vue3前端代码生成模板</li> + <li>新增图片预览组件</li> + <li>新增压缩插件实现打包Gzip</li> + <li>自定义xss校验注解实现</li> + <li>自定义文字复制剪贴指令</li> + <li>代码生成预览支持复制内容</li> + <li>路由支持单独配置菜单或角色权限</li> + <li>用户管理部门查询选择节点后分页参数初始</li> + <li>修复用户分配角色属性错误</li> + <li>修复打包后字体图标偶现的乱码问题</li> + <li>修复菜单管理重置表单出现的错误</li> + <li>修复版本差异导致的懒加载报错问题</li> + <li>修复Cron组件中周回显问题</li> + <li>修复定时任务多参数逗号分隔的问题</li> + <li>修复根据ID查询列表可能出现的主键溢出问题</li> + <li>修复tomcat配置参数已过期问题</li> + <li>升级clipboard到最新版本2.0.8</li> + <li>升级oshi到最新版本v5.8.6</li> + <li>升级fastjson到最新版1.2.79</li> + <li>升级spring-boot到最新版本2.5.8</li> + <li>升级log4j2到2.17.1,防止漏洞风险</li> + <li>优化下载解析blob异常提示</li> + <li>优化代码生成字典组重复问题</li> + <li>优化查询用户的角色组&岗位组代码</li> + <li>优化定时任务cron表达式小时设置24</li> + <li>优化用户导入提示溢出则显示滚动条</li> + <li>优化防重复提交标识组合为(key+url+header)</li> + <li>优化分页方法设置成通用方便灵活调用</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.8.0 - 2021-12-01"> + <ol> + <li>新增配套并同步的Vue3前端版本</li> + <li>新增通用方法简化模态/缓存/下载/权限/页签使用</li> + <li>优化导出数据/使用通用下载方法</li> + <li>Excel注解支持自定义数据处理器</li> + <li>Excel注解支持导入导出标题信息</li> + <li>Excel导入支持@Excels注解</li> + <li>新增组件data-dict,简化数据字典使用</li> + <li>新增Jaxb依赖,防止jdk8以上出现的兼容错误</li> + <li>生产环境使用路由懒加载提升页面响应速度</li> + <li>修复五级以上菜单出现的404问题</li> + <li>防重提交注解支持配置间隔时间/提示消息</li> + <li>日志注解新增是否保存响应参数</li> + <li>任务屏蔽违规字符&参数忽略双引号中的逗号</li> + <li>升级SpringBoot到最新版本2.5.6</li> + <li>升级pagehelper到最新版1.4.0</li> + <li>升级spring-boot-mybatis到最新版2.2.0</li> + <li>升级oshi到最新版本v5.8.2</li> + <li>升级druid到最新版1.2.8</li> + <li>升级velocity到最新版本2.3</li> + <li>升级fastjson到最新版1.2.78</li> + <li>升级axios到最新版本0.24.0</li> + <li>升级dart-sass到版本1.32.13</li> + <li>升级core-js到最新版本3.19.1</li> + <li>升级jsencrypt到最新版本3.2.1</li> + <li>升级js-cookie到最新版本3.0.1</li> + <li>升级file-saver到最新版本2.0.5</li> + <li>升级sass-loader到最新版本10.1.1</li> + <li>升级element-ui到最新版本2.15.6</li> + <li>新增sendGet无参请求方法</li> + <li>禁用el-tag组件的渐变动画</li> + <li>代码生成点击预览重置激活tab</li> + <li>AjaxResult重写put方法,以方便链式调用</li> + <li>优化登录/验证码请求headers不设置token</li> + <li>优化用户个人信息接口防止修改用户名</li> + <li>优化Cron表达式生成器关闭时销毁避免缓存</li> + <li>优化注册成功提示消息类型success</li> + <li>优化aop语法,使用spring自动注入注解</li> + <li>优化记录登录信息,移除不必要的修改</li> + <li>优化mybatis全局默认的执行器</li> + <li>优化Excel导入图片可能出现的异常</li> + <li>修复代码生成模板主子表删除缺少事务</li> + <li>修复日志记录可能出现的转换异常</li> + <li>修复代码生成复选框字典遗漏问题</li> + <li>修复关闭xss功能导致可重复读RepeatableFilter失效</li> + <li>修复字符串无法被反转义问题</li> + <li>修复后端主子表代码模板方法名生成错误问题</li> + <li>修复xss过滤后格式出现的异常</li> + <li>修复swagger没有指定dataTypeClass导致启动出现warn日志</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.7.0 - 2021-09-13"> + <ol> + <li>参数管理支持配置验证码开关</li> + <li>新增是否开启用户注册功能</li> + <li>定时任务支持在线生成cron表达式</li> + <li>菜单管理支持配置路由参数</li> + <li>支持自定义注解实现接口限流</li> + <li>Excel注解支持Image图片导入</li> + <li>自定义弹层溢出滚动样式</li> + <li>自定义可拖动弹窗宽度指令</li> + <li>自定义可拖动弹窗高度指令</li> + <li>修复任意账户越权问题</li> + <li>修改时检查用户数据权限范围</li> + <li>修复保存配置主题颜色失效问题</li> + <li>新增暗色菜单风格主题</li> + <li>菜单&部门新增展开/折叠功能</li> + <li>页签新增关闭左侧&添加图标</li> + <li>顶部菜单排除隐藏的默认路由</li> + <li>顶部菜单同步系统主题样式</li> + <li>跳转路由高亮相对应的菜单栏</li> + <li>代码生成主子表多选行数据</li> + <li>日期范围支持添加多组</li> + <li>升级element-ui到最新版本2.15.5</li> + <li>升级oshi到最新版本v5.8.0</li> + <li>升级commons.io到最新版本v2.11.0</li> + <li>定时任务屏蔽ldap远程调用</li> + <li>定时任务屏蔽http(s)远程调用</li> + <li>补充定时任务表字段注释</li> + <li>定时任务对检查异常进行事务回滚</li> + <li>启用父部门状态排除顶级节点</li> + <li>富文本新增上传文件大小限制</li> + <li>默认首页使用keep-alive缓存</li> + <li>修改代码生成字典回显样式</li> + <li>自定义分页合理化传入参数</li> + <li>修复字典组件值为整形不显示问题</li> + <li>修复定时任务日志执行状态显示</li> + <li>角色&菜单新增字段属性提示信息</li> + <li>修复角色分配用户页面参数类型错误提醒</li> + <li>优化布局设置动画特效</li> + <li>优化异常处理信息</li> + <li>优化错误token导致的解析异常</li> + <li>密码框新增显示切换密码图标</li> + <li>定时任务新增更多操作</li> + <li>更多操作按钮添加权限控制</li> + <li>导入用户样式优化</li> + <li>提取通用方法到基类控制器</li> + <li>优化使用权限工具获取用户信息</li> + <li>优化用户不能删除自己</li> + <li>优化XSS跨站脚本过滤</li> + <li>优化代码生成模板</li> + <li>验证码默认20s超时</li> + <li>BLOB下载时清除URL对象引用</li> + <li>代码生成导入表按创建时间排序</li> + <li>修复代码生成页面数据编辑保存之后总是跳转第一页的问题</li> + <li>修复带safari浏览器无法格式化utc日期格式yyyy-MM-dd'T'HH:mm:ss.SSS问题</li> + <li>多图上传组件移除多余的api地址&验证失败导致图片删除问题&无法删除相应图片修复</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.6.0 - 2021-07-12"> + <ol> + <li>角色管理新增分配用户功能</li> + <li>用户管理新增分配角色功能</li> + <li>日志列表支持排序操作</li> + <li>优化参数&字典缓存操作</li> + <li>系统布局配置支持动态标题开关</li> + <li>菜单路由配置支持内链访问</li> + <li>默认访问后端首页新增提示语</li> + <li>富文本默认上传返回url类型</li> + <li>新增自定义弹窗拖拽指令</li> + <li>全局注册常用通用组件</li> + <li>全局挂载字典标签组件</li> + <li>ImageUpload组件支持多图片上传</li> + <li>FileUpload组件支持多文件上传</li> + <li>文件上传组件添加数量限制属性</li> + <li>富文本编辑组件添加类型属性</li> + <li>富文本组件工具栏配置视频</li> + <li>封装通用iframe组件</li> + <li>限制超级管理员不允许操作</li> + <li>用户信息长度校验限制</li> + <li>分页组件新增pagerCount属性</li> + <li>添加bat脚本执行应用</li> + <li>升级oshi到最新版本v5.7.4</li> + <li>升级element-ui到最新版本2.15.2</li> + <li>升级pagehelper到最新版1.3.1</li> + <li>升级commons.io到最新版本v2.10.0</li> + <li>升级commons.fileupload到最新版本v1.4</li> + <li>升级swagger到最新版本v3.0.0</li> + <li>修复关闭confirm提示框控制台报错问题</li> + <li>修复存在的SQL注入漏洞问题</li> + <li>定时任务屏蔽rmi远程调用</li> + <li>修复用户搜索分页变量错误</li> + <li>修复导出角色数据范围翻译缺少仅本人</li> + <li>修复表单构建选择下拉选择控制台报错问题</li> + <li>优化图片工具类读取文件</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.5.0 - 2021-05-25"> + <ol> + <li>新增菜单导航显示风格TopNav(false为左侧导航菜单,true为顶部导航菜单)</li> + <li>布局设置支持保存&重置配置</li> + <li>修复树表数据显示不全&加载慢问题</li> + <li>新增IE浏览器版本过低提示页面</li> + <li>用户登录后记录最后登录IP&时间</li> + <li>页面导出按钮点击之后添加遮罩</li> + <li>富文本编辑器支持自定义上传地址</li> + <li>富文本编辑组件新增readOnly属性</li> + <li>页签TagsView新增关闭右侧功能</li> + <li>显隐列组件加载初始默认隐藏列</li> + <li>关闭头像上传窗口还原默认图片</li> + <li>个人信息添加手机&邮箱重复验证</li> + <li>代码生成模板导出按钮点击后添加遮罩</li> + <li>代码生成模板树表操作列添加新增按钮</li> + <li>代码生成模板修复主子表字段重名问题</li> + <li>升级fastjson到最新版1.2.76</li> + <li>升级druid到最新版本v1.2.6</li> + <li>升级mybatis到最新版3.5.6 阻止远程代码执行漏洞</li> + <li>升级oshi到最新版本v5.6.0</li> + <li>velocity剔除commons-collections版本,防止3.2.1版本的反序列化漏洞</li> + <li>数据监控页默认账户密码防止越权访问</li> + <li>修复firefox下表单构建拖拽会新打卡一个选项卡</li> + <li>修正后端导入表权限标识</li> + <li>修正前端操作日志&登录日志权限标识</li> + <li>设置Redis配置HashKey序列化</li> + <li>删除操作日志记录信息</li> + <li>上传媒体类型添加视频格式</li> + <li>修复请求形参未传值记录日志异常问题</li> + <li>优化xss校验json请求条件</li> + <li>树级结构更新子节点使用replaceFirst</li> + <li>优化ExcelUtil空值处理</li> + <li>日志记录过滤BindingResult对象,防止异常</li> + <li>修改主题后mini类型按钮无效问题</li> + <li>优化通用下载完成后删除节点</li> + <li>通用Controller添加响应返回消息</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.4.0 - 2021-02-22"> + <ol> + <li>代码生成模板支持主子表</li> + <li>表格右侧工具栏组件支持显隐列</li> + <li>图片组件添加预览&移除功能</li> + <li>Excel注解支持Image图片导出</li> + <li>操作按钮组调整为朴素按钮样式</li> + <li>代码生成支持文件上传组件</li> + <li>代码生成日期控件区分范围</li> + <li>代码生成数据库文本类型生成表单文本域</li> + <li>用户手机邮箱&菜单组件修改允许空字符串</li> + <li>升级SpringBoot到最新版本2.2.13 提升启动速度</li> + <li>升级druid到最新版本v1.2.4</li> + <li>升级fastjson到最新版1.2.75</li> + <li>升级element-ui到最新版本2.15.0</li> + <li>修复IE11浏览器报错问题</li> + <li>优化多级菜单之间切换无法缓存的问题</li> + <li>修复四级菜单无法显示问题</li> + <li>修正侧边栏静态路由丢失问题</li> + <li>修复角色管理-编辑角色-功能权限显示异常</li> + <li>配置文件新增redis数据库索引属性</li> + <li>权限工具类增加admin判断</li> + <li>角色非自定义权限范围清空选择值</li> + <li>修复导入数据为负浮点数时丢失精度问题</li> + <li>移除path-to-regexp正则匹配插件</li> + <li>修复生成树表代码异常</li> + <li>修改ip字段长度防止ipv6地址长度不够</li> + <li>防止get请求参数值为false或0等特殊值会导致无法正确的传参</li> + <li>登录后push添加catch防止出现检查错误</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.3.0 - 2020-12-14"> + <ol> + <li>新增缓存监控功能</li> + <li>支持主题风格配置</li> + <li>修复多级菜单之间切换无法缓存的问题</li> + <li>多级菜单自动配置组件</li> + <li>代码生成预览支持高亮显示</li> + <li>支持Get请求映射Params参数</li> + <li>删除用户和角色解绑关联</li> + <li>去除用户手机邮箱部门必填验证</li> + <li>Excel支持注解align对齐方式</li> + <li>Excel支持导入Boolean型数据</li> + <li>优化头像样式,鼠标移入悬停遮罩</li> + <li>代码生成预览提供滚动机制</li> + <li>代码生成删除多余的数字float类型</li> + <li>修正转换字符串的目标字符集属性</li> + <li>回显数据字典防止空值报错</li> + <li>日志记录增加过滤多文件场景</li> + <li>修改缓存Set方法可能导致嵌套的问题</li> + <li>移除前端一些多余的依赖</li> + <li>防止安全扫描YUI出现的风险提示</li> + <li>修改node-sass为dart-sass</li> + <li>升级SpringBoot到最新版本2.1.18</li> + <li>升级poi到最新版本4.1.2</li> + <li>升级oshi到最新版本v5.3.6</li> + <li>升级bitwalker到最新版本1.21</li> + <li>升级axios到最新版本0.21.0</li> + <li>升级element-ui到最新版本2.14.1</li> + <li>升级vue到最新版本2.6.12</li> + <li>升级vuex到最新版本3.6.0</li> + <li>升级vue-cli到版本4.5.9</li> + <li>升级vue-router到最新版本3.4.9</li> + <li>升级vue-cli到最新版本4.4.6</li> + <li>升级vue-cropper到最新版本0.5.5</li> + <li>升级clipboard到最新版本2.0.6</li> + <li>升级core-js到最新版本3.8.1</li> + <li>升级echarts到最新版本4.9.0</li> + <li>升级file-saver到最新版本2.0.4</li> + <li>升级fuse.js到最新版本6.4.3</li> + <li>升级js-beautify到最新版本1.13.0</li> + <li>升级js-cookie到最新版本2.2.1</li> + <li>升级path-to-regexp到最新版本6.2.0</li> + <li>升级quill到最新版本1.3.7</li> + <li>升级screenfull到最新版本5.0.2</li> + <li>升级sortablejs到最新版本1.10.2</li> + <li>升级vuedraggable到最新版本2.24.3</li> + <li>升级chalk到最新版本4.1.0</li> + <li>升级eslint到最新版本7.15.0</li> + <li>升级eslint-plugin-vue到最新版本7.2.0</li> + <li>升级lint-staged到最新版本10.5.3</li> + <li>升级runjs到最新版本4.4.2</li> + <li>升级sass-loader到最新版本10.1.0</li> + <li>升级script-ext-html-webpack-plugin到最新版本2.1.5</li> + <li>升级svg-sprite-loader到最新版本5.1.1</li> + <li>升级vue-template-compiler到最新版本2.6.12</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.2.1 - 2020-11-18"> + <ol> + <li>阻止任意文件下载漏洞</li> + <li>代码生成支持上传控件</li> + <li>新增图片上传组件</li> + <li>调整默认首页</li> + <li>升级druid到最新版本v1.2.2</li> + <li>mapperLocations配置支持分隔符</li> + <li>权限信息调整</li> + <li>调整sql默认时间</li> + <li>解决代码生成没有bit类型的问题</li> + <li>升级pagehelper到最新版1.3.0</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v3.2.0 - 2020-10-10"> + <ol> + <li>升级springboot版本到2.1.17 提升安全性</li> + <li>升级oshi到最新版本v5.2.5</li> + <li>升级druid到最新版本v1.2.1</li> + <li>升级jjwt到版本0.9.1</li> + <li>升级fastjson到最新版1.2.74</li> + <li>修改sass为node-sass,避免el-icon图标乱码</li> + <li>代码生成支持同步数据库</li> + <li>代码生成支持富文本控件</li> + <li>代码生成页面时不忽略remark属性</li> + <li>代码生成添加select必填选项</li> + <li>Excel导出类型NUMERIC支持精度浮点类型</li> + <li>Excel导出targetAttr优化获取值,防止get方法不规范</li> + <li>Excel注解支持自动统计数据总和</li> + <li>Excel注解支持设置BigDecimal精度&舍入规则</li> + <li>菜单&数据权限新增(展开/折叠 全选/全不选 父子联动)</li> + <li>允许用户分配到部门父节点</li> + <li>菜单新增是否缓存keep-alive</li> + <li>表格操作列间距调整</li> + <li>限制系统内置参数不允许删除</li> + <li>富文本组件优化,支持自定义高度&图片冲突问题</li> + <li>富文本工具栏样式对齐</li> + <li>导入excel整形值校验优化</li> + <li>修复页签关闭所有时固定标签路由不刷新问题</li> + <li>表单构建布局型组件新增按钮</li> + <li>左侧菜单文字过长显示省略号</li> + <li>修正根节点为子部门时,树状结构显示问题</li> + <li>修正调用目标字符串最大长度</li> + <li>修正菜单提示信息错误</li> + <li>修正定时任务执行一次权限标识</li> + <li>修正数据库字符串类型nvarchar</li> + <li>优化递归子节点</li> + <li>优化数据权限判断</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + + <el-collapse-item title="v3.1.0 - 2020-08-13"> + <ol> + <li>表格工具栏右侧添加刷新&显隐查询组件</li> + <li>后端支持CORS跨域请求</li> + <li>代码生成支持选择上级菜单</li> + <li>代码生成支持自定义路径</li> + <li>代码生成支持复选框</li> + <li>Excel导出导入支持dictType字典类型</li> + <li>Excel支持分割字符串组内容</li> + <li>验证码类型支持(数组计算、字符验证)</li> + <li>升级vue-cli版本到4.4.4</li> + <li>修改 node-sass 为 dart-sass</li> + <li>表单类型为Integer/Long设置整形默认值</li> + <li>代码生成器默认mapper路径与默认mapperScan路径不一致</li> + <li>优化防重复提交拦截器</li> + <li>优化上级菜单不能选择自己</li> + <li>修复角色的权限分配后,未实时生效问题</li> + <li>修复在线用户日志记录类型</li> + <li>修复富文本空格和缩进保存后不生效问题</li> + <li>修复在线用户判断逻辑</li> + <li>唯一限制条件只返回单条数据</li> + <li>添加获取当前的环境配置方法</li> + <li>超时登录后页面跳转到首页</li> + <li>全局异常状态汉化拦截处理</li> + <li>HTML过滤器改为将html转义</li> + <li>检查字符支持小数点&降级改成异常提醒</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + + <el-collapse-item title="v3.0.0 - 2020-07-20"> + <ol> + <li>单应用调整为多模块项目</li> + <li>升级element-ui版本到2.13.2</li> + <li>删除babel,提高编译速度。</li> + <li>新增菜单默认主类目</li> + <li>编码文件名修改为uuid方式</li> + <li>定时任务cron表达式验证</li> + <li>角色权限修改时已有权限未自动勾选异常修复</li> + <li>防止切换权限用户后登录出现404</li> + <li>Excel支持sort导出排序</li> + <li>创建用户不允许选择超级管理员角色</li> + <li>修复代码生成导入表结构出现异常页面不提醒问题</li> + <li>修复代码生成点击多次表修改数据不变化的问题</li> + <li>修复头像上传成功二次打开无法改变裁剪框大小和位置问题</li> + <li>修复布局为small者mini用户表单显示错位问题</li> + <li>修复热部署导致的强换异常问题</li> + <li>修改用户管理复选框宽度,防止部分浏览器出现省略号</li> + <li>IpUtils工具,清除Xss特殊字符,防止Xff注入攻击</li> + <li>生成domain 如果是浮点型 统一用BigDecimal</li> + <li>定时任务调整label-width,防止部署出现错位</li> + <li>调整表头固定列默认样式</li> + <li>代码生成模板调整,字段为String并且必填则加空串条件</li> + <li>代码生成字典Integer/Long使用parseInt</li> + <li> + 修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序 + </li> + <li>修正岗位导出权限注解</li> + <li>禁止加密密文返回前端</li> + <li>修复代码生成页面中的查询条件创建时间未生效的问题</li> + <li>修复首页搜索菜单外链无法点击跳转问题</li> + <li>修复菜单管理选择图标,backspace删除时不过滤数据</li> + <li>用户管理部门分支节点不可检查&显示计数</li> + <li>数据范围过滤属性调整</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + + <el-collapse-item title="v2.3.0 - 2020-06-01"> + <ol> + <li>升级fastjson到最新版1.2.70 修复高危安全漏洞</li> + <li>dev启动默认打开浏览器</li> + <li>vue-cli使用默认source-map</li> + <li>slidebar eslint报错优化</li> + <li>当tags-view滚动关闭右键菜单</li> + <li>字典管理添加缓存读取</li> + <li>参数管理支持缓存操作</li> + <li>支持一级菜单(和主页同级)在main区域显示</li> + <li>限制外链地址必须以http(s)开头</li> + <li>tagview & sidebar 主题颜色与element ui(全局)同步</li> + <li>修改数据源类型优先级,先根据方法,再根据类</li> + <li>支持是否需要设置token属性,自定义返回码消息。</li> + <li>swagger请求前缀加入配置。</li> + <li>登录地点设置内容过长则隐藏显示</li> + <li>修复定时任务执行一次按钮后不提示消息问题</li> + <li>修改上级部门(选择项排除本身和下级)</li> + <li>通用http发送方法增加参数 contentType 编码类型</li> + <li>更换IP地址查询接口</li> + <li>修复页签变量undefined</li> + <li>添加校验部门包含未停用的子部门</li> + <li>修改定时任务详情下次执行时间日期显示错误</li> + <li>角色管理查询设置默认排序字段</li> + <li>swagger添加enable参数控制是否启用</li> + <li>只对json类型请求构建可重复读取inputStream的request</li> + <li>修改代码生成字典字段int类型没有自动选中问题</li> + <li>vuex用户名取值修正</li> + <li>表格树模板去掉多余的)</li> + <li>代码生成序号修正</li> + <li>全屏情况下不调整上外边距</li> + <li>代码生成Date字段添加默认格式</li> + <li>用户管理角色选择权限控制</li> + <li>修复路由懒加载报错问题</li> + <li>模板sql.vm添加菜单状态</li> + <li>设置用户名称不能修改</li> + <li>dialog添加append-to-body属性,防止ie遮罩</li> + <li>菜单区分状态和显示隐藏功能</li> + <li>升级fastjson到最新版1.2.68 修复安全加固</li> + <li>修复代码生成如果选择字典类型缺失逗号问题</li> + <li>登录请求params更换为data,防止暴露url</li> + <li>日志返回时间格式处理</li> + <li>添加handle控制允许拖动的元素</li> + <li>布局设置点击扩大范围</li> + <li>代码生成列属性排序查询</li> + <li>代码生成列支持拖动排序</li> + <li>修复时间格式不支持ios问题</li> + <li>表单构建添加父级class,防止冲突</li> + <li>定时任务并发属性修正</li> + <li>角色禁用&菜单隐藏不查询权限</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + + <el-collapse-item title="v2.2.0 - 2020-03-18"> + <ol> + <li>系统监控新增定时任务功能</li> + <li>添加一个打包Web工程bat</li> + <li>修复页签鼠标滚轮按下的时候,可以关闭不可关闭的tag</li> + <li>修复点击退出登录有时会无提示问题</li> + <li>修复防重复提交注解无效问题</li> + <li>修复通知公告批量删除异常问题</li> + <li>添加菜单时路由地址必填限制</li> + <li>代码生成字段描述可编辑</li> + <li>修复用户修改个人信息导致缓存不过期问题</li> + <li>个人信息创建时间获取正确属性值</li> + <li>操作日志详细显示正确类型</li> + <li>导入表单击行数据时选中对应的复选框</li> + <li>批量替换表前缀逻辑调整</li> + <li>固定重定向路径表达式</li> + <li>升级element-ui版本到2.13.0</li> + <li>操作日志排序调整</li> + <li>修复charts切换侧边栏或者缩放窗口显示bug</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + + <el-collapse-item title="v2.1.0 - 2020-02-24"> + <ol> + <li>新增表单构建</li> + <li>代码生成支持树表结构</li> + <li>新增用户导入</li> + <li>修复动态加载路由页面刷新问题</li> + <li>修复地址开关无效问题</li> + <li>汉化错误提示页面</li> + <li>代码生成已知问题修改</li> + <li>修复多数据源下配置关闭出现异常处理</li> + <li>添加HTML过滤器,用于去除XSS漏洞隐患</li> + <li>修复上传头像控制台出现异常</li> + <li>修改用户管理分页不正确的问题</li> + <li>修复验证码记录提示错误</li> + <li>修复request.js缺少Message引用</li> + <li>修复表格时间为空出现的异常</li> + <li>添加Jackson日期反序列化时区配置</li> + <li>调整根据用户权限加载菜单数据树形结构</li> + <li>调整成功登录不恢复按钮,防止多次点击</li> + <li>修改用户个人资料同步缓存信息</li> + <li>修复页面同时出现el-upload和Editor不显示处理</li> + <li>修复在角色管理页修改菜单权限偶尔未选中问题</li> + <li>配置文件新增redis密码属性</li> + <li>设置mybatis全局的配置文件</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + + <el-collapse-item title="v2.0.0 - 2019-12-02"> + <ol> + <li>新增代码生成</li> + <li>新增@RepeatSubmit注解,防止重复提交</li> + <li>新增菜单主目录添加/删除操作</li> + <li>日志记录过滤特殊对象,防止转换异常</li> + <li>修改代码生成路由脚本错误</li> + <li>用户上传头像实时同步缓存,无需重新登录</li> + <li>调整切换页签后不重新加载数据</li> + <li>添加jsencrypt实现参数的前端加密</li> + <li>系统退出删除用户缓存记录</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v1.1.0 - 2019-11-11"> + <ol> + <li>新增在线用户管理</li> + <li>新增按钮组功能实现(批量删除、导出、清空)</li> + <li>新增查询条件重置按钮</li> + <li>新增Swagger全局Token配置</li> + <li>新增后端参数校验</li> + <li>修复字典管理页面的日期查询异常</li> + <li>修改时间函数命名防止冲突</li> + <li>去除菜单上级校验,默认为顶级</li> + <li>修复用户密码无法修改问题</li> + <li>修复菜单类型为按钮时不显示权限标识</li> + <li>其他细节优化</li> + </ol> + </el-collapse-item> + <el-collapse-item title="v1.0.0 - 2019-10-08"> + <ol> + <li>若依前后端分离系统正式发布</li> + </ol> + </el-collapse-item> + </el-collapse> + </el-card> + </el-col> + <el-col :xs="24" :sm="24" :md="12" :lg="8"> + <el-card class="update-log"> + <div slot="header" class="clearfix"> + <span>捐赠支持</span> + </div> + <div class="body"> + <img + src="@/assets/images/pay.png" + alt="donate" + width="100%" + /> + <span style="display: inline-block; height: 30px; line-height: 30px" + >你可以请作者喝杯咖啡表示鼓励</span + > + </div> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +export default { + name: "Index", + data() { + return { + // 版本号 + version: "3.8.6" + }; + }, + methods: { + goTarget(href) { + window.open(href, "_blank"); + } + } +}; +</script> + +<style scoped lang="scss"> +.home { + blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; + } + hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; + } + .col-item { + margin-bottom: 20px; + } + + ul { + padding: 0; + margin: 0; + } + + font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + color: #676a6c; + overflow-x: hidden; + + ul { + list-style-type: none; + } + + h4 { + margin-top: 0px; + } + + h2 { + margin-top: 10px; + font-size: 26px; + font-weight: 100; + } + + p { + margin-top: 10px; + + b { + font-weight: 700; + } + } + + .update-log { + ol { + display: block; + list-style-type: decimal; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0; + margin-inline-end: 0; + padding-inline-start: 40px; + } + } +} +</style> + diff --git a/ruoyi-ui/src/views/index_v1.vue b/ruoyi-ui/src/views/index_v1.vue new file mode 100644 index 0000000..d2d2ec6 --- /dev/null +++ b/ruoyi-ui/src/views/index_v1.vue @@ -0,0 +1,98 @@ +<template> + <div class="dashboard-editor-container"> + + <panel-group @handleSetLineChartData="handleSetLineChartData" /> + + <el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;"> + <line-chart :chart-data="lineChartData" /> + </el-row> + + <el-row :gutter="32"> + <el-col :xs="24" :sm="24" :lg="8"> + <div class="chart-wrapper"> + <raddar-chart /> + </div> + </el-col> + <el-col :xs="24" :sm="24" :lg="8"> + <div class="chart-wrapper"> + <pie-chart /> + </div> + </el-col> + <el-col :xs="24" :sm="24" :lg="8"> + <div class="chart-wrapper"> + <bar-chart /> + </div> + </el-col> + </el-row> + + + </div> +</template> + +<script> +import PanelGroup from './dashboard/PanelGroup' +import LineChart from './dashboard/LineChart' +import RaddarChart from './dashboard/RaddarChart' +import PieChart from './dashboard/PieChart' +import BarChart from './dashboard/BarChart' + +const lineChartData = { + newVisitis: { + expectedData: [100, 120, 161, 134, 105, 160, 165], + actualData: [120, 82, 91, 154, 162, 140, 145] + }, + messages: { + expectedData: [200, 192, 120, 144, 160, 130, 140], + actualData: [180, 160, 151, 106, 145, 150, 130] + }, + purchases: { + expectedData: [80, 100, 121, 104, 105, 90, 100], + actualData: [120, 90, 100, 138, 142, 130, 130] + }, + shoppings: { + expectedData: [130, 140, 141, 142, 145, 150, 160], + actualData: [120, 82, 91, 154, 162, 140, 130] + } +} + +export default { + name: 'Index', + components: { + PanelGroup, + LineChart, + RaddarChart, + PieChart, + BarChart + }, + data() { + return { + lineChartData: lineChartData.newVisitis + } + }, + methods: { + handleSetLineChartData(type) { + this.lineChartData = lineChartData[type] + } + } +} +</script> + +<style lang="scss" scoped> +.dashboard-editor-container { + padding: 32px; + background-color: rgb(240, 242, 245); + position: relative; + + .chart-wrapper { + background: #fff; + padding: 16px 16px 0; + margin-bottom: 32px; + } +} + +@media (max-width:1024px) { + .chart-wrapper { + padding: 8px; + } +} +</style> diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue new file mode 100644 index 0000000..a779e88 --- /dev/null +++ b/ruoyi-ui/src/views/login.vue @@ -0,0 +1,219 @@ +<template> + <div class="login"> + <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form"> + <h3 class="title">若依后台管理系统</h3> + <el-form-item prop="username"> + <el-input + v-model="loginForm.username" + type="text" + auto-complete="off" + placeholder="账号" + > + <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <el-form-item prop="password"> + <el-input + v-model="loginForm.password" + type="password" + auto-complete="off" + placeholder="密码" + @keyup.enter.native="handleLogin" + > + <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <el-form-item prop="code" v-if="captchaEnabled"> + <el-input + v-model="loginForm.code" + auto-complete="off" + placeholder="验证码" + style="width: 63%" + @keyup.enter.native="handleLogin" + > + <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> + </el-input> + <div class="login-code"> + <img :src="codeUrl" @click="getCode" class="login-code-img"/> + </div> + </el-form-item> + <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> + <el-form-item style="width:100%;"> + <el-button + :loading="loading" + size="medium" + type="primary" + style="width:100%;" + @click.native.prevent="handleLogin" + > + <span v-if="!loading">登 录</span> + <span v-else>登 录 中...</span> + </el-button> + <div style="float: right;" v-if="register"> + <router-link class="link-type" :to="'/register'">立即注册</router-link> + </div> + </el-form-item> + </el-form> + <!-- 底部 --> + <div class="el-login-footer"> + <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span> + </div> + </div> +</template> + +<script> +import { getCodeImg } from "@/api/login"; +import Cookies from "js-cookie"; +import { encrypt, decrypt } from '@/utils/jsencrypt' + +export default { + name: "Login", + data() { + return { + codeUrl: "", + loginForm: { + username: "admin", + password: "admin123", + rememberMe: false, + code: "", + uuid: "" + }, + loginRules: { + username: [ + { required: true, trigger: "blur", message: "请输入您的账号" } + ], + password: [ + { required: true, trigger: "blur", message: "请输入您的密码" } + ], + code: [{ required: true, trigger: "change", message: "请输入验证码" }] + }, + loading: false, + // 验证码开关 + captchaEnabled: false, + // 注册开关 + register: false, + redirect: undefined + }; + }, + watch: { + $route: { + handler: function(route) { + this.redirect = route.query && route.query.redirect; + }, + immediate: true + } + }, + created() { + this.getCode(); + this.getCookie(); + }, + methods: { + getCode() { + getCodeImg().then(res => { + this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled; + if (this.captchaEnabled) { + this.codeUrl = "data:image/gif;base64," + res.img; + this.loginForm.uuid = res.uuid; + } + }); + }, + getCookie() { + const username = Cookies.get("username"); + const password = Cookies.get("password"); + const rememberMe = Cookies.get('rememberMe') + this.loginForm = { + username: username === undefined ? this.loginForm.username : username, + password: password === undefined ? this.loginForm.password : decrypt(password), + rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) + }; + }, + handleLogin() { + this.$refs.loginForm.validate(valid => { + if (valid) { + this.loading = true; + if (this.loginForm.rememberMe) { + Cookies.set("username", this.loginForm.username, { expires: 30 }); + Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 }); + Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 }); + } else { + Cookies.remove("username"); + Cookies.remove("password"); + Cookies.remove('rememberMe'); + } + this.$store.dispatch("Login", this.loginForm).then(() => { + this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); + }).catch(() => { + this.loading = false; + if (this.captchaEnabled) { + this.getCode(); + } + }); + } + }); + } + } +}; +</script> + +<style rel="stylesheet/scss" lang="scss"> +.login { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + background-image: url("../assets/images/login-background.jpg"); + background-size: cover; +} +.title { + margin: 0px auto 30px auto; + text-align: center; + color: #707070; +} + +.login-form { + border-radius: 6px; + background: #ffffff; + width: 400px; + padding: 25px 25px 5px 25px; + .el-input { + height: 38px; + input { + height: 38px; + } + } + .input-icon { + height: 39px; + width: 14px; + margin-left: 2px; + } +} +.login-tip { + font-size: 13px; + text-align: center; + color: #bfbfbf; +} +.login-code { + width: 33%; + height: 38px; + float: right; + img { + cursor: pointer; + vertical-align: middle; + } +} +.el-login-footer { + height: 40px; + line-height: 40px; + position: fixed; + bottom: 0; + width: 100%; + text-align: center; + color: #fff; + font-family: Arial; + font-size: 12px; + letter-spacing: 1px; +} +.login-code-img { + height: 38px; +} +</style> diff --git a/ruoyi-ui/src/views/monitor/cache/index.vue b/ruoyi-ui/src/views/monitor/cache/index.vue new file mode 100644 index 0000000..e81da2e --- /dev/null +++ b/ruoyi-ui/src/views/monitor/cache/index.vue @@ -0,0 +1,144 @@ +<template> + <div class="app-container"> + <el-row> + <el-col :span="24" class="card-box"> + <el-card> + <div slot="header"><span><i class="el-icon-monitor"></i> 基本信息</span></div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <table cellspacing="0" style="width: 100%"> + <tbody> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">端口</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">运行时间(天)</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">AOF是否开启</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "否" : "是" }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">RDB是否成功</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td> + <td class="el-table__cell is-leaf"><div class="cell">网络入口/出口</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td> + </tr> + </tbody> + </table> + </div> + </el-card> + </el-col> + + <el-col :span="12" class="card-box"> + <el-card> + <div slot="header"><span><i class="el-icon-pie-chart"></i> 命令统计</span></div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <div ref="commandstats" style="height: 420px" /> + </div> + </el-card> + </el-col> + + <el-col :span="12" class="card-box"> + <el-card> + <div slot="header"><span><i class="el-icon-odometer"></i> 内存信息</span></div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <div ref="usedmemory" style="height: 420px" /> + </div> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import { getCache } from "@/api/monitor/cache"; +import * as echarts from "echarts"; + +export default { + name: "Cache", + data() { + return { + // 统计命令信息 + commandstats: null, + // 使用内存 + usedmemory: null, + // cache信息 + cache: [] + } + }, + created() { + this.getList(); + this.openLoading(); + }, + methods: { + /** 查缓存询信息 */ + getList() { + getCache().then((response) => { + this.cache = response.data; + this.$modal.closeLoading(); + + this.commandstats = echarts.init(this.$refs.commandstats, "macarons"); + this.commandstats.setOption({ + tooltip: { + trigger: "item", + formatter: "{a} <br/>{b} : {c} ({d}%)", + }, + series: [ + { + name: "命令", + type: "pie", + roseType: "radius", + radius: [15, 95], + center: ["50%", "38%"], + data: response.data.commandStats, + animationEasing: "cubicInOut", + animationDuration: 1000, + } + ] + }); + this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons"); + this.usedmemory.setOption({ + tooltip: { + formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human, + }, + series: [ + { + name: "峰值", + type: "gauge", + min: 0, + max: 1000, + detail: { + formatter: this.cache.info.used_memory_human, + }, + data: [ + { + value: parseFloat(this.cache.info.used_memory_human), + name: "内存消耗", + } + ] + } + ] + }); + }); + }, + // 打开加载层 + openLoading() { + this.$modal.loading("正在加载缓存监控数据,请稍候!"); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/monitor/cache/list.vue b/ruoyi-ui/src/views/monitor/cache/list.vue new file mode 100644 index 0000000..29a7c74 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/cache/list.vue @@ -0,0 +1,241 @@ +<template> + <div class="app-container"> + <el-row :gutter="10"> + <el-col :span="8"> + <el-card style="height: calc(100vh - 125px)"> + <div slot="header"> + <span><i class="el-icon-collection"></i> 缓存列表</span> + <el-button + style="float: right; padding: 3px 0" + type="text" + icon="el-icon-refresh-right" + @click="refreshCacheNames()" + ></el-button> + </div> + <el-table + v-loading="loading" + :data="cacheNames" + :height="tableHeight" + highlight-current-row + @row-click="getCacheKeys" + style="width: 100%" + > + <el-table-column + label="序号" + width="60" + type="index" + ></el-table-column> + + <el-table-column + label="缓存名称" + align="center" + prop="cacheName" + :show-overflow-tooltip="true" + :formatter="nameFormatter" + ></el-table-column> + + <el-table-column + label="备注" + align="center" + prop="remark" + :show-overflow-tooltip="true" + /> + <el-table-column + label="操作" + width="60" + align="center" + class-name="small-padding fixed-width" + > + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleClearCacheName(scope.row)" + ></el-button> + </template> + </el-table-column> + </el-table> + </el-card> + </el-col> + + <el-col :span="8"> + <el-card style="height: calc(100vh - 125px)"> + <div slot="header"> + <span><i class="el-icon-key"></i> 键名列表</span> + <el-button + style="float: right; padding: 3px 0" + type="text" + icon="el-icon-refresh-right" + @click="refreshCacheKeys()" + ></el-button> + </div> + <el-table + v-loading="subLoading" + :data="cacheKeys" + :height="tableHeight" + highlight-current-row + @row-click="handleCacheValue" + style="width: 100%" + > + <el-table-column + label="序号" + width="60" + type="index" + ></el-table-column> + <el-table-column + label="缓存键名" + align="center" + :show-overflow-tooltip="true" + :formatter="keyFormatter" + > + </el-table-column> + <el-table-column + label="操作" + width="60" + align="center" + class-name="small-padding fixed-width" + > + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleClearCacheKey(scope.row)" + ></el-button> + </template> + </el-table-column> + </el-table> + </el-card> + </el-col> + + <el-col :span="8"> + <el-card :bordered="false" style="height: calc(100vh - 125px)"> + <div slot="header"> + <span><i class="el-icon-document"></i> 缓存内容</span> + <el-button + style="float: right; padding: 3px 0" + type="text" + icon="el-icon-refresh-right" + @click="handleClearCacheAll()" + >清理全部</el-button + > + </div> + <el-form :model="cacheForm"> + <el-row :gutter="32"> + <el-col :offset="1" :span="22"> + <el-form-item label="缓存名称:" prop="cacheName"> + <el-input v-model="cacheForm.cacheName" :readOnly="true" /> + </el-form-item> + </el-col> + <el-col :offset="1" :span="22"> + <el-form-item label="缓存键名:" prop="cacheKey"> + <el-input v-model="cacheForm.cacheKey" :readOnly="true" /> + </el-form-item> + </el-col> + <el-col :offset="1" :span="22"> + <el-form-item label="缓存内容:" prop="cacheValue"> + <el-input + v-model="cacheForm.cacheValue" + type="textarea" + :rows="8" + :readOnly="true" + /> + </el-form-item> + </el-col> + </el-row> + </el-form> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import { listCacheName, listCacheKey, getCacheValue, clearCacheName, clearCacheKey, clearCacheAll } from "@/api/monitor/cache"; + +export default { + name: "CacheList", + data() { + return { + cacheNames: [], + cacheKeys: [], + cacheForm: {}, + loading: true, + subLoading: false, + nowCacheName: "", + tableHeight: window.innerHeight - 200 + }; + }, + created() { + this.getCacheNames(); + }, + methods: { + /** 查询缓存名称列表 */ + getCacheNames() { + this.loading = true; + listCacheName().then(response => { + this.cacheNames = response.data; + this.loading = false; + }); + }, + /** 刷新缓存名称列表 */ + refreshCacheNames() { + this.getCacheNames(); + this.$modal.msgSuccess("刷新缓存列表成功"); + }, + /** 清理指定名称缓存 */ + handleClearCacheName(row) { + clearCacheName(row.cacheName).then(response => { + this.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功"); + this.getCacheKeys(); + }); + }, + /** 查询缓存键名列表 */ + getCacheKeys(row) { + const cacheName = row !== undefined ? row.cacheName : this.nowCacheName; + if (cacheName === "") { + return; + } + this.subLoading = true; + listCacheKey(cacheName).then(response => { + this.cacheKeys = response.data; + this.subLoading = false; + this.nowCacheName = cacheName; + }); + }, + /** 刷新缓存键名列表 */ + refreshCacheKeys() { + this.getCacheKeys(); + this.$modal.msgSuccess("刷新键名列表成功"); + }, + /** 清理指定键名缓存 */ + handleClearCacheKey(cacheKey) { + clearCacheKey(cacheKey).then(response => { + this.$modal.msgSuccess("清理缓存键名[" + cacheKey + "]成功"); + this.getCacheKeys(); + }); + }, + /** 列表前缀去除 */ + nameFormatter(row) { + return row.cacheName.replace(":", ""); + }, + /** 键名前缀去除 */ + keyFormatter(cacheKey) { + return cacheKey.replace(this.nowCacheName, ""); + }, + /** 查询缓存内容详细 */ + handleCacheValue(cacheKey) { + getCacheValue(this.nowCacheName, cacheKey).then(response => { + this.cacheForm = response.data; + }); + }, + /** 清理全部缓存 */ + handleClearCacheAll() { + clearCacheAll().then(response => { + this.$modal.msgSuccess("清理全部缓存成功"); + }); + } + }, +}; +</script> diff --git a/ruoyi-ui/src/views/monitor/druid/index.vue b/ruoyi-ui/src/views/monitor/druid/index.vue new file mode 100644 index 0000000..c6ad585 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/druid/index.vue @@ -0,0 +1,15 @@ +<template> + <i-frame :src="url" /> +</template> +<script> +import iFrame from "@/components/iFrame/index"; +export default { + name: "Druid", + components: { iFrame }, + data() { + return { + url: process.env.VUE_APP_BASE_API + "/druid/login.html" + }; + }, +}; +</script> diff --git a/ruoyi-ui/src/views/monitor/job/index.vue b/ruoyi-ui/src/views/monitor/job/index.vue new file mode 100644 index 0000000..892c727 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/job/index.vue @@ -0,0 +1,513 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="任务名称" prop="jobName"> + <el-input + v-model="queryParams.jobName" + placeholder="请输入任务名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="任务组名" prop="jobGroup"> + <el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable> + <el-option + v-for="dict in dict.type.sys_job_group" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="任务状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable> + <el-option + v-for="dict in dict.type.sys_job_status" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['monitor:job:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['monitor:job:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['monitor:job:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['monitor:job:export']" + >导出</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-s-operation" + size="mini" + @click="handleJobLog" + v-hasPermi="['monitor:job:query']" + >日志</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="任务编号" width="100" align="center" prop="jobId" /> + <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" /> + <el-table-column label="任务组名" align="center" prop="jobGroup"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/> + </template> + </el-table-column> + <el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" /> + <el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" /> + <el-table-column label="状态" align="center"> + <template slot-scope="scope"> + <el-switch + v-model="scope.row.status" + active-value="0" + inactive-value="1" + @change="handleStatusChange(scope.row)" + ></el-switch> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['monitor:job:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['monitor:job:remove']" + >删除</el-button> + <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['monitor:job:changeStatus', 'monitor:job:query']"> + <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item command="handleRun" icon="el-icon-caret-right" + v-hasPermi="['monitor:job:changeStatus']">执行一次</el-dropdown-item> + <el-dropdown-item command="handleView" icon="el-icon-view" + v-hasPermi="['monitor:job:query']">任务详细</el-dropdown-item> + <el-dropdown-item command="handleJobLog" icon="el-icon-s-operation" + v-hasPermi="['monitor:job:query']">调度日志</el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改定时任务对话框 --> + <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="120px"> + <el-row> + <el-col :span="12"> + <el-form-item label="任务名称" prop="jobName"> + <el-input v-model="form.jobName" placeholder="请输入任务名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="任务分组" prop="jobGroup"> + <el-select v-model="form.jobGroup" placeholder="请选择任务分组"> + <el-option + v-for="dict in dict.type.sys_job_group" + :key="dict.value" + :label="dict.label" + :value="dict.value" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item prop="invokeTarget"> + <span slot="label"> + 调用方法 + <el-tooltip placement="top"> + <div slot="content"> + Bean调用示例:ryTask.ryParams('ry') + <br />Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry') + <br />参数说明:支持字符串,布尔类型,长整型,浮点型,整型 + </div> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="cron表达式" prop="cronExpression"> + <el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式"> + <template slot="append"> + <el-button type="primary" @click="handleShowCron"> + 生成表达式 + <i class="el-icon-time el-icon--right"></i> + </el-button> + </template> + </el-input> + </el-form-item> + </el-col> + <el-col :span="24" v-if="form.jobId !== undefined"> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_job_status" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="执行策略" prop="misfirePolicy"> + <el-radio-group v-model="form.misfirePolicy" size="small"> + <el-radio-button label="1">立即执行</el-radio-button> + <el-radio-button label="2">执行一次</el-radio-button> + <el-radio-button label="3">放弃执行</el-radio-button> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="是否并发" prop="concurrent"> + <el-radio-group v-model="form.concurrent" size="small"> + <el-radio-button label="0">允许</el-radio-button> + <el-radio-button label="1">禁止</el-radio-button> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + + <el-dialog title="Cron表达式生成器" :visible.sync="openCron" append-to-body destroy-on-close class="scrollbar"> + <crontab @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab> + </el-dialog> + + <!-- 任务日志详细 --> + <el-dialog title="任务详细" :visible.sync="openView" width="700px" append-to-body> + <el-form ref="form" :model="form" label-width="120px" size="mini"> + <el-row> + <el-col :span="12"> + <el-form-item label="任务编号:">{{ form.jobId }}</el-form-item> + <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item> + <el-form-item label="创建时间:">{{ form.createTime }}</el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="下次执行时间:">{{ parseTime(form.nextValidTime) }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="任务状态:"> + <div v-if="form.status == 0">正常</div> + <div v-else-if="form.status == 1">暂停</div> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="是否并发:"> + <div v-if="form.concurrent == 0">允许</div> + <div v-else-if="form.concurrent == 1">禁止</div> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="执行策略:"> + <div v-if="form.misfirePolicy == 0">默认策略</div> + <div v-else-if="form.misfirePolicy == 1">立即执行</div> + <div v-else-if="form.misfirePolicy == 2">执行一次</div> + <div v-else-if="form.misfirePolicy == 3">放弃执行</div> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button @click="openView = false">关 闭</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job"; +import Crontab from '@/components/Crontab' + +export default { + components: { Crontab }, + name: "Job", + dicts: ['sys_job_group', 'sys_job_status'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 定时任务表格数据 + jobList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 是否显示详细弹出层 + openView: false, + // 是否显示Cron表达式弹出层 + openCron: false, + // 传入的表达式 + expression: "", + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + jobName: undefined, + jobGroup: undefined, + status: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + jobName: [ + { required: true, message: "任务名称不能为空", trigger: "blur" } + ], + invokeTarget: [ + { required: true, message: "调用目标字符串不能为空", trigger: "blur" } + ], + cronExpression: [ + { required: true, message: "cron执行表达式不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询定时任务列表 */ + getList() { + this.loading = true; + listJob(this.queryParams).then(response => { + this.jobList = response.rows; + this.total = response.total; + this.loading = false; + }); + }, + // 任务组名字典翻译 + jobGroupFormat(row, column) { + return this.selectDictLabel(this.dict.type.sys_job_group, row.jobGroup); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + jobId: undefined, + jobName: undefined, + jobGroup: undefined, + invokeTarget: undefined, + cronExpression: undefined, + misfirePolicy: 1, + concurrent: 1, + status: "0" + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.jobId); + this.single = selection.length != 1; + this.multiple = !selection.length; + }, + // 更多操作触发 + handleCommand(command, row) { + switch (command) { + case "handleRun": + this.handleRun(row); + break; + case "handleView": + this.handleView(row); + break; + case "handleJobLog": + this.handleJobLog(row); + break; + default: + break; + } + }, + // 任务状态修改 + handleStatusChange(row) { + let text = row.status === "0" ? "启用" : "停用"; + this.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function() { + return changeJobStatus(row.jobId, row.status); + }).then(() => { + this.$modal.msgSuccess(text + "成功"); + }).catch(function() { + row.status = row.status === "0" ? "1" : "0"; + }); + }, + /* 立即执行一次 */ + handleRun(row) { + this.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function() { + return runJob(row.jobId, row.jobGroup); + }).then(() => { + this.$modal.msgSuccess("执行成功"); + }).catch(() => {}); + }, + /** 任务详细信息 */ + handleView(row) { + getJob(row.jobId).then(response => { + this.form = response.data; + this.openView = true; + }); + }, + /** cron表达式按钮操作 */ + handleShowCron() { + this.expression = this.form.cronExpression; + this.openCron = true; + }, + /** 确定后回传值 */ + crontabFill(value) { + this.form.cronExpression = value; + }, + /** 任务日志列表查询 */ + handleJobLog(row) { + const jobId = row.jobId || 0; + this.$router.push('/monitor/job-log/index/' + jobId) + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加任务"; + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const jobId = row.jobId || this.ids; + getJob(jobId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改任务"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.jobId != undefined) { + updateJob(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addJob(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const jobIds = row.jobId || this.ids; + this.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function() { + return delJob(jobIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('monitor/job/export', { + ...this.queryParams + }, `job_${new Date().getTime()}.xlsx`) + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/monitor/job/log.vue b/ruoyi-ui/src/views/monitor/job/log.vue new file mode 100644 index 0000000..60bee1d --- /dev/null +++ b/ruoyi-ui/src/views/monitor/job/log.vue @@ -0,0 +1,295 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="任务名称" prop="jobName"> + <el-input + v-model="queryParams.jobName" + placeholder="请输入任务名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="任务组名" prop="jobGroup"> + <el-select + v-model="queryParams.jobGroup" + placeholder="请选择任务组名" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_job_group" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="执行状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="请选择执行状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_common_status" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="执行时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['monitor:job:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + @click="handleClean" + v-hasPermi="['monitor:job:remove']" + >清空</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['monitor:job:export']" + >导出</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-close" + size="mini" + @click="handleClose" + >关闭</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="日志编号" width="80" align="center" prop="jobLogId" /> + <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" /> + <el-table-column label="任务组名" align="center" prop="jobGroup" :show-overflow-tooltip="true"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/> + </template> + </el-table-column> + <el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" /> + <el-table-column label="日志信息" align="center" prop="jobMessage" :show-overflow-tooltip="true" /> + <el-table-column label="执行状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="执行时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-view" + @click="handleView(scope.row)" + v-hasPermi="['monitor:job:query']" + >详细</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 调度日志详细 --> + <el-dialog title="调度日志详细" :visible.sync="open" width="700px" append-to-body> + <el-form ref="form" :model="form" label-width="100px" size="mini"> + <el-row> + <el-col :span="12"> + <el-form-item label="日志序号:">{{ form.jobLogId }}</el-form-item> + <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="任务分组:">{{ form.jobGroup }}</el-form-item> + <el-form-item label="执行时间:">{{ form.createTime }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="调用方法:">{{ form.invokeTarget }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="执行状态:"> + <div v-if="form.status == 0">正常</div> + <div v-else-if="form.status == 1">失败</div> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="异常信息:" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button @click="open = false">关 闭</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { getJob} from "@/api/monitor/job"; +import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog"; + +export default { + name: "JobLog", + dicts: ['sys_common_status', 'sys_job_group'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 调度日志表格数据 + jobLogList: [], + // 是否显示弹出层 + open: false, + // 日期范围 + dateRange: [], + // 表单参数 + form: {}, + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + jobName: undefined, + jobGroup: undefined, + status: undefined + } + }; + }, + created() { + const jobId = this.$route.params && this.$route.params.jobId; + if (jobId !== undefined && jobId != 0) { + getJob(jobId).then(response => { + this.queryParams.jobName = response.data.jobName; + this.queryParams.jobGroup = response.data.jobGroup; + this.getList(); + }); + } else { + this.getList(); + } + }, + methods: { + /** 查询调度日志列表 */ + getList() { + this.loading = true; + listJobLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.jobLogList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + // 返回按钮 + handleClose() { + const obj = { path: "/monitor/job" }; + this.$tab.closeOpenPage(obj); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.jobLogId); + this.multiple = !selection.length; + }, + /** 详细按钮操作 */ + handleView(row) { + this.open = true; + this.form = row; + }, + /** 删除按钮操作 */ + handleDelete(row) { + const jobLogIds = this.ids; + this.$modal.confirm('是否确认删除调度日志编号为"' + jobLogIds + '"的数据项?').then(function() { + return delJobLog(jobLogIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 清空按钮操作 */ + handleClean() { + this.$modal.confirm('是否确认清空所有调度日志数据项?').then(function() { + return cleanJobLog(); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("清空成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('/monitor/jobLog/export', { + ...this.queryParams + }, `log_${new Date().getTime()}.xlsx`) + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/monitor/logininfor/index.vue b/ruoyi-ui/src/views/monitor/logininfor/index.vue new file mode 100644 index 0000000..d6af834 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/logininfor/index.vue @@ -0,0 +1,246 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="登录地址" prop="ipaddr"> + <el-input + v-model="queryParams.ipaddr" + placeholder="请输入登录地址" + clearable + style="width: 240px;" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="用户名称" prop="userName"> + <el-input + v-model="queryParams.userName" + placeholder="请输入用户名称" + clearable + style="width: 240px;" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="登录状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_common_status" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="登录时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['monitor:logininfor:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + @click="handleClean" + v-hasPermi="['monitor:logininfor:remove']" + >清空</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-unlock" + size="mini" + :disabled="single" + @click="handleUnlock" + v-hasPermi="['monitor:logininfor:unlock']" + >解锁</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['monitor:logininfor:export']" + >导出</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table ref="tables" v-loading="loading" :data="list" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="访问编号" align="center" prop="infoId" /> + <el-table-column label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" /> + <el-table-column label="登录地址" align="center" prop="ipaddr" width="130" :show-overflow-tooltip="true" /> + <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" /> + <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" /> + <el-table-column label="操作系统" align="center" prop="os" /> + <el-table-column label="登录状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="操作信息" align="center" prop="msg" :show-overflow-tooltip="true" /> + <el-table-column label="登录日期" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.loginTime) }}</span> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </div> +</template> + +<script> +import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/monitor/logininfor"; + +export default { + name: "Logininfor", + dicts: ['sys_common_status'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 选择用户名 + selectName: "", + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 表格数据 + list: [], + // 日期范围 + dateRange: [], + // 默认排序 + defaultSort: {prop: 'loginTime', order: 'descending'}, + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + ipaddr: undefined, + userName: undefined, + status: undefined + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询登录日志列表 */ + getList() { + this.loading = true; + list(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.list = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.queryParams.pageNum = 1; + this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order) + }, + /** 多选框选中数据 */ + handleSelectionChange(selection) { + this.ids = selection.map(item => item.infoId) + this.single = selection.length!=1 + this.multiple = !selection.length + this.selectName = selection.map(item => item.userName); + }, + /** 排序触发事件 */ + handleSortChange(column, prop, order) { + this.queryParams.orderByColumn = column.prop; + this.queryParams.isAsc = column.order; + this.getList(); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const infoIds = row.infoId || this.ids; + this.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?').then(function() { + return delLogininfor(infoIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 清空按钮操作 */ + handleClean() { + this.$modal.confirm('是否确认清空所有登录日志数据项?').then(function() { + return cleanLogininfor(); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("清空成功"); + }).catch(() => {}); + }, + /** 解锁按钮操作 */ + handleUnlock() { + const username = this.selectName; + this.$modal.confirm('是否确认解锁用户"' + username + '"数据项?').then(function() { + return unlockLogininfor(username); + }).then(() => { + this.$modal.msgSuccess("用户" + username + "解锁成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('monitor/logininfor/export', { + ...this.queryParams + }, `logininfor_${new Date().getTime()}.xlsx`) + } + } +}; +</script> + diff --git a/ruoyi-ui/src/views/monitor/online/index.vue b/ruoyi-ui/src/views/monitor/online/index.vue new file mode 100644 index 0000000..ad613c9 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/online/index.vue @@ -0,0 +1,122 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px"> + <el-form-item label="登录地址" prop="ipaddr"> + <el-input + v-model="queryParams.ipaddr" + placeholder="请输入登录地址" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="用户名称" prop="userName"> + <el-input + v-model="queryParams.userName" + placeholder="请输入用户名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + + </el-form> + <el-table + v-loading="loading" + :data="list.slice((pageNum-1)*pageSize,pageNum*pageSize)" + style="width: 100%;" + > + <el-table-column label="序号" type="index" align="center"> + <template slot-scope="scope"> + <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span> + </template> + </el-table-column> + <el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" /> + <el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column label="部门名称" align="center" prop="deptName" /> + <el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" /> + <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" /> + <el-table-column label="浏览器" align="center" prop="browser" /> + <el-table-column label="操作系统" align="center" prop="os" /> + <el-table-column label="登录时间" align="center" prop="loginTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.loginTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleForceLogout(scope.row)" + v-hasPermi="['monitor:online:forceLogout']" + >强退</el-button> + </template> + </el-table-column> + </el-table> + + <pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" /> + </div> +</template> + +<script> +import { list, forceLogout } from "@/api/monitor/online"; + +export default { + name: "Online", + data() { + return { + // 遮罩层 + loading: true, + // 总条数 + total: 0, + // 表格数据 + list: [], + pageNum: 1, + pageSize: 10, + // 查询参数 + queryParams: { + ipaddr: undefined, + userName: undefined + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询登录日志列表 */ + getList() { + this.loading = true; + list(this.queryParams).then(response => { + this.list = response.rows; + this.total = response.total; + this.loading = false; + }); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 强退按钮操作 */ + handleForceLogout(row) { + this.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?').then(function() { + return forceLogout(row.tokenId); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("强退成功"); + }).catch(() => {}); + } + } +}; +</script> + diff --git a/ruoyi-ui/src/views/monitor/operlog/index.vue b/ruoyi-ui/src/views/monitor/operlog/index.vue new file mode 100644 index 0000000..988f983 --- /dev/null +++ b/ruoyi-ui/src/views/monitor/operlog/index.vue @@ -0,0 +1,323 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="操作地址" prop="operIp"> + <el-input + v-model="queryParams.operIp" + placeholder="请输入操作地址" + clearable + style="width: 240px;" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="系统模块" prop="title"> + <el-input + v-model="queryParams.title" + placeholder="请输入系统模块" + clearable + style="width: 240px;" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="操作人员" prop="operName"> + <el-input + v-model="queryParams.operName" + placeholder="请输入操作人员" + clearable + style="width: 240px;" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="类型" prop="businessType"> + <el-select + v-model="queryParams.businessType" + placeholder="操作类型" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_oper_type" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="操作状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_common_status" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="操作时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['monitor:operlog:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + @click="handleClean" + v-hasPermi="['monitor:operlog:remove']" + >清空</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['monitor:operlog:export']" + >导出</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table ref="tables" v-loading="loading" :data="list" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange"> + <el-table-column type="selection" width="50" align="center" /> + <el-table-column label="日志编号" align="center" prop="operId" /> + <el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" /> + <el-table-column label="操作类型" align="center" prop="businessType"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_oper_type" :value="scope.row.businessType"/> + </template> + </el-table-column> + <el-table-column label="操作人员" align="center" prop="operName" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" /> + <el-table-column label="操作地址" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" /> + <el-table-column label="操作地点" align="center" prop="operLocation" :show-overflow-tooltip="true" /> + <el-table-column label="操作状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="操作日期" align="center" prop="operTime" width="160" sortable="custom" :sort-orders="['descending', 'ascending']"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.operTime) }}</span> + </template> + </el-table-column> + <el-table-column label="消耗时间" align="center" prop="costTime" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']"> + <template slot-scope="scope"> + <span>{{ scope.row.costTime }}毫秒</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-view" + @click="handleView(scope.row,scope.index)" + v-hasPermi="['monitor:operlog:query']" + >详细</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 操作日志详细 --> + <el-dialog title="操作日志详细" :visible.sync="open" width="700px" append-to-body> + <el-form ref="form" :model="form" label-width="100px" size="mini"> + <el-row> + <el-col :span="12"> + <el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item> + <el-form-item + label="登录信息:" + >{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="请求地址:">{{ form.operUrl }}</el-form-item> + <el-form-item label="请求方式:">{{ form.requestMethod }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="操作方法:">{{ form.method }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="请求参数:">{{ form.operParam }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="操作状态:"> + <div v-if="form.status === 0">正常</div> + <div v-else-if="form.status === 1">失败</div> + </el-form-item> + </el-col> + <el-col :span="8"> + <el-form-item label="消耗时间:">{{ form.costTime }}毫秒</el-form-item> + </el-col> + <el-col :span="10"> + <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button @click="open = false">关 闭</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog"; + +export default { + name: "Operlog", + dicts: ['sys_oper_type', 'sys_common_status'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 表格数据 + list: [], + // 是否显示弹出层 + open: false, + // 日期范围 + dateRange: [], + // 默认排序 + defaultSort: {prop: 'operTime', order: 'descending'}, + // 表单参数 + form: {}, + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + operIp: undefined, + title: undefined, + operName: undefined, + businessType: undefined, + status: undefined + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询登录日志 */ + getList() { + this.loading = true; + list(this.addDateRange(this.queryParams, this.dateRange)).then( response => { + this.list = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + // 操作日志类型字典翻译 + typeFormat(row, column) { + return this.selectDictLabel(this.dict.type.sys_oper_type, row.businessType); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.queryParams.pageNum = 1; + this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order) + }, + /** 多选框选中数据 */ + handleSelectionChange(selection) { + this.ids = selection.map(item => item.operId) + this.multiple = !selection.length + }, + /** 排序触发事件 */ + handleSortChange(column, prop, order) { + this.queryParams.orderByColumn = column.prop; + this.queryParams.isAsc = column.order; + this.getList(); + }, + /** 详细按钮操作 */ + handleView(row) { + this.open = true; + this.form = row; + }, + /** 删除按钮操作 */ + handleDelete(row) { + const operIds = row.operId || this.ids; + this.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?').then(function() { + return delOperlog(operIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 清空按钮操作 */ + handleClean() { + this.$modal.confirm('是否确认清空所有操作日志数据项?').then(function() { + return cleanOperlog(); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("清空成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('monitor/operlog/export', { + ...this.queryParams + }, `operlog_${new Date().getTime()}.xlsx`) + } + } +}; +</script> + diff --git a/ruoyi-ui/src/views/monitor/server/index.vue b/ruoyi-ui/src/views/monitor/server/index.vue new file mode 100644 index 0000000..15ffc9a --- /dev/null +++ b/ruoyi-ui/src/views/monitor/server/index.vue @@ -0,0 +1,207 @@ +<template> + <div class="app-container"> + <el-row> + <el-col :span="12" class="card-box"> + <el-card> + <div slot="header"><span><i class="el-icon-cpu"></i> CPU</span></div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <table cellspacing="0" style="width: 100%;"> + <thead> + <tr> + <th class="el-table__cell is-leaf"><div class="cell">属性</div></th> + <th class="el-table__cell is-leaf"><div class="cell">值</div></th> + </tr> + </thead> + <tbody> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">核心数</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">用户使用率</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">系统使用率</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">当前空闲率</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td> + </tr> + </tbody> + </table> + </div> + </el-card> + </el-col> + + <el-col :span="12" class="card-box"> + <el-card> + <div slot="header"><span><i class="el-icon-tickets"></i> 内存</span></div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <table cellspacing="0" style="width: 100%;"> + <thead> + <tr> + <th class="el-table__cell is-leaf"><div class="cell">属性</div></th> + <th class="el-table__cell is-leaf"><div class="cell">内存</div></th> + <th class="el-table__cell is-leaf"><div class="cell">JVM</div></th> + </tr> + </thead> + <tbody> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">总内存</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">已用内存</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">剩余内存</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">使用率</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td> + </tr> + </tbody> + </table> + </div> + </el-card> + </el-col> + + <el-col :span="24" class="card-box"> + <el-card> + <div slot="header"> + <span><i class="el-icon-monitor"></i> 服务器信息</span> + </div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <table cellspacing="0" style="width: 100%;"> + <tbody> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">服务器名称</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">操作系统</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">服务器IP</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">系统架构</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td> + </tr> + </tbody> + </table> + </div> + </el-card> + </el-col> + + <el-col :span="24" class="card-box"> + <el-card> + <div slot="header"> + <span><i class="el-icon-coffee-cup"></i> Java虚拟机信息</span> + </div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <table cellspacing="0" style="width: 100%;table-layout:fixed;"> + <tbody> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">Java名称</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">Java版本</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td> + </tr> + <tr> + <td class="el-table__cell is-leaf"><div class="cell">启动时间</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">运行时长</div></td> + <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td> + </tr> + <tr> + <td colspan="1" class="el-table__cell is-leaf"><div class="cell">安装路径</div></td> + <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td> + </tr> + <tr> + <td colspan="1" class="el-table__cell is-leaf"><div class="cell">项目路径</div></td> + <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td> + </tr> + <tr> + <td colspan="1" class="el-table__cell is-leaf"><div class="cell">运行参数</div></td> + <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.inputArgs }}</div></td> + </tr> + </tbody> + </table> + </div> + </el-card> + </el-col> + + <el-col :span="24" class="card-box"> + <el-card> + <div slot="header"> + <span><i class="el-icon-receiving"></i> 磁盘状态</span> + </div> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <table cellspacing="0" style="width: 100%;"> + <thead> + <tr> + <th class="el-table__cell el-table__cell is-leaf"><div class="cell">盘符路径</div></th> + <th class="el-table__cell is-leaf"><div class="cell">文件系统</div></th> + <th class="el-table__cell is-leaf"><div class="cell">盘符类型</div></th> + <th class="el-table__cell is-leaf"><div class="cell">总大小</div></th> + <th class="el-table__cell is-leaf"><div class="cell">可用大小</div></th> + <th class="el-table__cell is-leaf"><div class="cell">已用大小</div></th> + <th class="el-table__cell is-leaf"><div class="cell">已用百分比</div></th> + </tr> + </thead> + <tbody v-if="server.sysFiles"> + <tr v-for="(sysFile, index) in server.sysFiles" :key="index"> + <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.dirName }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.sysTypeName }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.typeName }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.total }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.free }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.used }}</div></td> + <td class="el-table__cell is-leaf"><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td> + </tr> + </tbody> + </table> + </div> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import { getServer } from "@/api/monitor/server"; + +export default { + name: "Server", + data() { + return { + // 服务器信息 + server: [] + }; + }, + created() { + this.getList(); + this.openLoading(); + }, + methods: { + /** 查询服务器信息 */ + getList() { + getServer().then(response => { + this.server = response.data; + this.$modal.closeLoading(); + }); + }, + // 打开加载层 + openLoading() { + this.$modal.loading("正在加载服务监控数据,请稍候!"); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/redirect.vue b/ruoyi-ui/src/views/redirect.vue new file mode 100644 index 0000000..db4c1d6 --- /dev/null +++ b/ruoyi-ui/src/views/redirect.vue @@ -0,0 +1,12 @@ +<script> +export default { + created() { + const { params, query } = this.$route + const { path } = params + this.$router.replace({ path: '/' + path, query }) + }, + render: function(h) { + return h() // avoid warning message + } +} +</script> diff --git a/ruoyi-ui/src/views/register.vue b/ruoyi-ui/src/views/register.vue new file mode 100644 index 0000000..396d582 --- /dev/null +++ b/ruoyi-ui/src/views/register.vue @@ -0,0 +1,209 @@ +<template> + <div class="register"> + <el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form"> + <h3 class="title">若依后台管理系统</h3> + <el-form-item prop="username"> + <el-input v-model="registerForm.username" type="text" auto-complete="off" placeholder="账号"> + <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <el-form-item prop="password"> + <el-input + v-model="registerForm.password" + type="password" + auto-complete="off" + placeholder="密码" + @keyup.enter.native="handleRegister" + > + <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <el-form-item prop="confirmPassword"> + <el-input + v-model="registerForm.confirmPassword" + type="password" + auto-complete="off" + placeholder="确认密码" + @keyup.enter.native="handleRegister" + > + <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> + </el-input> + </el-form-item> + <el-form-item prop="code" v-if="captchaEnabled"> + <el-input + v-model="registerForm.code" + auto-complete="off" + placeholder="验证码" + style="width: 63%" + @keyup.enter.native="handleRegister" + > + <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> + </el-input> + <div class="register-code"> + <img :src="codeUrl" @click="getCode" class="register-code-img"/> + </div> + </el-form-item> + <el-form-item style="width:100%;"> + <el-button + :loading="loading" + size="medium" + type="primary" + style="width:100%;" + @click.native.prevent="handleRegister" + > + <span v-if="!loading">注 册</span> + <span v-else>注 册 中...</span> + </el-button> + <div style="float: right;"> + <router-link class="link-type" :to="'/login'">使用已有账户登录</router-link> + </div> + </el-form-item> + </el-form> + <!-- 底部 --> + <div class="el-register-footer"> + <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span> + </div> + </div> +</template> + +<script> +import { getCodeImg, register } from "@/api/login"; + +export default { + name: "Register", + data() { + const equalToPassword = (rule, value, callback) => { + if (this.registerForm.password !== value) { + callback(new Error("两次输入的密码不一致")); + } else { + callback(); + } + }; + return { + codeUrl: "", + registerForm: { + username: "", + password: "", + confirmPassword: "", + code: "", + uuid: "" + }, + registerRules: { + username: [ + { required: true, trigger: "blur", message: "请输入您的账号" }, + { min: 2, max: 20, message: '用户账号长度必须介于 2 和 20 之间', trigger: 'blur' } + ], + password: [ + { required: true, trigger: "blur", message: "请输入您的密码" }, + { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' } + ], + confirmPassword: [ + { required: true, trigger: "blur", message: "请再次输入您的密码" }, + { required: true, validator: equalToPassword, trigger: "blur" } + ], + code: [{ required: true, trigger: "change", message: "请输入验证码" }] + }, + loading: false, + captchaEnabled: true + }; + }, + created() { + this.getCode(); + }, + methods: { + getCode() { + getCodeImg().then(res => { + this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled; + if (this.captchaEnabled) { + this.codeUrl = "data:image/gif;base64," + res.img; + this.registerForm.uuid = res.uuid; + } + }); + }, + handleRegister() { + this.$refs.registerForm.validate(valid => { + if (valid) { + this.loading = true; + register(this.registerForm).then(res => { + const username = this.registerForm.username; + this.$alert("<font color='red'>恭喜你,您的账号 " + username + " 注册成功!</font>", '系统提示', { + dangerouslyUseHTMLString: true, + type: 'success' + }).then(() => { + this.$router.push("/login"); + }).catch(() => {}); + }).catch(() => { + this.loading = false; + if (this.captchaEnabled) { + this.getCode(); + } + }) + } + }); + } + } +}; +</script> + +<style rel="stylesheet/scss" lang="scss"> +.register { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + background-image: url("../assets/images/login-background.jpg"); + background-size: cover; +} +.title { + margin: 0px auto 30px auto; + text-align: center; + color: #707070; +} + +.register-form { + border-radius: 6px; + background: #ffffff; + width: 400px; + padding: 25px 25px 5px 25px; + .el-input { + height: 38px; + input { + height: 38px; + } + } + .input-icon { + height: 39px; + width: 14px; + margin-left: 2px; + } +} +.register-tip { + font-size: 13px; + text-align: center; + color: #bfbfbf; +} +.register-code { + width: 33%; + height: 38px; + float: right; + img { + cursor: pointer; + vertical-align: middle; + } +} +.el-register-footer { + height: 40px; + line-height: 40px; + position: fixed; + bottom: 0; + width: 100%; + text-align: center; + color: #fff; + font-family: Arial; + font-size: 12px; + letter-spacing: 1px; +} +.register-code-img { + height: 38px; +} +</style> diff --git a/ruoyi-ui/src/views/system/config/index.vue b/ruoyi-ui/src/views/system/config/index.vue new file mode 100644 index 0000000..3ab81fc --- /dev/null +++ b/ruoyi-ui/src/views/system/config/index.vue @@ -0,0 +1,343 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="参数名称" prop="configName"> + <el-input + v-model="queryParams.configName" + placeholder="请输入参数名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="参数键名" prop="configKey"> + <el-input + v-model="queryParams.configKey" + placeholder="请输入参数键名" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="系统内置" prop="configType"> + <el-select v-model="queryParams.configType" placeholder="系统内置" clearable> + <el-option + v-for="dict in dict.type.sys_yes_no" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:config:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:config:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:config:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:config:export']" + >导出</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-refresh" + size="mini" + @click="handleRefreshCache" + v-hasPermi="['system:config:remove']" + >刷新缓存</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="参数主键" align="center" prop="configId" /> + <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" /> + <el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" /> + <el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" /> + <el-table-column label="系统内置" align="center" prop="configType"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_yes_no" :value="scope.row.configType"/> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:config:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:config:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改参数配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="参数名称" prop="configName"> + <el-input v-model="form.configName" placeholder="请输入参数名称" /> + </el-form-item> + <el-form-item label="参数键名" prop="configKey"> + <el-input v-model="form.configKey" placeholder="请输入参数键名" /> + </el-form-item> + <el-form-item label="参数键值" prop="configValue"> + <el-input v-model="form.configValue" placeholder="请输入参数键值" /> + </el-form-item> + <el-form-item label="系统内置" prop="configType"> + <el-radio-group v-model="form.configType"> + <el-radio + v-for="dict in dict.type.sys_yes_no" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config"; + +export default { + name: "Config", + dicts: ['sys_yes_no'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 参数表格数据 + configList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 日期范围 + dateRange: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + configName: undefined, + configKey: undefined, + configType: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + configName: [ + { required: true, message: "参数名称不能为空", trigger: "blur" } + ], + configKey: [ + { required: true, message: "参数键名不能为空", trigger: "blur" } + ], + configValue: [ + { required: true, message: "参数键值不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询参数列表 */ + getList() { + this.loading = true; + listConfig(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.configList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + configId: undefined, + configName: undefined, + configKey: undefined, + configValue: undefined, + configType: "Y", + remark: undefined + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加参数"; + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.configId) + this.single = selection.length!=1 + this.multiple = !selection.length + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const configId = row.configId || this.ids + getConfig(configId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改参数"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.configId != undefined) { + updateConfig(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addConfig(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const configIds = row.configId || this.ids; + this.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?').then(function() { + return delConfig(configIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('system/config/export', { + ...this.queryParams + }, `config_${new Date().getTime()}.xlsx`) + }, + /** 刷新缓存按钮操作 */ + handleRefreshCache() { + refreshCache().then(() => { + this.$modal.msgSuccess("刷新成功"); + }); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/dept/index.vue b/ruoyi-ui/src/views/system/dept/index.vue new file mode 100644 index 0000000..e502b4e --- /dev/null +++ b/ruoyi-ui/src/views/system/dept/index.vue @@ -0,0 +1,340 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"> + <el-form-item label="部门名称" prop="deptName"> + <el-input + v-model="queryParams.deptName" + placeholder="请输入部门名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="部门状态" clearable> + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:dept:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-sort" + size="mini" + @click="toggleExpandAll" + >展开/折叠</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table + v-if="refreshTable" + v-loading="loading" + :data="deptList" + row-key="deptId" + :default-expand-all="isExpandAll" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + > + <el-table-column prop="deptName" label="部门名称" width="260"></el-table-column> + <el-table-column prop="orderNum" label="排序" width="200"></el-table-column> + <el-table-column prop="status" label="状态" width="100"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime" width="200"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:dept:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-plus" + @click="handleAdd(scope.row)" + v-hasPermi="['system:dept:add']" + >新增</el-button> + <el-button + v-if="scope.row.parentId != 0" + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:dept:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <!-- 添加或修改部门对话框 --> + <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="24" v-if="form.parentId !== 0"> + <el-form-item label="上级部门" prop="parentId"> + <treeselect v-model="form.parentId" :options="deptOptions" :normalizer="normalizer" placeholder="选择上级部门" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="部门名称" prop="deptName"> + <el-input v-model="form.deptName" placeholder="请输入部门名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="显示排序" prop="orderNum"> + <el-input-number v-model="form.orderNum" controls-position="right" :min="0" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="负责人" prop="leader"> + <el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="联系电话" prop="phone"> + <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="部门状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"; +import Treeselect from "@riophae/vue-treeselect"; +import "@riophae/vue-treeselect/dist/vue-treeselect.css"; + +export default { + name: "Dept", + dicts: ['sys_normal_disable'], + components: { Treeselect }, + data() { + return { + // 遮罩层 + loading: true, + // 显示搜索条件 + showSearch: true, + // 表格树数据 + deptList: [], + // 部门树选项 + deptOptions: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 是否展开,默认全部展开 + isExpandAll: true, + // 重新渲染表格状态 + refreshTable: true, + // 查询参数 + queryParams: { + deptName: undefined, + status: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + parentId: [ + { required: true, message: "上级部门不能为空", trigger: "blur" } + ], + deptName: [ + { required: true, message: "部门名称不能为空", trigger: "blur" } + ], + orderNum: [ + { required: true, message: "显示排序不能为空", trigger: "blur" } + ], + email: [ + { + type: "email", + message: "请输入正确的邮箱地址", + trigger: ["blur", "change"] + } + ], + phone: [ + { + pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, + message: "请输入正确的手机号码", + trigger: "blur" + } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询部门列表 */ + getList() { + this.loading = true; + listDept(this.queryParams).then(response => { + this.deptList = this.handleTree(response.data, "deptId"); + this.loading = false; + }); + }, + /** 转换部门数据结构 */ + normalizer(node) { + if (node.children && !node.children.length) { + delete node.children; + } + return { + id: node.deptId, + label: node.deptName, + children: node.children + }; + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + deptId: undefined, + parentId: undefined, + deptName: undefined, + orderNum: undefined, + leader: undefined, + phone: undefined, + email: undefined, + status: "0" + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 新增按钮操作 */ + handleAdd(row) { + this.reset(); + if (row != undefined) { + this.form.parentId = row.deptId; + } + this.open = true; + this.title = "添加部门"; + listDept().then(response => { + this.deptOptions = this.handleTree(response.data, "deptId"); + }); + }, + /** 展开/折叠操作 */ + toggleExpandAll() { + this.refreshTable = false; + this.isExpandAll = !this.isExpandAll; + this.$nextTick(() => { + this.refreshTable = true; + }); + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + getDept(row.deptId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改部门"; + listDeptExcludeChild(row.deptId).then(response => { + this.deptOptions = this.handleTree(response.data, "deptId"); + if (this.deptOptions.length == 0) { + const noResultsOptions = { deptId: this.form.parentId, deptName: this.form.parentName, children: [] }; + this.deptOptions.push(noResultsOptions); + } + }); + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.deptId != undefined) { + updateDept(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addDept(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + this.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() { + return delDept(row.deptId); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/dict/data.vue b/ruoyi-ui/src/views/system/dict/data.vue new file mode 100644 index 0000000..0b5a8f3 --- /dev/null +++ b/ruoyi-ui/src/views/system/dict/data.vue @@ -0,0 +1,402 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="字典名称" prop="dictType"> + <el-select v-model="queryParams.dictType"> + <el-option + v-for="item in typeOptions" + :key="item.dictId" + :label="item.dictName" + :value="item.dictType" + /> + </el-select> + </el-form-item> + <el-form-item label="字典标签" prop="dictLabel"> + <el-input + v-model="queryParams.dictLabel" + placeholder="请输入字典标签" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="数据状态" clearable> + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:dict:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:dict:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:dict:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:dict:export']" + >导出</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-close" + size="mini" + @click="handleClose" + >关闭</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="字典编码" align="center" prop="dictCode" /> + <el-table-column label="字典标签" align="center" prop="dictLabel"> + <template slot-scope="scope"> + <span v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span> + <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag> + </template> + </el-table-column> + <el-table-column label="字典键值" align="center" prop="dictValue" /> + <el-table-column label="字典排序" align="center" prop="dictSort" /> + <el-table-column label="状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:dict:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:dict:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改参数配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="字典类型"> + <el-input v-model="form.dictType" :disabled="true" /> + </el-form-item> + <el-form-item label="数据标签" prop="dictLabel"> + <el-input v-model="form.dictLabel" placeholder="请输入数据标签" /> + </el-form-item> + <el-form-item label="数据键值" prop="dictValue"> + <el-input v-model="form.dictValue" placeholder="请输入数据键值" /> + </el-form-item> + <el-form-item label="样式属性" prop="cssClass"> + <el-input v-model="form.cssClass" placeholder="请输入样式属性" /> + </el-form-item> + <el-form-item label="显示排序" prop="dictSort"> + <el-input-number v-model="form.dictSort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="回显样式" prop="listClass"> + <el-select v-model="form.listClass"> + <el-option + v-for="item in listClassOptions" + :key="item.value" + :label="item.label + '(' + item.value + ')'" + :value="item.value" + ></el-option> + </el-select> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data"; +import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type"; + +export default { + name: "Data", + dicts: ['sys_normal_disable'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 字典表格数据 + dataList: [], + // 默认字典类型 + defaultDictType: "", + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 数据标签回显样式 + listClassOptions: [ + { + value: "default", + label: "默认" + }, + { + value: "primary", + label: "主要" + }, + { + value: "success", + label: "成功" + }, + { + value: "info", + label: "信息" + }, + { + value: "warning", + label: "警告" + }, + { + value: "danger", + label: "危险" + } + ], + // 类型数据字典 + typeOptions: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + dictName: undefined, + dictType: undefined, + status: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + dictLabel: [ + { required: true, message: "数据标签不能为空", trigger: "blur" } + ], + dictValue: [ + { required: true, message: "数据键值不能为空", trigger: "blur" } + ], + dictSort: [ + { required: true, message: "数据顺序不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + const dictId = this.$route.params && this.$route.params.dictId; + this.getType(dictId); + this.getTypeList(); + }, + methods: { + /** 查询字典类型详细 */ + getType(dictId) { + getType(dictId).then(response => { + this.queryParams.dictType = response.data.dictType; + this.defaultDictType = response.data.dictType; + this.getList(); + }); + }, + /** 查询字典类型列表 */ + getTypeList() { + getDictOptionselect().then(response => { + this.typeOptions = response.data; + }); + }, + /** 查询字典数据列表 */ + getList() { + this.loading = true; + listData(this.queryParams).then(response => { + this.dataList = response.rows; + this.total = response.total; + this.loading = false; + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + dictCode: undefined, + dictLabel: undefined, + dictValue: undefined, + cssClass: undefined, + listClass: 'default', + dictSort: 0, + status: "0", + remark: undefined + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 返回按钮操作 */ + handleClose() { + const obj = { path: "/system/dict" }; + this.$tab.closeOpenPage(obj); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.queryParams.dictType = this.defaultDictType; + this.handleQuery(); + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加字典数据"; + this.form.dictType = this.queryParams.dictType; + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.dictCode) + this.single = selection.length!=1 + this.multiple = !selection.length + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const dictCode = row.dictCode || this.ids + getData(dictCode).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改字典数据"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.dictCode != undefined) { + updateData(this.form).then(response => { + this.$store.dispatch('dict/removeDict', this.queryParams.dictType); + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addData(this.form).then(response => { + this.$store.dispatch('dict/removeDict', this.queryParams.dictType); + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const dictCodes = row.dictCode || this.ids; + this.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?').then(function() { + return delData(dictCodes); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + this.$store.dispatch('dict/removeDict', this.queryParams.dictType); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('system/dict/data/export', { + ...this.queryParams + }, `data_${new Date().getTime()}.xlsx`) + } + } +}; +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/dict/index.vue b/ruoyi-ui/src/views/system/dict/index.vue new file mode 100644 index 0000000..6ca5457 --- /dev/null +++ b/ruoyi-ui/src/views/system/dict/index.vue @@ -0,0 +1,347 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="字典名称" prop="dictName"> + <el-input + v-model="queryParams.dictName" + placeholder="请输入字典名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="字典类型" prop="dictType"> + <el-input + v-model="queryParams.dictType" + placeholder="请输入字典类型" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="字典状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:dict:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:dict:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:dict:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:dict:export']" + >导出</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-refresh" + size="mini" + @click="handleRefreshCache" + v-hasPermi="['system:dict:remove']" + >刷新缓存</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="字典编号" align="center" prop="dictId" /> + <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" /> + <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true"> + <template slot-scope="scope"> + <router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type"> + <span>{{ scope.row.dictType }}</span> + </router-link> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:dict:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:dict:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改参数配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="字典名称" prop="dictName"> + <el-input v-model="form.dictName" placeholder="请输入字典名称" /> + </el-form-item> + <el-form-item label="字典类型" prop="dictType"> + <el-input v-model="form.dictType" placeholder="请输入字典类型" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listType, getType, delType, addType, updateType, refreshCache } from "@/api/system/dict/type"; + +export default { + name: "Dict", + dicts: ['sys_normal_disable'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 字典表格数据 + typeList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 日期范围 + dateRange: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + dictName: undefined, + dictType: undefined, + status: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + dictName: [ + { required: true, message: "字典名称不能为空", trigger: "blur" } + ], + dictType: [ + { required: true, message: "字典类型不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询字典类型列表 */ + getList() { + this.loading = true; + listType(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.typeList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + dictId: undefined, + dictName: undefined, + dictType: undefined, + status: "0", + remark: undefined + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加字典类型"; + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.dictId) + this.single = selection.length!=1 + this.multiple = !selection.length + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const dictId = row.dictId || this.ids + getType(dictId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改字典类型"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.dictId != undefined) { + updateType(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addType(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const dictIds = row.dictId || this.ids; + this.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?').then(function() { + return delType(dictIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('system/dict/type/export', { + ...this.queryParams + }, `type_${new Date().getTime()}.xlsx`) + }, + /** 刷新缓存按钮操作 */ + handleRefreshCache() { + refreshCache().then(() => { + this.$modal.msgSuccess("刷新成功"); + this.$store.dispatch('dict/cleanDict'); + }); + } + } +}; +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/menu/index.vue b/ruoyi-ui/src/views/system/menu/index.vue new file mode 100644 index 0000000..c703fa0 --- /dev/null +++ b/ruoyi-ui/src/views/system/menu/index.vue @@ -0,0 +1,452 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"> + <el-form-item label="菜单名称" prop="menuName"> + <el-input + v-model="queryParams.menuName" + placeholder="请输入菜单名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="菜单状态" clearable> + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:menu:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-sort" + size="mini" + @click="toggleExpandAll" + >展开/折叠</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table + v-if="refreshTable" + v-loading="loading" + :data="menuList" + row-key="menuId" + :default-expand-all="isExpandAll" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + > + <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column> + <el-table-column prop="icon" label="图标" align="center" width="100"> + <template slot-scope="scope"> + <svg-icon :icon-class="scope.row.icon" /> + </template> + </el-table-column> + <el-table-column prop="orderNum" label="排序" width="60"></el-table-column> + <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column> + <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column> + <el-table-column prop="status" label="状态" width="80"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:menu:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-plus" + @click="handleAdd(scope.row)" + v-hasPermi="['system:menu:add']" + >新增</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:menu:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <!-- 添加或修改菜单对话框 --> + <el-dialog :title="title" :visible.sync="open" width="680px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="100px"> + <el-row> + <el-col :span="24"> + <el-form-item label="上级菜单" prop="parentId"> + <treeselect + v-model="form.parentId" + :options="menuOptions" + :normalizer="normalizer" + :show-count="true" + placeholder="选择上级菜单" + /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="菜单类型" prop="menuType"> + <el-radio-group v-model="form.menuType"> + <el-radio label="M">目录</el-radio> + <el-radio label="C">菜单</el-radio> + <el-radio label="F">按钮</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="24" v-if="form.menuType != 'F'"> + <el-form-item label="菜单图标" prop="icon"> + <el-popover + placement="bottom-start" + width="460" + trigger="click" + @show="$refs['iconSelect'].reset()" + > + <IconSelect ref="iconSelect" @selected="selected" :active-icon="form.icon" /> + <el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly> + <svg-icon + v-if="form.icon" + slot="prefix" + :icon-class="form.icon" + style="width: 25px;" + /> + <i v-else slot="prefix" class="el-icon-search el-input__icon" /> + </el-input> + </el-popover> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="菜单名称" prop="menuName"> + <el-input v-model="form.menuName" placeholder="请输入菜单名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="显示排序" prop="orderNum"> + <el-input-number v-model="form.orderNum" controls-position="right" :min="0" /> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType != 'F'"> + <el-form-item prop="isFrame"> + <span slot="label"> + <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 是否外链 + </span> + <el-radio-group v-model="form.isFrame"> + <el-radio label="0">是</el-radio> + <el-radio label="1">否</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType != 'F'"> + <el-form-item prop="path"> + <span slot="label"> + <el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 路由地址 + </span> + <el-input v-model="form.path" placeholder="请输入路由地址" /> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType == 'C'"> + <el-form-item prop="component"> + <span slot="label"> + <el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 组件路径 + </span> + <el-input v-model="form.component" placeholder="请输入组件路径" /> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType != 'M'"> + <el-form-item prop="perms"> + <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" /> + <span slot="label"> + <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 权限字符 + </span> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType == 'C'"> + <el-form-item prop="query"> + <el-input v-model="form.query" placeholder="请输入路由参数" maxlength="255" /> + <span slot="label"> + <el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 路由参数 + </span> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType == 'C'"> + <el-form-item prop="isCache"> + <span slot="label"> + <el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 是否缓存 + </span> + <el-radio-group v-model="form.isCache"> + <el-radio label="0">缓存</el-radio> + <el-radio label="1">不缓存</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="12" v-if="form.menuType != 'F'"> + <el-form-item prop="visible"> + <span slot="label"> + <el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 显示状态 + </span> + <el-radio-group v-model="form.visible"> + <el-radio + v-for="dict in dict.type.sys_show_hide" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item prop="status"> + <span slot="label"> + <el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 菜单状态 + </span> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listMenu, getMenu, delMenu, addMenu, updateMenu } from "@/api/system/menu"; +import Treeselect from "@riophae/vue-treeselect"; +import "@riophae/vue-treeselect/dist/vue-treeselect.css"; +import IconSelect from "@/components/IconSelect"; + +export default { + name: "Menu", + dicts: ['sys_show_hide', 'sys_normal_disable'], + components: { Treeselect, IconSelect }, + data() { + return { + // 遮罩层 + loading: true, + // 显示搜索条件 + showSearch: true, + // 菜单表格树数据 + menuList: [], + // 菜单树选项 + menuOptions: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 是否展开,默认全部折叠 + isExpandAll: false, + // 重新渲染表格状态 + refreshTable: true, + // 查询参数 + queryParams: { + menuName: undefined, + visible: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + menuName: [ + { required: true, message: "菜单名称不能为空", trigger: "blur" } + ], + orderNum: [ + { required: true, message: "菜单顺序不能为空", trigger: "blur" } + ], + path: [ + { required: true, message: "路由地址不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + // 选择图标 + selected(name) { + this.form.icon = name; + }, + /** 查询菜单列表 */ + getList() { + this.loading = true; + listMenu(this.queryParams).then(response => { + this.menuList = this.handleTree(response.data, "menuId"); + this.loading = false; + }); + }, + /** 转换菜单数据结构 */ + normalizer(node) { + if (node.children && !node.children.length) { + delete node.children; + } + return { + id: node.menuId, + label: node.menuName, + children: node.children + }; + }, + /** 查询菜单下拉树结构 */ + getTreeselect() { + listMenu().then(response => { + this.menuOptions = []; + const menu = { menuId: 0, menuName: '主类目', children: [] }; + menu.children = this.handleTree(response.data, "menuId"); + this.menuOptions.push(menu); + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + menuId: undefined, + parentId: 0, + menuName: undefined, + icon: undefined, + menuType: "M", + orderNum: undefined, + isFrame: "1", + isCache: "0", + visible: "0", + status: "0" + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 新增按钮操作 */ + handleAdd(row) { + this.reset(); + this.getTreeselect(); + if (row != null && row.menuId) { + this.form.parentId = row.menuId; + } else { + this.form.parentId = 0; + } + this.open = true; + this.title = "添加菜单"; + }, + /** 展开/折叠操作 */ + toggleExpandAll() { + this.refreshTable = false; + this.isExpandAll = !this.isExpandAll; + this.$nextTick(() => { + this.refreshTable = true; + }); + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + this.getTreeselect(); + getMenu(row.menuId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改菜单"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.menuId != undefined) { + updateMenu(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addMenu(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + this.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?').then(function() { + return delMenu(row.menuId); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/notice/index.vue b/ruoyi-ui/src/views/system/notice/index.vue new file mode 100644 index 0000000..7982b54 --- /dev/null +++ b/ruoyi-ui/src/views/system/notice/index.vue @@ -0,0 +1,312 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="公告标题" prop="noticeTitle"> + <el-input + v-model="queryParams.noticeTitle" + placeholder="请输入公告标题" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="操作人员" prop="createBy"> + <el-input + v-model="queryParams.createBy" + placeholder="请输入操作人员" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="类型" prop="noticeType"> + <el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable> + <el-option + v-for="dict in dict.type.sys_notice_type" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:notice:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:notice:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:notice:remove']" + >删除</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="序号" align="center" prop="noticeId" width="100" /> + <el-table-column + label="公告标题" + align="center" + prop="noticeTitle" + :show-overflow-tooltip="true" + /> + <el-table-column label="公告类型" align="center" prop="noticeType" width="100"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_notice_type" :value="scope.row.noticeType"/> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="status" width="100"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="创建者" align="center" prop="createBy" width="100" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="100"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:notice:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:notice:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改公告对话框 --> + <el-dialog :title="title" :visible.sync="open" width="780px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="公告标题" prop="noticeTitle"> + <el-input v-model="form.noticeTitle" placeholder="请输入公告标题" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="公告类型" prop="noticeType"> + <el-select v-model="form.noticeType" placeholder="请选择公告类型"> + <el-option + v-for="dict in dict.type.sys_notice_type" + :key="dict.value" + :label="dict.label" + :value="dict.value" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_notice_status" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="内容"> + <editor v-model="form.noticeContent" :min-height="192"/> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice"; + +export default { + name: "Notice", + dicts: ['sys_notice_status', 'sys_notice_type'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 公告表格数据 + noticeList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + noticeTitle: undefined, + createBy: undefined, + status: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + noticeTitle: [ + { required: true, message: "公告标题不能为空", trigger: "blur" } + ], + noticeType: [ + { required: true, message: "公告类型不能为空", trigger: "change" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询公告列表 */ + getList() { + this.loading = true; + listNotice(this.queryParams).then(response => { + this.noticeList = response.rows; + this.total = response.total; + this.loading = false; + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + noticeId: undefined, + noticeTitle: undefined, + noticeType: undefined, + noticeContent: undefined, + status: "0" + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.noticeId) + this.single = selection.length!=1 + this.multiple = !selection.length + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加公告"; + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const noticeId = row.noticeId || this.ids + getNotice(noticeId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改公告"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.noticeId != undefined) { + updateNotice(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addNotice(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const noticeIds = row.noticeId || this.ids + this.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?').then(function() { + return delNotice(noticeIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/post/index.vue b/ruoyi-ui/src/views/system/post/index.vue new file mode 100644 index 0000000..444bf63 --- /dev/null +++ b/ruoyi-ui/src/views/system/post/index.vue @@ -0,0 +1,309 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="岗位编码" prop="postCode"> + <el-input + v-model="queryParams.postCode" + placeholder="请输入岗位编码" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="岗位名称" prop="postName"> + <el-input + v-model="queryParams.postName" + placeholder="请输入岗位名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="岗位状态" clearable> + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:post:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:post:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:post:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:post:export']" + >导出</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="岗位编号" align="center" prop="postId" /> + <el-table-column label="岗位编码" align="center" prop="postCode" /> + <el-table-column label="岗位名称" align="center" prop="postName" /> + <el-table-column label="岗位排序" align="center" prop="postSort" /> + <el-table-column label="状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:post:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:post:remove']" + >删除</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改岗位对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="岗位名称" prop="postName"> + <el-input v-model="form.postName" placeholder="请输入岗位名称" /> + </el-form-item> + <el-form-item label="岗位编码" prop="postCode"> + <el-input v-model="form.postCode" placeholder="请输入编码名称" /> + </el-form-item> + <el-form-item label="岗位顺序" prop="postSort"> + <el-input-number v-model="form.postSort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="岗位状态" prop="status"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listPost, getPost, delPost, addPost, updatePost } from "@/api/system/post"; + +export default { + name: "Post", + dicts: ['sys_normal_disable'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 岗位表格数据 + postList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + postCode: undefined, + postName: undefined, + status: undefined + }, + // 表单参数 + form: {}, + // 表单校验 + rules: { + postName: [ + { required: true, message: "岗位名称不能为空", trigger: "blur" } + ], + postCode: [ + { required: true, message: "岗位编码不能为空", trigger: "blur" } + ], + postSort: [ + { required: true, message: "岗位顺序不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询岗位列表 */ + getList() { + this.loading = true; + listPost(this.queryParams).then(response => { + this.postList = response.rows; + this.total = response.total; + this.loading = false; + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + postId: undefined, + postCode: undefined, + postName: undefined, + postSort: 0, + status: "0", + remark: undefined + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.postId) + this.single = selection.length!=1 + this.multiple = !selection.length + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.open = true; + this.title = "添加岗位"; + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const postId = row.postId || this.ids + getPost(postId).then(response => { + this.form = response.data; + this.open = true; + this.title = "修改岗位"; + }); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.postId != undefined) { + updatePost(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addPost(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const postIds = row.postId || this.ids; + this.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?').then(function() { + return delPost(postIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('system/post/export', { + ...this.queryParams + }, `post_${new Date().getTime()}.xlsx`) + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/role/authUser.vue b/ruoyi-ui/src/views/system/role/authUser.vue new file mode 100644 index 0000000..147aa33 --- /dev/null +++ b/ruoyi-ui/src/views/system/role/authUser.vue @@ -0,0 +1,199 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"> + <el-form-item label="用户名称" prop="userName"> + <el-input + v-model="queryParams.userName" + placeholder="请输入用户名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input + v-model="queryParams.phonenumber" + placeholder="请输入手机号码" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="openSelectUser" + v-hasPermi="['system:role:add']" + >添加用户</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-circle-close" + size="mini" + :disabled="multiple" + @click="cancelAuthUserAll" + v-hasPermi="['system:role:remove']" + >批量取消授权</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-close" + size="mini" + @click="handleClose" + >关闭</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" /> + <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" /> + <el-table-column label="状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-circle-close" + @click="cancelAuthUser(scope.row)" + v-hasPermi="['system:role:remove']" + >取消授权</el-button> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + <select-user ref="select" :roleId="queryParams.roleId" @ok="handleQuery" /> + </div> +</template> + +<script> +import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role"; +import selectUser from "./selectUser"; + +export default { + name: "AuthUser", + dicts: ['sys_normal_disable'], + components: { selectUser }, + data() { + return { + // 遮罩层 + loading: true, + // 选中用户组 + userIds: [], + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 用户表格数据 + userList: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + roleId: undefined, + userName: undefined, + phonenumber: undefined + } + }; + }, + created() { + const roleId = this.$route.params && this.$route.params.roleId; + if (roleId) { + this.queryParams.roleId = roleId; + this.getList(); + } + }, + methods: { + /** 查询授权用户列表 */ + getList() { + this.loading = true; + allocatedUserList(this.queryParams).then(response => { + this.userList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + // 返回按钮 + handleClose() { + const obj = { path: "/system/role" }; + this.$tab.closeOpenPage(obj); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.userIds = selection.map(item => item.userId) + this.multiple = !selection.length + }, + /** 打开授权用户表弹窗 */ + openSelectUser() { + this.$refs.select.show(); + }, + /** 取消授权按钮操作 */ + cancelAuthUser(row) { + const roleId = this.queryParams.roleId; + this.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?').then(function() { + return authUserCancel({ userId: row.userId, roleId: roleId }); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("取消授权成功"); + }).catch(() => {}); + }, + /** 批量取消授权按钮操作 */ + cancelAuthUserAll(row) { + const roleId = this.queryParams.roleId; + const userIds = this.userIds.join(","); + this.$modal.confirm('是否取消选中用户授权数据项?').then(function() { + return authUserCancelAll({ roleId: roleId, userIds: userIds }); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("取消授权成功"); + }).catch(() => {}); + } + } +}; +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/role/index.vue b/ruoyi-ui/src/views/system/role/index.vue new file mode 100644 index 0000000..fb3b5ef --- /dev/null +++ b/ruoyi-ui/src/views/system/role/index.vue @@ -0,0 +1,605 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"> + <el-form-item label="角色名称" prop="roleName"> + <el-input + v-model="queryParams.roleName" + placeholder="请输入角色名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="权限字符" prop="roleKey"> + <el-input + v-model="queryParams.roleKey" + placeholder="请输入权限字符" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="角色状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:role:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:role:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:role:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:role:export']" + >导出</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="角色编号" prop="roleId" width="120" /> + <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" /> + <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" /> + <el-table-column label="显示顺序" prop="roleSort" width="100" /> + <el-table-column label="状态" align="center" width="100"> + <template slot-scope="scope"> + <el-switch + v-model="scope.row.status" + active-value="0" + inactive-value="1" + @change="handleStatusChange(scope.row)" + ></el-switch> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope" v-if="scope.row.roleId !== 1"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:role:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:role:remove']" + >删除</el-button> + <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:role:edit']"> + <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item command="handleDataScope" icon="el-icon-circle-check" + v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item> + <el-dropdown-item command="handleAuthUser" icon="el-icon-user" + v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 添加或修改角色配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="100px"> + <el-form-item label="角色名称" prop="roleName"> + <el-input v-model="form.roleName" placeholder="请输入角色名称" /> + </el-form-item> + <el-form-item prop="roleKey"> + <span slot="label"> + <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + 权限字符 + </span> + <el-input v-model="form.roleKey" placeholder="请输入权限字符" /> + </el-form-item> + <el-form-item label="角色顺序" prop="roleSort"> + <el-input-number v-model="form.roleSort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="菜单权限"> + <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox> + <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox> + <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox> + <el-tree + class="tree-border" + :data="menuOptions" + show-checkbox + ref="menu" + node-key="id" + :check-strictly="!form.menuCheckStrictly" + empty-text="加载中,请稍候" + :props="defaultProps" + ></el-tree> + </el-form-item> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + + <!-- 分配角色数据权限对话框 --> + <el-dialog :title="title" :visible.sync="openDataScope" width="500px" append-to-body> + <el-form :model="form" label-width="80px"> + <el-form-item label="角色名称"> + <el-input v-model="form.roleName" :disabled="true" /> + </el-form-item> + <el-form-item label="权限字符"> + <el-input v-model="form.roleKey" :disabled="true" /> + </el-form-item> + <el-form-item label="权限范围"> + <el-select v-model="form.dataScope" @change="dataScopeSelectChange"> + <el-option + v-for="item in dataScopeOptions" + :key="item.value" + :label="item.label" + :value="item.value" + ></el-option> + </el-select> + </el-form-item> + <el-form-item label="数据权限" v-show="form.dataScope == 2"> + <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox> + <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox> + <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox> + <el-tree + class="tree-border" + :data="deptOptions" + show-checkbox + default-expand-all + ref="dept" + node-key="id" + :check-strictly="!form.deptCheckStrictly" + empty-text="加载中,请稍候" + :props="defaultProps" + ></el-tree> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitDataScope">确 定</el-button> + <el-button @click="cancelDataScope">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listRole, getRole, delRole, addRole, updateRole, dataScope, changeRoleStatus, deptTreeSelect } from "@/api/system/role"; +import { treeselect as menuTreeselect, roleMenuTreeselect } from "@/api/system/menu"; + +export default { + name: "Role", + dicts: ['sys_normal_disable'], + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 角色表格数据 + roleList: [], + // 弹出层标题 + title: "", + // 是否显示弹出层 + open: false, + // 是否显示弹出层(数据权限) + openDataScope: false, + menuExpand: false, + menuNodeAll: false, + deptExpand: true, + deptNodeAll: false, + // 日期范围 + dateRange: [], + // 数据范围选项 + dataScopeOptions: [ + { + value: "1", + label: "全部数据权限" + }, + { + value: "2", + label: "自定数据权限" + }, + { + value: "3", + label: "本部门数据权限" + }, + { + value: "4", + label: "本部门及以下数据权限" + }, + { + value: "5", + label: "仅本人数据权限" + } + ], + // 菜单列表 + menuOptions: [], + // 部门列表 + deptOptions: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + roleName: undefined, + roleKey: undefined, + status: undefined + }, + // 表单参数 + form: {}, + defaultProps: { + children: "children", + label: "label" + }, + // 表单校验 + rules: { + roleName: [ + { required: true, message: "角色名称不能为空", trigger: "blur" } + ], + roleKey: [ + { required: true, message: "权限字符不能为空", trigger: "blur" } + ], + roleSort: [ + { required: true, message: "角色顺序不能为空", trigger: "blur" } + ] + } + }; + }, + created() { + this.getList(); + }, + methods: { + /** 查询角色列表 */ + getList() { + this.loading = true; + listRole(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.roleList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + /** 查询菜单树结构 */ + getMenuTreeselect() { + menuTreeselect().then(response => { + this.menuOptions = response.data; + }); + }, + // 所有菜单节点数据 + getMenuAllCheckedKeys() { + // 目前被选中的菜单节点 + let checkedKeys = this.$refs.menu.getCheckedKeys(); + // 半选中的菜单节点 + let halfCheckedKeys = this.$refs.menu.getHalfCheckedKeys(); + checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys); + return checkedKeys; + }, + // 所有部门节点数据 + getDeptAllCheckedKeys() { + // 目前被选中的部门节点 + let checkedKeys = this.$refs.dept.getCheckedKeys(); + // 半选中的部门节点 + let halfCheckedKeys = this.$refs.dept.getHalfCheckedKeys(); + checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys); + return checkedKeys; + }, + /** 根据角色ID查询菜单树结构 */ + getRoleMenuTreeselect(roleId) { + return roleMenuTreeselect(roleId).then(response => { + this.menuOptions = response.menus; + return response; + }); + }, + /** 根据角色ID查询部门树结构 */ + getDeptTree(roleId) { + return deptTreeSelect(roleId).then(response => { + this.deptOptions = response.depts; + return response; + }); + }, + // 角色状态修改 + handleStatusChange(row) { + let text = row.status === "0" ? "启用" : "停用"; + this.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?').then(function() { + return changeRoleStatus(row.roleId, row.status); + }).then(() => { + this.$modal.msgSuccess(text + "成功"); + }).catch(function() { + row.status = row.status === "0" ? "1" : "0"; + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 取消按钮(数据权限) + cancelDataScope() { + this.openDataScope = false; + this.reset(); + }, + // 表单重置 + reset() { + if (this.$refs.menu != undefined) { + this.$refs.menu.setCheckedKeys([]); + } + this.menuExpand = false, + this.menuNodeAll = false, + this.deptExpand = true, + this.deptNodeAll = false, + this.form = { + roleId: undefined, + roleName: undefined, + roleKey: undefined, + roleSort: 0, + status: "0", + menuIds: [], + deptIds: [], + menuCheckStrictly: true, + deptCheckStrictly: true, + remark: undefined + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.roleId) + this.single = selection.length!=1 + this.multiple = !selection.length + }, + // 更多操作触发 + handleCommand(command, row) { + switch (command) { + case "handleDataScope": + this.handleDataScope(row); + break; + case "handleAuthUser": + this.handleAuthUser(row); + break; + default: + break; + } + }, + // 树权限(展开/折叠) + handleCheckedTreeExpand(value, type) { + if (type == 'menu') { + let treeList = this.menuOptions; + for (let i = 0; i < treeList.length; i++) { + this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value; + } + } else if (type == 'dept') { + let treeList = this.deptOptions; + for (let i = 0; i < treeList.length; i++) { + this.$refs.dept.store.nodesMap[treeList[i].id].expanded = value; + } + } + }, + // 树权限(全选/全不选) + handleCheckedTreeNodeAll(value, type) { + if (type == 'menu') { + this.$refs.menu.setCheckedNodes(value ? this.menuOptions: []); + } else if (type == 'dept') { + this.$refs.dept.setCheckedNodes(value ? this.deptOptions: []); + } + }, + // 树权限(父子联动) + handleCheckedTreeConnect(value, type) { + if (type == 'menu') { + this.form.menuCheckStrictly = value ? true: false; + } else if (type == 'dept') { + this.form.deptCheckStrictly = value ? true: false; + } + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + this.getMenuTreeselect(); + this.open = true; + this.title = "添加角色"; + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const roleId = row.roleId || this.ids + const roleMenu = this.getRoleMenuTreeselect(roleId); + getRole(roleId).then(response => { + this.form = response.data; + this.open = true; + this.$nextTick(() => { + roleMenu.then(res => { + let checkedKeys = res.checkedKeys + checkedKeys.forEach((v) => { + this.$nextTick(()=>{ + this.$refs.menu.setChecked(v, true ,false); + }) + }) + }); + }); + this.title = "修改角色"; + }); + }, + /** 选择角色权限范围触发 */ + dataScopeSelectChange(value) { + if(value !== '2') { + this.$refs.dept.setCheckedKeys([]); + } + }, + /** 分配数据权限操作 */ + handleDataScope(row) { + this.reset(); + const deptTreeSelect = this.getDeptTree(row.roleId); + getRole(row.roleId).then(response => { + this.form = response.data; + this.openDataScope = true; + this.$nextTick(() => { + deptTreeSelect.then(res => { + this.$refs.dept.setCheckedKeys(res.checkedKeys); + }); + }); + this.title = "分配数据权限"; + }); + }, + /** 分配用户操作 */ + handleAuthUser: function(row) { + const roleId = row.roleId; + this.$router.push("/system/role-auth/user/" + roleId); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.roleId != undefined) { + this.form.menuIds = this.getMenuAllCheckedKeys(); + updateRole(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + this.form.menuIds = this.getMenuAllCheckedKeys(); + addRole(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 提交按钮(数据权限) */ + submitDataScope: function() { + if (this.form.roleId != undefined) { + this.form.deptIds = this.getDeptAllCheckedKeys(); + dataScope(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.openDataScope = false; + this.getList(); + }); + } + }, + /** 删除按钮操作 */ + handleDelete(row) { + const roleIds = row.roleId || this.ids; + this.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function() { + return delRole(roleIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('system/role/export', { + ...this.queryParams + }, `role_${new Date().getTime()}.xlsx`) + } + } +}; +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/role/selectUser.vue b/ruoyi-ui/src/views/system/role/selectUser.vue new file mode 100644 index 0000000..b2b072f --- /dev/null +++ b/ruoyi-ui/src/views/system/role/selectUser.vue @@ -0,0 +1,138 @@ +<template> + <!-- 授权用户 --> + <el-dialog title="选择用户" :visible.sync="visible" width="800px" top="5vh" append-to-body> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true"> + <el-form-item label="用户名称" prop="userName"> + <el-input + v-model="queryParams.userName" + placeholder="请输入用户名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input + v-model="queryParams.phonenumber" + placeholder="请输入手机号码" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + <el-row> + <el-table @row-click="clickRow" ref="table" :data="userList" @selection-change="handleSelectionChange" height="260px"> + <el-table-column type="selection" width="55"></el-table-column> + <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" /> + <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" /> + <el-table-column label="状态" align="center" prop="status"> + <template slot-scope="scope"> + <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </el-row> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="handleSelectUser">确 定</el-button> + <el-button @click="visible = false">取 消</el-button> + </div> + </el-dialog> +</template> + +<script> +import { unallocatedUserList, authUserSelectAll } from "@/api/system/role"; +export default { + dicts: ['sys_normal_disable'], + props: { + // 角色编号 + roleId: { + type: [Number, String] + } + }, + data() { + return { + // 遮罩层 + visible: false, + // 选中数组值 + userIds: [], + // 总条数 + total: 0, + // 未授权用户数据 + userList: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + roleId: undefined, + userName: undefined, + phonenumber: undefined + } + }; + }, + methods: { + // 显示弹框 + show() { + this.queryParams.roleId = this.roleId; + this.getList(); + this.visible = true; + }, + clickRow(row) { + this.$refs.table.toggleRowSelection(row); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.userIds = selection.map(item => item.userId); + }, + // 查询表数据 + getList() { + unallocatedUserList(this.queryParams).then(res => { + this.userList = res.rows; + this.total = res.total; + }); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 选择授权用户操作 */ + handleSelectUser() { + const roleId = this.queryParams.roleId; + const userIds = this.userIds.join(","); + if (userIds == "") { + this.$modal.msgError("请选择要分配的用户"); + return; + } + authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => { + this.$modal.msgSuccess(res.msg); + if (res.code === 200) { + this.visible = false; + this.$emit("ok"); + } + }); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/user/authRole.vue b/ruoyi-ui/src/views/system/user/authRole.vue new file mode 100644 index 0000000..ab5e72f --- /dev/null +++ b/ruoyi-ui/src/views/system/user/authRole.vue @@ -0,0 +1,117 @@ +<template> + <div class="app-container"> + <h4 class="form-header h4">基本信息</h4> + <el-form ref="form" :model="form" label-width="80px"> + <el-row> + <el-col :span="8" :offset="2"> + <el-form-item label="用户昵称" prop="nickName"> + <el-input v-model="form.nickName" disabled /> + </el-form-item> + </el-col> + <el-col :span="8" :offset="2"> + <el-form-item label="登录账号" prop="userName"> + <el-input v-model="form.userName" disabled /> + </el-form-item> + </el-col> + </el-row> + </el-form> + + <h4 class="form-header h4">角色信息</h4> + <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="table" @selection-change="handleSelectionChange" :data="roles.slice((pageNum-1)*pageSize,pageNum*pageSize)"> + <el-table-column label="序号" type="index" align="center"> + <template slot-scope="scope"> + <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span> + </template> + </el-table-column> + <el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column> + <el-table-column label="角色编号" align="center" prop="roleId" /> + <el-table-column label="角色名称" align="center" prop="roleName" /> + <el-table-column label="权限字符" align="center" prop="roleKey" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + </el-table> + + <pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" /> + + <el-form label-width="100px"> + <el-form-item style="text-align: center;margin-left:-120px;margin-top:30px;"> + <el-button type="primary" @click="submitForm()">提交</el-button> + <el-button @click="close()">返回</el-button> + </el-form-item> + </el-form> + </div> +</template> + +<script> +import { getAuthRole, updateAuthRole } from "@/api/system/user"; + +export default { + name: "AuthRole", + data() { + return { + // 遮罩层 + loading: true, + // 分页信息 + total: 0, + pageNum: 1, + pageSize: 10, + // 选中角色编号 + roleIds:[], + // 角色信息 + roles: [], + // 用户信息 + form: {} + }; + }, + created() { + const userId = this.$route.params && this.$route.params.userId; + if (userId) { + this.loading = true; + getAuthRole(userId).then((response) => { + this.form = response.user; + this.roles = response.roles; + this.total = this.roles.length; + this.$nextTick(() => { + this.roles.forEach((row) => { + if (row.flag) { + this.$refs.table.toggleRowSelection(row); + } + }); + }); + this.loading = false; + }); + } + }, + methods: { + /** 单击选中行数据 */ + clickRow(row) { + this.$refs.table.toggleRowSelection(row); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.roleIds = selection.map((item) => item.roleId); + }, + // 保存选中的数据编号 + getRowKey(row) { + return row.roleId; + }, + /** 提交按钮 */ + submitForm() { + const userId = this.form.userId; + const roleIds = this.roleIds.join(","); + updateAuthRole({ userId: userId, roleIds: roleIds }).then((response) => { + this.$modal.msgSuccess("授权成功"); + this.close(); + }); + }, + /** 关闭按钮 */ + close() { + const obj = { path: "/system/user" }; + this.$tab.closeOpenPage(obj); + }, + }, +}; +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue new file mode 100644 index 0000000..ae87fe4 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/index.vue @@ -0,0 +1,670 @@ +<template> + <div class="app-container"> + <el-row :gutter="20"> + <!--部门数据--> + <el-col :span="4" :xs="24"> + <div class="head-container"> + <el-input + v-model="deptName" + placeholder="请输入部门名称" + clearable + size="small" + prefix-icon="el-icon-search" + style="margin-bottom: 20px" + /> + </div> + <div class="head-container"> + <el-tree + :data="deptOptions" + :props="defaultProps" + :expand-on-click-node="false" + :filter-node-method="filterNode" + ref="tree" + node-key="id" + default-expand-all + highlight-current + @node-click="handleNodeClick" + /> + </div> + </el-col> + <!--用户数据--> + <el-col :span="20" :xs="24"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="用户名称" prop="userName"> + <el-input + v-model="queryParams.userName" + placeholder="请输入用户名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input + v-model="queryParams.phonenumber" + placeholder="请输入手机号码" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="用户状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:user:add']" + >新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleUpdate" + v-hasPermi="['system:user:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['system:user:remove']" + >删除</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-upload2" + size="mini" + @click="handleImport" + v-hasPermi="['system:user:import']" + >导入</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + plain + icon="el-icon-download" + size="mini" + @click="handleExport" + v-hasPermi="['system:user:export']" + >导出</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="50" align="center" /> + <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" /> + <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" /> + <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" /> + <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" /> + <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" /> + <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible"> + <template slot-scope="scope"> + <el-switch + v-model="scope.row.status" + active-value="0" + inactive-value="1" + @change="handleStatusChange(scope.row)" + ></el-switch> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160"> + <template slot-scope="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + width="160" + class-name="small-padding fixed-width" + > + <template slot-scope="scope" v-if="scope.row.userId !== 1"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:user:edit']" + >修改</el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['system:user:remove']" + >删除</el-button> + <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:user:resetPwd', 'system:user:edit']"> + <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item command="handleResetPwd" icon="el-icon-key" + v-hasPermi="['system:user:resetPwd']">重置密码</el-dropdown-item> + <el-dropdown-item command="handleAuthRole" icon="el-icon-circle-check" + v-hasPermi="['system:user:edit']">分配角色</el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </el-col> + </el-row> + + <!-- 添加或修改用户配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickName"> + <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <treeselect v-model="form.deptId" :options="deptOptions" :show-count="true" placeholder="请选择归属部门" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName"> + <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password"> + <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="form.sex" placeholder="请选择性别"> + <el-option + v-for="dict in dict.type.sys_user_sex" + :key="dict.value" + :label="dict.label" + :value="dict.value" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in dict.type.sys_normal_disable" + :key="dict.value" + :label="dict.value" + >{{dict.label}}</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="form.postIds" multiple placeholder="请选择岗位"> + <el-option + v-for="item in postOptions" + :key="item.postId" + :label="item.postName" + :value="item.postId" + :disabled="item.status == 1" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="角色"> + <el-select v-model="form.roleIds" multiple placeholder="请选择角色"> + <el-option + v-for="item in roleOptions" + :key="item.roleId" + :label="item.roleName" + :value="item.roleId" + :disabled="item.status == 1" + ></el-option> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + + <!-- 用户导入对话框 --> + <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> + <el-upload + ref="upload" + :limit="1" + accept=".xlsx, .xls" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :auto-upload="false" + drag + > + <i class="el-icon-upload"></i> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <div class="el-upload__tip text-center" slot="tip"> + <div class="el-upload__tip" slot="tip"> + <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 + </div> + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link> + </div> + </el-upload> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="upload.open = false">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus, deptTreeSelect } from "@/api/system/user"; +import { getToken } from "@/utils/auth"; +import Treeselect from "@riophae/vue-treeselect"; +import "@riophae/vue-treeselect/dist/vue-treeselect.css"; + +export default { + name: "User", + dicts: ['sys_normal_disable', 'sys_user_sex'], + components: { Treeselect }, + data() { + return { + // 遮罩层 + loading: true, + // 选中数组 + ids: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 用户表格数据 + userList: null, + // 弹出层标题 + title: "", + // 部门树选项 + deptOptions: undefined, + // 是否显示弹出层 + open: false, + // 部门名称 + deptName: undefined, + // 默认密码 + initPassword: undefined, + // 日期范围 + dateRange: [], + // 岗位选项 + postOptions: [], + // 角色选项 + roleOptions: [], + // 表单参数 + form: {}, + defaultProps: { + children: "children", + label: "label" + }, + // 用户导入参数 + upload: { + // 是否显示弹出层(用户导入) + open: false, + // 弹出层标题(用户导入) + title: "", + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: { Authorization: "Bearer " + getToken() }, + // 上传的地址 + url: process.env.VUE_APP_BASE_API + "/system/user/importData" + }, + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + userName: undefined, + phonenumber: undefined, + status: undefined, + deptId: undefined + }, + // 列信息 + columns: [ + { key: 0, label: `用户编号`, visible: true }, + { key: 1, label: `用户名称`, visible: true }, + { key: 2, label: `用户昵称`, visible: true }, + { key: 3, label: `部门`, visible: true }, + { key: 4, label: `手机号码`, visible: true }, + { key: 5, label: `状态`, visible: true }, + { key: 6, label: `创建时间`, visible: true } + ], + // 表单校验 + rules: { + userName: [ + { required: true, message: "用户名称不能为空", trigger: "blur" }, + { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' } + ], + nickName: [ + { required: true, message: "用户昵称不能为空", trigger: "blur" } + ], + password: [ + { required: true, message: "用户密码不能为空", trigger: "blur" }, + { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' } + ], + email: [ + { + type: "email", + message: "请输入正确的邮箱地址", + trigger: ["blur", "change"] + } + ], + phonenumber: [ + { + pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, + message: "请输入正确的手机号码", + trigger: "blur" + } + ] + } + }; + }, + watch: { + // 根据名称筛选部门树 + deptName(val) { + this.$refs.tree.filter(val); + } + }, + created() { + this.getList(); + this.getDeptTree(); + this.getConfigKey("sys.user.initPassword").then(response => { + this.initPassword = response.msg; + }); + }, + methods: { + /** 查询用户列表 */ + getList() { + this.loading = true; + listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.userList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + /** 查询部门下拉树结构 */ + getDeptTree() { + deptTreeSelect().then(response => { + this.deptOptions = response.data; + }); + }, + // 筛选节点 + filterNode(value, data) { + if (!value) return true; + return data.label.indexOf(value) !== -1; + }, + // 节点单击事件 + handleNodeClick(data) { + this.queryParams.deptId = data.id; + this.handleQuery(); + }, + // 用户状态修改 + handleStatusChange(row) { + let text = row.status === "0" ? "启用" : "停用"; + this.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?').then(function() { + return changeUserStatus(row.userId, row.status); + }).then(() => { + this.$modal.msgSuccess(text + "成功"); + }).catch(function() { + row.status = row.status === "0" ? "1" : "0"; + }); + }, + // 取消按钮 + cancel() { + this.open = false; + this.reset(); + }, + // 表单重置 + reset() { + this.form = { + userId: undefined, + deptId: undefined, + userName: undefined, + nickName: undefined, + password: undefined, + phonenumber: undefined, + email: undefined, + sex: undefined, + status: "0", + remark: undefined, + postIds: [], + roleIds: [] + }; + this.resetForm("form"); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.queryParams.deptId = undefined; + this.$refs.tree.setCurrentKey(null); + this.handleQuery(); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.userId); + this.single = selection.length != 1; + this.multiple = !selection.length; + }, + // 更多操作触发 + handleCommand(command, row) { + switch (command) { + case "handleResetPwd": + this.handleResetPwd(row); + break; + case "handleAuthRole": + this.handleAuthRole(row); + break; + default: + break; + } + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset(); + getUser().then(response => { + this.postOptions = response.posts; + this.roleOptions = response.roles; + this.open = true; + this.title = "添加用户"; + this.form.password = this.initPassword; + }); + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset(); + const userId = row.userId || this.ids; + getUser(userId).then(response => { + this.form = response.data; + this.postOptions = response.posts; + this.roleOptions = response.roles; + this.$set(this.form, "postIds", response.postIds); + this.$set(this.form, "roleIds", response.roleIds); + this.open = true; + this.title = "修改用户"; + this.form.password = ""; + }); + }, + /** 重置密码按钮操作 */ + handleResetPwd(row) { + this.$prompt('请输入"' + row.userName + '"的新密码', "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + closeOnClickModal: false, + inputPattern: /^.{5,20}$/, + inputErrorMessage: "用户密码长度必须介于 5 和 20 之间" + }).then(({ value }) => { + resetUserPwd(row.userId, value).then(response => { + this.$modal.msgSuccess("修改成功,新密码是:" + value); + }); + }).catch(() => {}); + }, + /** 分配角色操作 */ + handleAuthRole: function(row) { + const userId = row.userId; + this.$router.push("/system/user-auth/role/" + userId); + }, + /** 提交按钮 */ + submitForm: function() { + this.$refs["form"].validate(valid => { + if (valid) { + if (this.form.userId != undefined) { + updateUser(this.form).then(response => { + this.$modal.msgSuccess("修改成功"); + this.open = false; + this.getList(); + }); + } else { + addUser(this.form).then(response => { + this.$modal.msgSuccess("新增成功"); + this.open = false; + this.getList(); + }); + } + } + }); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const userIds = row.userId || this.ids; + this.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function() { + return delUser(userIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + }, + /** 导出按钮操作 */ + handleExport() { + this.download('system/user/export', { + ...this.queryParams + }, `user_${new Date().getTime()}.xlsx`) + }, + /** 导入按钮操作 */ + handleImport() { + this.upload.title = "用户导入"; + this.upload.open = true; + }, + /** 下载模板操作 */ + importTemplate() { + this.download('system/user/importTemplate', { + }, `user_template_${new Date().getTime()}.xlsx`) + }, + // 文件上传中处理 + handleFileUploadProgress(event, file, fileList) { + this.upload.isUploading = true; + }, + // 文件上传成功处理 + handleFileSuccess(response, file, fileList) { + this.upload.open = false; + this.upload.isUploading = false; + this.$refs.upload.clearFiles(); + this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true }); + this.getList(); + }, + // 提交上传文件 + submitFileForm() { + this.$refs.upload.submit(); + } + } +}; +</script> \ No newline at end of file diff --git a/ruoyi-ui/src/views/system/user/profile/index.vue b/ruoyi-ui/src/views/system/user/profile/index.vue new file mode 100644 index 0000000..529c564 --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/index.vue @@ -0,0 +1,91 @@ +<template> + <div class="app-container"> + <el-row :gutter="20"> + <el-col :span="6" :xs="24"> + <el-card class="box-card"> + <div slot="header" class="clearfix"> + <span>个人信息</span> + </div> + <div> + <div class="text-center"> + <userAvatar /> + </div> + <ul class="list-group list-group-striped"> + <li class="list-group-item"> + <svg-icon icon-class="user" />用户名称 + <div class="pull-right">{{ user.userName }}</div> + </li> + <li class="list-group-item"> + <svg-icon icon-class="phone" />手机号码 + <div class="pull-right">{{ user.phonenumber }}</div> + </li> + <li class="list-group-item"> + <svg-icon icon-class="email" />用户邮箱 + <div class="pull-right">{{ user.email }}</div> + </li> + <li class="list-group-item"> + <svg-icon icon-class="tree" />所属部门 + <div class="pull-right" v-if="user.dept">{{ user.dept.deptName }} / {{ postGroup }}</div> + </li> + <li class="list-group-item"> + <svg-icon icon-class="peoples" />所属角色 + <div class="pull-right">{{ roleGroup }}</div> + </li> + <li class="list-group-item"> + <svg-icon icon-class="date" />创建日期 + <div class="pull-right">{{ user.createTime }}</div> + </li> + </ul> + </div> + </el-card> + </el-col> + <el-col :span="18" :xs="24"> + <el-card> + <div slot="header" class="clearfix"> + <span>基本资料</span> + </div> + <el-tabs v-model="activeTab"> + <el-tab-pane label="基本资料" name="userinfo"> + <userInfo :user="user" /> + </el-tab-pane> + <el-tab-pane label="修改密码" name="resetPwd"> + <resetPwd /> + </el-tab-pane> + </el-tabs> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> +import userAvatar from "./userAvatar"; +import userInfo from "./userInfo"; +import resetPwd from "./resetPwd"; +import { getUserProfile } from "@/api/system/user"; + +export default { + name: "Profile", + components: { userAvatar, userInfo, resetPwd }, + data() { + return { + user: {}, + roleGroup: {}, + postGroup: {}, + activeTab: "userinfo" + }; + }, + created() { + this.getUser(); + }, + methods: { + getUser() { + getUserProfile().then(response => { + this.user = response.data; + this.roleGroup = response.roleGroup; + this.postGroup = response.postGroup; + }); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/user/profile/resetPwd.vue b/ruoyi-ui/src/views/system/user/profile/resetPwd.vue new file mode 100644 index 0000000..64e8f8c --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/resetPwd.vue @@ -0,0 +1,68 @@ +<template> + <el-form ref="form" :model="user" :rules="rules" label-width="80px"> + <el-form-item label="旧密码" prop="oldPassword"> + <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/> + </el-form-item> + <el-form-item label="新密码" prop="newPassword"> + <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/> + </el-form-item> + <el-form-item label="确认密码" prop="confirmPassword"> + <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/> + </el-form-item> + <el-form-item> + <el-button type="primary" size="mini" @click="submit">保存</el-button> + <el-button type="danger" size="mini" @click="close">关闭</el-button> + </el-form-item> + </el-form> +</template> + +<script> +import { updateUserPwd } from "@/api/system/user"; + +export default { + data() { + const equalToPassword = (rule, value, callback) => { + if (this.user.newPassword !== value) { + callback(new Error("两次输入的密码不一致")); + } else { + callback(); + } + }; + return { + user: { + oldPassword: undefined, + newPassword: undefined, + confirmPassword: undefined + }, + // 表单校验 + rules: { + oldPassword: [ + { required: true, message: "旧密码不能为空", trigger: "blur" } + ], + newPassword: [ + { required: true, message: "新密码不能为空", trigger: "blur" }, + { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" } + ], + confirmPassword: [ + { required: true, message: "确认密码不能为空", trigger: "blur" }, + { required: true, validator: equalToPassword, trigger: "blur" } + ] + } + }; + }, + methods: { + submit() { + this.$refs["form"].validate(valid => { + if (valid) { + updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => { + this.$modal.msgSuccess("修改成功"); + }); + } + }); + }, + close() { + this.$tab.closePage(); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/system/user/profile/userAvatar.vue b/ruoyi-ui/src/views/system/user/profile/userAvatar.vue new file mode 100644 index 0000000..96aa01f --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/userAvatar.vue @@ -0,0 +1,182 @@ +<template> + <div> + <div class="user-info-head" @click="editCropper()"><img v-bind:src="options.img" title="点击上传头像" class="img-circle img-lg" /></div> + <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog"> + <el-row> + <el-col :xs="24" :md="12" :style="{height: '350px'}"> + <vue-cropper + ref="cropper" + :img="options.img" + :info="true" + :autoCrop="options.autoCrop" + :autoCropWidth="options.autoCropWidth" + :autoCropHeight="options.autoCropHeight" + :fixedBox="options.fixedBox" + :outputType="options.outputType" + @realTime="realTime" + v-if="visible" + /> + </el-col> + <el-col :xs="24" :md="12" :style="{height: '350px'}"> + <div class="avatar-upload-preview"> + <img :src="previews.url" :style="previews.img" /> + </div> + </el-col> + </el-row> + <br /> + <el-row> + <el-col :lg="2" :sm="3" :xs="3"> + <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload"> + <el-button size="small"> + 选择 + <i class="el-icon-upload el-icon--right"></i> + </el-button> + </el-upload> + </el-col> + <el-col :lg="{span: 1, offset: 2}" :sm="2" :xs="2"> + <el-button icon="el-icon-plus" size="small" @click="changeScale(1)"></el-button> + </el-col> + <el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2"> + <el-button icon="el-icon-minus" size="small" @click="changeScale(-1)"></el-button> + </el-col> + <el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2"> + <el-button icon="el-icon-refresh-left" size="small" @click="rotateLeft()"></el-button> + </el-col> + <el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2"> + <el-button icon="el-icon-refresh-right" size="small" @click="rotateRight()"></el-button> + </el-col> + <el-col :lg="{span: 2, offset: 6}" :sm="2" :xs="2"> + <el-button type="primary" size="small" @click="uploadImg()">提 交</el-button> + </el-col> + </el-row> + </el-dialog> + </div> +</template> + +<script> +import store from "@/store"; +import { VueCropper } from "vue-cropper"; +import { uploadAvatar } from "@/api/system/user"; +import { debounce } from '@/utils' + +export default { + components: { VueCropper }, + data() { + return { + // 是否显示弹出层 + open: false, + // 是否显示cropper + visible: false, + // 弹出层标题 + title: "修改头像", + options: { + img: store.getters.avatar, //裁剪图片的地址 + autoCrop: true, // 是否默认生成截图框 + autoCropWidth: 200, // 默认生成截图框宽度 + autoCropHeight: 200, // 默认生成截图框高度 + fixedBox: true, // 固定截图框大小 不允许改变 + outputType:"png" // 默认生成截图为PNG格式 + }, + previews: {}, + resizeHandler: null + }; + }, + methods: { + // 编辑头像 + editCropper() { + this.open = true; + }, + // 打开弹出层结束时的回调 + modalOpened() { + this.visible = true; + if (!this.resizeHandler) { + this.resizeHandler = debounce(() => { + this.refresh() + }, 100) + } + window.addEventListener("resize", this.resizeHandler) + }, + // 刷新组件 + refresh() { + this.$refs.cropper.refresh(); + }, + // 覆盖默认的上传行为 + requestUpload() { + }, + // 向左旋转 + rotateLeft() { + this.$refs.cropper.rotateLeft(); + }, + // 向右旋转 + rotateRight() { + this.$refs.cropper.rotateRight(); + }, + // 图片缩放 + changeScale(num) { + num = num || 1; + this.$refs.cropper.changeScale(num); + }, + // 上传预处理 + beforeUpload(file) { + if (file.type.indexOf("image/") == -1) { + this.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。"); + } else { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + this.options.img = reader.result; + }; + } + }, + // 上传图片 + uploadImg() { + this.$refs.cropper.getCropBlob(data => { + let formData = new FormData(); + formData.append("avatarfile", data); + uploadAvatar(formData).then(response => { + this.open = false; + this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl; + store.commit('SET_AVATAR', this.options.img); + this.$modal.msgSuccess("修改成功"); + this.visible = false; + }); + }); + }, + // 实时预览 + realTime(data) { + this.previews = data; + }, + // 关闭窗口 + closeDialog() { + this.options.img = store.getters.avatar + this.visible = false; + window.removeEventListener("resize", this.resizeHandler) + } + } +}; +</script> +<style scoped lang="scss"> +.user-info-head { + position: relative; + display: inline-block; + height: 120px; +} + +.user-info-head:hover:after { + content: '+'; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + color: #eee; + background: rgba(0, 0, 0, 0.5); + font-size: 24px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + cursor: pointer; + line-height: 110px; + border-radius: 50%; +} +</style> diff --git a/ruoyi-ui/src/views/system/user/profile/userInfo.vue b/ruoyi-ui/src/views/system/user/profile/userInfo.vue new file mode 100644 index 0000000..c09a20b --- /dev/null +++ b/ruoyi-ui/src/views/system/user/profile/userInfo.vue @@ -0,0 +1,75 @@ +<template> + <el-form ref="form" :model="user" :rules="rules" label-width="80px"> + <el-form-item label="用户昵称" prop="nickName"> + <el-input v-model="user.nickName" maxlength="30" /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="user.phonenumber" maxlength="11" /> + </el-form-item> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="user.email" maxlength="50" /> + </el-form-item> + <el-form-item label="性别"> + <el-radio-group v-model="user.sex"> + <el-radio label="0">男</el-radio> + <el-radio label="1">女</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item> + <el-button type="primary" size="mini" @click="submit">保存</el-button> + <el-button type="danger" size="mini" @click="close">关闭</el-button> + </el-form-item> + </el-form> +</template> + +<script> +import { updateUserProfile } from "@/api/system/user"; + +export default { + props: { + user: { + type: Object + } + }, + data() { + return { + // 表单校验 + rules: { + nickName: [ + { required: true, message: "用户昵称不能为空", trigger: "blur" } + ], + email: [ + { required: true, message: "邮箱地址不能为空", trigger: "blur" }, + { + type: "email", + message: "请输入正确的邮箱地址", + trigger: ["blur", "change"] + } + ], + phonenumber: [ + { required: true, message: "手机号码不能为空", trigger: "blur" }, + { + pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, + message: "请输入正确的手机号码", + trigger: "blur" + } + ] + } + }; + }, + methods: { + submit() { + this.$refs["form"].validate(valid => { + if (valid) { + updateUserProfile(this.user).then(response => { + this.$modal.msgSuccess("修改成功"); + }); + } + }); + }, + close() { + this.$tab.closePage(); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue b/ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue new file mode 100644 index 0000000..b5c2e2e --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/CodeTypeDialog.vue @@ -0,0 +1,106 @@ +<template> + <div> + <el-dialog + v-bind="$attrs" + width="500px" + :close-on-click-modal="false" + :modal-append-to-body="false" + v-on="$listeners" + @open="onOpen" + @close="onClose" + > + <el-row :gutter="15"> + <el-form + ref="elForm" + :model="formData" + :rules="rules" + size="medium" + label-width="100px" + > + <el-col :span="24"> + <el-form-item label="生成类型" prop="type"> + <el-radio-group v-model="formData.type"> + <el-radio-button + v-for="(item, index) in typeOptions" + :key="index" + :label="item.value" + :disabled="item.disabled" + > + {{ item.label }} + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-if="showFileName" label="文件名" prop="fileName"> + <el-input v-model="formData.fileName" placeholder="请输入文件名" clearable /> + </el-form-item> + </el-col> + </el-form> + </el-row> + + <div slot="footer"> + <el-button @click="close"> + 取消 + </el-button> + <el-button type="primary" @click="handleConfirm"> + 确定 + </el-button> + </div> + </el-dialog> + </div> +</template> +<script> +export default { + inheritAttrs: false, + props: ['showFileName'], + data() { + return { + formData: { + fileName: undefined, + type: 'file' + }, + rules: { + fileName: [{ + required: true, + message: '请输入文件名', + trigger: 'blur' + }], + type: [{ + required: true, + message: '生成类型不能为空', + trigger: 'change' + }] + }, + typeOptions: [{ + label: '页面', + value: 'file' + }, { + label: '弹窗', + value: 'dialog' + }] + } + }, + computed: { + }, + watch: {}, + mounted() {}, + methods: { + onOpen() { + if (this.showFileName) { + this.formData.fileName = `${+new Date()}.vue` + } + }, + onClose() { + }, + close(e) { + this.$emit('update:visible', false) + }, + handleConfirm() { + this.$refs.elForm.validate(valid => { + if (!valid) return + this.$emit('confirm', { ...this.formData }) + this.close() + }) + } + } +} +</script> diff --git a/ruoyi-ui/src/views/tool/build/DraggableItem.vue b/ruoyi-ui/src/views/tool/build/DraggableItem.vue new file mode 100644 index 0000000..e881778 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/DraggableItem.vue @@ -0,0 +1,100 @@ +<script> +import draggable from 'vuedraggable' +import render from '@/utils/generator/render' + +const components = { + itemBtns(h, element, index, parent) { + const { copyItem, deleteItem } = this.$listeners + return [ + <span class="drawing-item-copy" title="复制" onClick={event => { + copyItem(element, parent); event.stopPropagation() + }}> + <i class="el-icon-copy-document" /> + </span>, + <span class="drawing-item-delete" title="删除" onClick={event => { + deleteItem(index, parent); event.stopPropagation() + }}> + <i class="el-icon-delete" /> + </span> + ] + } +} +const layouts = { + colFormItem(h, element, index, parent) { + const { activeItem } = this.$listeners + let className = this.activeId === element.formId ? 'drawing-item active-from-item' : 'drawing-item' + if (this.formConf.unFocusedComponentBorder) className += ' unfocus-bordered' + return ( + <el-col span={element.span} class={className} + nativeOnClick={event => { activeItem(element); event.stopPropagation() }}> + <el-form-item label-width={element.labelWidth ? `${element.labelWidth}px` : null} + label={element.label} required={element.required}> + <render key={element.renderKey} conf={element} onInput={ event => { + this.$set(element, 'defaultValue', event) + }} /> + </el-form-item> + {components.itemBtns.apply(this, arguments)} + </el-col> + ) + }, + rowFormItem(h, element, index, parent) { + const { activeItem } = this.$listeners + const className = this.activeId === element.formId ? 'drawing-row-item active-from-item' : 'drawing-row-item' + let child = renderChildren.apply(this, arguments) + if (element.type === 'flex') { + child = <el-row type={element.type} justify={element.justify} align={element.align}> + {child} + </el-row> + } + return ( + <el-col span={element.span}> + <el-row gutter={element.gutter} class={className} + nativeOnClick={event => { activeItem(element); event.stopPropagation() }}> + <span class="component-name">{element.componentName}</span> + <draggable list={element.children} animation={340} group="componentsGroup" class="drag-wrapper"> + {child} + </draggable> + {components.itemBtns.apply(this, arguments)} + </el-row> + </el-col> + ) + } +} + +function renderChildren(h, element, index, parent) { + if (!Array.isArray(element.children)) return null + return element.children.map((el, i) => { + const layout = layouts[el.layout] + if (layout) { + return layout.call(this, h, el, i, element.children) + } + return layoutIsNotFound() + }) +} + +function layoutIsNotFound() { + throw new Error(`没有与${this.element.layout}匹配的layout`) +} + +export default { + components: { + render, + draggable + }, + props: [ + 'element', + 'index', + 'drawingList', + 'activeId', + 'formConf' + ], + render(h) { + const layout = layouts[this.element.layout] + + if (layout) { + return layout.call(this, h, this.element, this.index, this.drawingList) + } + return layoutIsNotFound() + } +} +</script> diff --git a/ruoyi-ui/src/views/tool/build/IconsDialog.vue b/ruoyi-ui/src/views/tool/build/IconsDialog.vue new file mode 100644 index 0000000..958be50 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/IconsDialog.vue @@ -0,0 +1,123 @@ +<template> + <div class="icon-dialog"> + <el-dialog + v-bind="$attrs" + width="980px" + :modal-append-to-body="false" + v-on="$listeners" + @open="onOpen" + @close="onClose" + > + <div slot="title"> + 选择图标 + <el-input + v-model="key" + size="mini" + :style="{width: '260px'}" + placeholder="请输入图标名称" + prefix-icon="el-icon-search" + clearable + /> + </div> + <ul class="icon-ul"> + <li + v-for="icon in iconList" + :key="icon" + :class="active===icon?'active-item':''" + @click="onSelect(icon)" + > + <i :class="icon" /> + <div>{{ icon }}</div> + </li> + </ul> + </el-dialog> + </div> +</template> +<script> +import iconList from '@/utils/generator/icon.json' + +const originList = iconList.map(name => `el-icon-${name}`) + +export default { + inheritAttrs: false, + props: ['current'], + data() { + return { + iconList: originList, + active: null, + key: '' + } + }, + watch: { + key(val) { + if (val) { + this.iconList = originList.filter(name => name.indexOf(val) > -1) + } else { + this.iconList = originList + } + } + }, + methods: { + onOpen() { + this.active = this.current + this.key = '' + }, + onClose() {}, + onSelect(icon) { + this.active = icon + this.$emit('select', icon) + this.$emit('update:visible', false) + } + } +} +</script> +<style lang="scss" scoped> +.icon-ul { + margin: 0; + padding: 0; + font-size: 0; + li { + list-style-type: none; + text-align: center; + font-size: 14px; + display: inline-block; + width: 16.66%; + box-sizing: border-box; + height: 108px; + padding: 15px 6px 6px 6px; + cursor: pointer; + overflow: hidden; + &:hover { + background: #f2f2f2; + } + &.active-item{ + background: #e1f3fb; + color: #7a6df0 + } + > i { + font-size: 30px; + line-height: 50px; + } + } +} +.icon-dialog { + ::v-deep .el-dialog { + border-radius: 8px; + margin-bottom: 0; + margin-top: 4vh !important; + display: flex; + flex-direction: column; + max-height: 92vh; + overflow: hidden; + box-sizing: border-box; + .el-dialog__header { + padding-top: 14px; + } + .el-dialog__body { + margin: 0 20px 20px 20px; + padding: 0; + overflow: auto; + } + } +} +</style> diff --git a/ruoyi-ui/src/views/tool/build/RightPanel.vue b/ruoyi-ui/src/views/tool/build/RightPanel.vue new file mode 100644 index 0000000..c2760eb --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/RightPanel.vue @@ -0,0 +1,946 @@ +<template> + <div class="right-board"> + <el-tabs v-model="currentTab" class="center-tabs"> + <el-tab-pane label="组件属性" name="field" /> + <el-tab-pane label="表单属性" name="form" /> + </el-tabs> + <div class="field-box"> + <a class="document-link" target="_blank" :href="documentLink" title="查看组件文档"> + <i class="el-icon-link" /> + </a> + <el-scrollbar class="right-scrollbar"> + <!-- 组件属性 --> + <el-form v-show="currentTab==='field' && showField" size="small" label-width="90px"> + <el-form-item v-if="activeData.changeTag" label="组件类型"> + <el-select + v-model="activeData.tagIcon" + placeholder="请选择组件类型" + :style="{width: '100%'}" + @change="tagChange" + > + <el-option-group v-for="group in tagList" :key="group.label" :label="group.label"> + <el-option + v-for="item in group.options" + :key="item.label" + :label="item.label" + :value="item.tagIcon" + > + <svg-icon class="node-icon" :icon-class="item.tagIcon" /> + <span> {{ item.label }}</span> + </el-option> + </el-option-group> + </el-select> + </el-form-item> + <el-form-item v-if="activeData.vModel!==undefined" label="字段名"> + <el-input v-model="activeData.vModel" placeholder="请输入字段名(v-model)" /> + </el-form-item> + <el-form-item v-if="activeData.componentName!==undefined" label="组件名"> + {{ activeData.componentName }} + </el-form-item> + <el-form-item v-if="activeData.label!==undefined" label="标题"> + <el-input v-model="activeData.label" placeholder="请输入标题" /> + </el-form-item> + <el-form-item v-if="activeData.placeholder!==undefined" label="占位提示"> + <el-input v-model="activeData.placeholder" placeholder="请输入占位提示" /> + </el-form-item> + <el-form-item v-if="activeData['start-placeholder']!==undefined" label="开始占位"> + <el-input v-model="activeData['start-placeholder']" placeholder="请输入占位提示" /> + </el-form-item> + <el-form-item v-if="activeData['end-placeholder']!==undefined" label="结束占位"> + <el-input v-model="activeData['end-placeholder']" placeholder="请输入占位提示" /> + </el-form-item> + <el-form-item v-if="activeData.span!==undefined" label="表单栅格"> + <el-slider v-model="activeData.span" :max="24" :min="1" :marks="{12:''}" @change="spanChange" /> + </el-form-item> + <el-form-item v-if="activeData.layout==='rowFormItem'" label="栅格间隔"> + <el-input-number v-model="activeData.gutter" :min="0" placeholder="栅格间隔" /> + </el-form-item> + <el-form-item v-if="activeData.layout==='rowFormItem'" label="布局模式"> + <el-radio-group v-model="activeData.type"> + <el-radio-button label="default" /> + <el-radio-button label="flex" /> + </el-radio-group> + </el-form-item> + <el-form-item v-if="activeData.justify!==undefined&&activeData.type==='flex'" label="水平排列"> + <el-select v-model="activeData.justify" placeholder="请选择水平排列" :style="{width: '100%'}"> + <el-option + v-for="(item, index) in justifyOptions" + :key="index" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + <el-form-item v-if="activeData.align!==undefined&&activeData.type==='flex'" label="垂直排列"> + <el-radio-group v-model="activeData.align"> + <el-radio-button label="top" /> + <el-radio-button label="middle" /> + <el-radio-button label="bottom" /> + </el-radio-group> + </el-form-item> + <el-form-item v-if="activeData.labelWidth!==undefined" label="标签宽度"> + <el-input v-model.number="activeData.labelWidth" type="number" placeholder="请输入标签宽度" /> + </el-form-item> + <el-form-item v-if="activeData.style&&activeData.style.width!==undefined" label="组件宽度"> + <el-input v-model="activeData.style.width" placeholder="请输入组件宽度" clearable /> + </el-form-item> + <el-form-item v-if="activeData.vModel!==undefined" label="默认值"> + <el-input + :value="setDefaultValue(activeData.defaultValue)" + placeholder="请输入默认值" + @input="onDefaultValueInput" + /> + </el-form-item> + <el-form-item v-if="activeData.tag==='el-checkbox-group'" label="至少应选"> + <el-input-number + :value="activeData.min" + :min="0" + placeholder="至少应选" + @input="$set(activeData, 'min', $event?$event:undefined)" + /> + </el-form-item> + <el-form-item v-if="activeData.tag==='el-checkbox-group'" label="最多可选"> + <el-input-number + :value="activeData.max" + :min="0" + placeholder="最多可选" + @input="$set(activeData, 'max', $event?$event:undefined)" + /> + </el-form-item> + <el-form-item v-if="activeData.prepend!==undefined" label="前缀"> + <el-input v-model="activeData.prepend" placeholder="请输入前缀" /> + </el-form-item> + <el-form-item v-if="activeData.append!==undefined" label="后缀"> + <el-input v-model="activeData.append" placeholder="请输入后缀" /> + </el-form-item> + <el-form-item v-if="activeData['prefix-icon']!==undefined" label="前图标"> + <el-input v-model="activeData['prefix-icon']" placeholder="请输入前图标名称"> + <el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('prefix-icon')"> + 选择 + </el-button> + </el-input> + </el-form-item> + <el-form-item v-if="activeData['suffix-icon'] !== undefined" label="后图标"> + <el-input v-model="activeData['suffix-icon']" placeholder="请输入后图标名称"> + <el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('suffix-icon')"> + 选择 + </el-button> + </el-input> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-cascader'" label="选项分隔符"> + <el-input v-model="activeData.separator" placeholder="请输入选项分隔符" /> + </el-form-item> + <el-form-item v-if="activeData.autosize !== undefined" label="最小行数"> + <el-input-number v-model="activeData.autosize.minRows" :min="1" placeholder="最小行数" /> + </el-form-item> + <el-form-item v-if="activeData.autosize !== undefined" label="最大行数"> + <el-input-number v-model="activeData.autosize.maxRows" :min="1" placeholder="最大行数" /> + </el-form-item> + <el-form-item v-if="activeData.min !== undefined" label="最小值"> + <el-input-number v-model="activeData.min" placeholder="最小值" /> + </el-form-item> + <el-form-item v-if="activeData.max !== undefined" label="最大值"> + <el-input-number v-model="activeData.max" placeholder="最大值" /> + </el-form-item> + <el-form-item v-if="activeData.step !== undefined" label="步长"> + <el-input-number v-model="activeData.step" placeholder="步数" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-input-number'" label="精度"> + <el-input-number v-model="activeData.precision" :min="0" placeholder="精度" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-input-number'" label="按钮位置"> + <el-radio-group v-model="activeData['controls-position']"> + <el-radio-button label=""> + 默认 + </el-radio-button> + <el-radio-button label="right"> + 右侧 + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-if="activeData.maxlength !== undefined" label="最多输入"> + <el-input v-model="activeData.maxlength" placeholder="请输入字符长度"> + <template slot="append"> + 个字符 + </template> + </el-input> + </el-form-item> + <el-form-item v-if="activeData['active-text'] !== undefined" label="开启提示"> + <el-input v-model="activeData['active-text']" placeholder="请输入开启提示" /> + </el-form-item> + <el-form-item v-if="activeData['inactive-text'] !== undefined" label="关闭提示"> + <el-input v-model="activeData['inactive-text']" placeholder="请输入关闭提示" /> + </el-form-item> + <el-form-item v-if="activeData['active-value'] !== undefined" label="开启值"> + <el-input + :value="setDefaultValue(activeData['active-value'])" + placeholder="请输入开启值" + @input="onSwitchValueInput($event, 'active-value')" + /> + </el-form-item> + <el-form-item v-if="activeData['inactive-value'] !== undefined" label="关闭值"> + <el-input + :value="setDefaultValue(activeData['inactive-value'])" + placeholder="请输入关闭值" + @input="onSwitchValueInput($event, 'inactive-value')" + /> + </el-form-item> + <el-form-item + v-if="activeData.type !== undefined && 'el-date-picker' === activeData.tag" + label="时间类型" + > + <el-select + v-model="activeData.type" + placeholder="请选择时间类型" + :style="{ width: '100%' }" + @change="dateTypeChange" + > + <el-option + v-for="(item, index) in dateOptions" + :key="index" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + <el-form-item v-if="activeData.name !== undefined" label="文件字段名"> + <el-input v-model="activeData.name" placeholder="请输入上传文件字段名" /> + </el-form-item> + <el-form-item v-if="activeData.accept !== undefined" label="文件类型"> + <el-select + v-model="activeData.accept" + placeholder="请选择文件类型" + :style="{ width: '100%' }" + clearable + > + <el-option label="图片" value="image/*" /> + <el-option label="视频" value="video/*" /> + <el-option label="音频" value="audio/*" /> + <el-option label="excel" value=".xls,.xlsx" /> + <el-option label="word" value=".doc,.docx" /> + <el-option label="pdf" value=".pdf" /> + <el-option label="txt" value=".txt" /> + </el-select> + </el-form-item> + <el-form-item v-if="activeData.fileSize !== undefined" label="文件大小"> + <el-input v-model.number="activeData.fileSize" placeholder="请输入文件大小"> + <el-select slot="append" v-model="activeData.sizeUnit" :style="{ width: '66px' }"> + <el-option label="KB" value="KB" /> + <el-option label="MB" value="MB" /> + <el-option label="GB" value="GB" /> + </el-select> + </el-input> + </el-form-item> + <el-form-item v-if="activeData.action !== undefined" label="上传地址"> + <el-input v-model="activeData.action" placeholder="请输入上传地址" clearable /> + </el-form-item> + <el-form-item v-if="activeData['list-type'] !== undefined" label="列表类型"> + <el-radio-group v-model="activeData['list-type']" size="small"> + <el-radio-button label="text"> + text + </el-radio-button> + <el-radio-button label="picture"> + picture + </el-radio-button> + <el-radio-button label="picture-card"> + picture-card + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item + v-if="activeData.buttonText !== undefined" + v-show="'picture-card' !== activeData['list-type']" + label="按钮文字" + > + <el-input v-model="activeData.buttonText" placeholder="请输入按钮文字" /> + </el-form-item> + <el-form-item v-if="activeData['range-separator'] !== undefined" label="分隔符"> + <el-input v-model="activeData['range-separator']" placeholder="请输入分隔符" /> + </el-form-item> + <el-form-item v-if="activeData['picker-options'] !== undefined" label="时间段"> + <el-input + v-model="activeData['picker-options'].selectableRange" + placeholder="请输入时间段" + /> + </el-form-item> + <el-form-item v-if="activeData.format !== undefined" label="时间格式"> + <el-input + :value="activeData.format" + placeholder="请输入时间格式" + @input="setTimeValue($event)" + /> + </el-form-item> + <template v-if="['el-checkbox-group', 'el-radio-group', 'el-select'].indexOf(activeData.tag) > -1"> + <el-divider>选项</el-divider> + <draggable + :list="activeData.options" + :animation="340" + group="selectItem" + handle=".option-drag" + > + <div v-for="(item, index) in activeData.options" :key="index" class="select-item"> + <div class="select-line-icon option-drag"> + <i class="el-icon-s-operation" /> + </div> + <el-input v-model="item.label" placeholder="选项名" size="small" /> + <el-input + placeholder="选项值" + size="small" + :value="item.value" + @input="setOptionValue(item, $event)" + /> + <div class="close-btn select-line-icon" @click="activeData.options.splice(index, 1)"> + <i class="el-icon-remove-outline" /> + </div> + </div> + </draggable> + <div style="margin-left: 20px;"> + <el-button + style="padding-bottom: 0" + icon="el-icon-circle-plus-outline" + type="text" + @click="addSelectItem" + > + 添加选项 + </el-button> + </div> + <el-divider /> + </template> + + <template v-if="['el-cascader'].indexOf(activeData.tag) > -1"> + <el-divider>选项</el-divider> + <el-form-item label="数据类型"> + <el-radio-group v-model="activeData.dataType" size="small"> + <el-radio-button label="dynamic"> + 动态数据 + </el-radio-button> + <el-radio-button label="static"> + 静态数据 + </el-radio-button> + </el-radio-group> + </el-form-item> + + <template v-if="activeData.dataType === 'dynamic'"> + <el-form-item label="标签键名"> + <el-input v-model="activeData.labelKey" placeholder="请输入标签键名" /> + </el-form-item> + <el-form-item label="值键名"> + <el-input v-model="activeData.valueKey" placeholder="请输入值键名" /> + </el-form-item> + <el-form-item label="子级键名"> + <el-input v-model="activeData.childrenKey" placeholder="请输入子级键名" /> + </el-form-item> + </template> + + <el-tree + v-if="activeData.dataType === 'static'" + draggable + :data="activeData.options" + node-key="id" + :expand-on-click-node="false" + :render-content="renderContent" + /> + <div v-if="activeData.dataType === 'static'" style="margin-left: 20px"> + <el-button + style="padding-bottom: 0" + icon="el-icon-circle-plus-outline" + type="text" + @click="addTreeItem" + > + 添加父级 + </el-button> + </div> + <el-divider /> + </template> + + <el-form-item v-if="activeData.optionType !== undefined" label="选项样式"> + <el-radio-group v-model="activeData.optionType"> + <el-radio-button label="default"> + 默认 + </el-radio-button> + <el-radio-button label="button"> + 按钮 + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-if="activeData['active-color'] !== undefined" label="开启颜色"> + <el-color-picker v-model="activeData['active-color']" /> + </el-form-item> + <el-form-item v-if="activeData['inactive-color'] !== undefined" label="关闭颜色"> + <el-color-picker v-model="activeData['inactive-color']" /> + </el-form-item> + + <el-form-item v-if="activeData['allow-half'] !== undefined" label="允许半选"> + <el-switch v-model="activeData['allow-half']" /> + </el-form-item> + <el-form-item v-if="activeData['show-text'] !== undefined" label="辅助文字"> + <el-switch v-model="activeData['show-text']" @change="rateTextChange" /> + </el-form-item> + <el-form-item v-if="activeData['show-score'] !== undefined" label="显示分数"> + <el-switch v-model="activeData['show-score']" @change="rateScoreChange" /> + </el-form-item> + <el-form-item v-if="activeData['show-stops'] !== undefined" label="显示间断点"> + <el-switch v-model="activeData['show-stops']" /> + </el-form-item> + <el-form-item v-if="activeData.range !== undefined" label="范围选择"> + <el-switch v-model="activeData.range" @change="rangeChange" /> + </el-form-item> + <el-form-item + v-if="activeData.border !== undefined && activeData.optionType === 'default'" + label="是否带边框" + > + <el-switch v-model="activeData.border" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-color-picker'" label="颜色格式"> + <el-select + v-model="activeData['color-format']" + placeholder="请选择颜色格式" + :style="{ width: '100%' }" + @change="colorFormatChange" + > + <el-option + v-for="(item, index) in colorFormatOptions" + :key="index" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + <el-form-item + v-if="activeData.size !== undefined && + (activeData.optionType === 'button' || + activeData.border || + activeData.tag === 'el-color-picker')" + label="选项尺寸" + > + <el-radio-group v-model="activeData.size"> + <el-radio-button label="medium"> + 中等 + </el-radio-button> + <el-radio-button label="small"> + 较小 + </el-radio-button> + <el-radio-button label="mini"> + 迷你 + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item v-if="activeData['show-word-limit'] !== undefined" label="输入统计"> + <el-switch v-model="activeData['show-word-limit']" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-input-number'" label="严格步数"> + <el-switch v-model="activeData['step-strictly']" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-cascader'" label="是否多选"> + <el-switch v-model="activeData.props.props.multiple" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-cascader'" label="展示全路径"> + <el-switch v-model="activeData['show-all-levels']" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-cascader'" label="可否筛选"> + <el-switch v-model="activeData.filterable" /> + </el-form-item> + <el-form-item v-if="activeData.clearable !== undefined" label="能否清空"> + <el-switch v-model="activeData.clearable" /> + </el-form-item> + <el-form-item v-if="activeData.showTip !== undefined" label="显示提示"> + <el-switch v-model="activeData.showTip" /> + </el-form-item> + <el-form-item v-if="activeData.multiple !== undefined" label="多选文件"> + <el-switch v-model="activeData.multiple" /> + </el-form-item> + <el-form-item v-if="activeData['auto-upload'] !== undefined" label="自动上传"> + <el-switch v-model="activeData['auto-upload']" /> + </el-form-item> + <el-form-item v-if="activeData.readonly !== undefined" label="是否只读"> + <el-switch v-model="activeData.readonly" /> + </el-form-item> + <el-form-item v-if="activeData.disabled !== undefined" label="是否禁用"> + <el-switch v-model="activeData.disabled" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-select'" label="是否可搜索"> + <el-switch v-model="activeData.filterable" /> + </el-form-item> + <el-form-item v-if="activeData.tag === 'el-select'" label="是否多选"> + <el-switch v-model="activeData.multiple" @change="multipleChange" /> + </el-form-item> + <el-form-item v-if="activeData.required !== undefined" label="是否必填"> + <el-switch v-model="activeData.required" /> + </el-form-item> + + <template v-if="activeData.layoutTree"> + <el-divider>布局结构树</el-divider> + <el-tree + :data="[activeData]" + :props="layoutTreeProps" + node-key="renderKey" + default-expand-all + draggable + > + <span slot-scope="{ node, data }"> + <span class="node-label"> + <svg-icon class="node-icon" :icon-class="data.tagIcon" /> + {{ node.label }} + </span> + </span> + </el-tree> + </template> + + <template v-if="activeData.layout === 'colFormItem' && activeData.tag !== 'el-button'"> + <el-divider>正则校验</el-divider> + <div + v-for="(item, index) in activeData.regList" + :key="index" + class="reg-item" + > + <span class="close-btn" @click="activeData.regList.splice(index, 1)"> + <i class="el-icon-close" /> + </span> + <el-form-item label="表达式"> + <el-input v-model="item.pattern" placeholder="请输入正则" /> + </el-form-item> + <el-form-item label="错误提示" style="margin-bottom:0"> + <el-input v-model="item.message" placeholder="请输入错误提示" /> + </el-form-item> + </div> + <div style="margin-left: 20px"> + <el-button icon="el-icon-circle-plus-outline" type="text" @click="addReg"> + 添加规则 + </el-button> + </div> + </template> + </el-form> + <!-- 表单属性 --> + <el-form v-show="currentTab === 'form'" size="small" label-width="90px"> + <el-form-item label="表单名"> + <el-input v-model="formConf.formRef" placeholder="请输入表单名(ref)" /> + </el-form-item> + <el-form-item label="表单模型"> + <el-input v-model="formConf.formModel" placeholder="请输入数据模型" /> + </el-form-item> + <el-form-item label="校验模型"> + <el-input v-model="formConf.formRules" placeholder="请输入校验模型" /> + </el-form-item> + <el-form-item label="表单尺寸"> + <el-radio-group v-model="formConf.size"> + <el-radio-button label="medium"> + 中等 + </el-radio-button> + <el-radio-button label="small"> + 较小 + </el-radio-button> + <el-radio-button label="mini"> + 迷你 + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item label="标签对齐"> + <el-radio-group v-model="formConf.labelPosition"> + <el-radio-button label="left"> + 左对齐 + </el-radio-button> + <el-radio-button label="right"> + 右对齐 + </el-radio-button> + <el-radio-button label="top"> + 顶部对齐 + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item label="标签宽度"> + <el-input-number v-model="formConf.labelWidth" placeholder="标签宽度" /> + </el-form-item> + <el-form-item label="栅格间隔"> + <el-input-number v-model="formConf.gutter" :min="0" placeholder="栅格间隔" /> + </el-form-item> + <el-form-item label="禁用表单"> + <el-switch v-model="formConf.disabled" /> + </el-form-item> + <el-form-item label="表单按钮"> + <el-switch v-model="formConf.formBtns" /> + </el-form-item> + <el-form-item label="显示未选中组件边框"> + <el-switch v-model="formConf.unFocusedComponentBorder" /> + </el-form-item> + </el-form> + </el-scrollbar> + </div> + + <treeNode-dialog :visible.sync="dialogVisible" title="添加选项" @commit="addNode" /> + <icons-dialog :visible.sync="iconsVisible" :current="activeData[currentIconModel]" @select="setIcon" /> + </div> +</template> + +<script> +import { isArray } from 'util' +import draggable from 'vuedraggable' +import TreeNodeDialog from './TreeNodeDialog' +import { isNumberStr } from '@/utils/index' +import IconsDialog from './IconsDialog' +import { + inputComponents, + selectComponents, + layoutComponents +} from '@/utils/generator/config' + +const dateTimeFormat = { + date: 'yyyy-MM-dd', + week: 'yyyy 第 WW 周', + month: 'yyyy-MM', + year: 'yyyy', + datetime: 'yyyy-MM-dd HH:mm:ss', + daterange: 'yyyy-MM-dd', + monthrange: 'yyyy-MM', + datetimerange: 'yyyy-MM-dd HH:mm:ss' +} + +export default { + components: { + draggable, + TreeNodeDialog, + IconsDialog + }, + props: ['showField', 'activeData', 'formConf'], + data() { + return { + currentTab: 'field', + currentNode: null, + dialogVisible: false, + iconsVisible: false, + currentIconModel: null, + dateTypeOptions: [ + { + label: '日(date)', + value: 'date' + }, + { + label: '周(week)', + value: 'week' + }, + { + label: '月(month)', + value: 'month' + }, + { + label: '年(year)', + value: 'year' + }, + { + label: '日期时间(datetime)', + value: 'datetime' + } + ], + dateRangeTypeOptions: [ + { + label: '日期范围(daterange)', + value: 'daterange' + }, + { + label: '月范围(monthrange)', + value: 'monthrange' + }, + { + label: '日期时间范围(datetimerange)', + value: 'datetimerange' + } + ], + colorFormatOptions: [ + { + label: 'hex', + value: 'hex' + }, + { + label: 'rgb', + value: 'rgb' + }, + { + label: 'rgba', + value: 'rgba' + }, + { + label: 'hsv', + value: 'hsv' + }, + { + label: 'hsl', + value: 'hsl' + } + ], + justifyOptions: [ + { + label: 'start', + value: 'start' + }, + { + label: 'end', + value: 'end' + }, + { + label: 'center', + value: 'center' + }, + { + label: 'space-around', + value: 'space-around' + }, + { + label: 'space-between', + value: 'space-between' + } + ], + layoutTreeProps: { + label(data, node) { + return data.componentName || `${data.label}: ${data.vModel}` + } + } + } + }, + computed: { + documentLink() { + return ( + this.activeData.document + || 'https://element.eleme.cn/#/zh-CN/component/installation' + ) + }, + dateOptions() { + if ( + this.activeData.type !== undefined + && this.activeData.tag === 'el-date-picker' + ) { + if (this.activeData['start-placeholder'] === undefined) { + return this.dateTypeOptions + } + return this.dateRangeTypeOptions + } + return [] + }, + tagList() { + return [ + { + label: '输入型组件', + options: inputComponents + }, + { + label: '选择型组件', + options: selectComponents + } + ] + } + }, + methods: { + addReg() { + this.activeData.regList.push({ + pattern: '', + message: '' + }) + }, + addSelectItem() { + this.activeData.options.push({ + label: '', + value: '' + }) + }, + addTreeItem() { + ++this.idGlobal + this.dialogVisible = true + this.currentNode = this.activeData.options + }, + renderContent(h, { node, data, store }) { + return ( + <div class="custom-tree-node"> + <span>{node.label}</span> + <span class="node-operation"> + <i on-click={() => this.append(data)} + class="el-icon-plus" + title="添加" + ></i> + <i on-click={() => this.remove(node, data)} + class="el-icon-delete" + title="删除" + ></i> + </span> + </div> + ) + }, + append(data) { + if (!data.children) { + this.$set(data, 'children', []) + } + this.dialogVisible = true + this.currentNode = data.children + }, + remove(node, data) { + const { parent } = node + const children = parent.data.children || parent.data + const index = children.findIndex(d => d.id === data.id) + children.splice(index, 1) + }, + addNode(data) { + this.currentNode.push(data) + }, + setOptionValue(item, val) { + item.value = isNumberStr(val) ? +val : val + }, + setDefaultValue(val) { + if (Array.isArray(val)) { + return val.join(',') + } + if (['string', 'number'].indexOf(val) > -1) { + return val + } + if (typeof val === 'boolean') { + return `${val}` + } + return val + }, + onDefaultValueInput(str) { + if (isArray(this.activeData.defaultValue)) { + // 数组 + this.$set( + this.activeData, + 'defaultValue', + str.split(',').map(val => (isNumberStr(val) ? +val : val)) + ) + } else if (['true', 'false'].indexOf(str) > -1) { + // 布尔 + this.$set(this.activeData, 'defaultValue', JSON.parse(str)) + } else { + // 字符串和数字 + this.$set( + this.activeData, + 'defaultValue', + isNumberStr(str) ? +str : str + ) + } + }, + onSwitchValueInput(val, name) { + if (['true', 'false'].indexOf(val) > -1) { + this.$set(this.activeData, name, JSON.parse(val)) + } else { + this.$set(this.activeData, name, isNumberStr(val) ? +val : val) + } + }, + setTimeValue(val, type) { + const valueFormat = type === 'week' ? dateTimeFormat.date : val + this.$set(this.activeData, 'defaultValue', null) + this.$set(this.activeData, 'value-format', valueFormat) + this.$set(this.activeData, 'format', val) + }, + spanChange(val) { + this.formConf.span = val + }, + multipleChange(val) { + this.$set(this.activeData, 'defaultValue', val ? [] : '') + }, + dateTypeChange(val) { + this.setTimeValue(dateTimeFormat[val], val) + }, + rangeChange(val) { + this.$set( + this.activeData, + 'defaultValue', + val ? [this.activeData.min, this.activeData.max] : this.activeData.min + ) + }, + rateTextChange(val) { + if (val) this.activeData['show-score'] = false + }, + rateScoreChange(val) { + if (val) this.activeData['show-text'] = false + }, + colorFormatChange(val) { + this.activeData.defaultValue = null + this.activeData['show-alpha'] = val.indexOf('a') > -1 + this.activeData.renderKey = +new Date() // 更新renderKey,重新渲染该组件 + }, + openIconsDialog(model) { + this.iconsVisible = true + this.currentIconModel = model + }, + setIcon(val) { + this.activeData[this.currentIconModel] = val + }, + tagChange(tagIcon) { + let target = inputComponents.find(item => item.tagIcon === tagIcon) + if (!target) target = selectComponents.find(item => item.tagIcon === tagIcon) + this.$emit('tag-change', target) + } + } +} +</script> + +<style lang="scss" scoped> +.right-board { + width: 350px; + position: absolute; + right: 0; + top: 0; + padding-top: 3px; + .field-box { + position: relative; + height: calc(100vh - 42px); + box-sizing: border-box; + overflow: hidden; + } + .el-scrollbar { + height: 100%; + } +} +.select-item { + display: flex; + border: 1px dashed #fff; + box-sizing: border-box; + & .close-btn { + cursor: pointer; + color: #f56c6c; + } + & .el-input + .el-input { + margin-left: 4px; + } +} +.select-item + .select-item { + margin-top: 4px; +} +.select-item.sortable-chosen { + border: 1px dashed #409eff; +} +.select-line-icon { + line-height: 32px; + font-size: 22px; + padding: 0 4px; + color: #777; +} +.option-drag { + cursor: move; +} +.time-range { + .el-date-editor { + width: 227px; + } + ::v-deep .el-icon-time { + display: none; + } +} +.document-link { + position: absolute; + display: block; + width: 26px; + height: 26px; + top: 0; + left: 0; + cursor: pointer; + background: #409eff; + z-index: 1; + border-radius: 0 0 6px 0; + text-align: center; + line-height: 26px; + color: #fff; + font-size: 18px; +} +.node-label{ + font-size: 14px; +} +.node-icon{ + color: #bebfc3; +} +</style> diff --git a/ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue b/ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue new file mode 100644 index 0000000..fa7f0b2 --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/TreeNodeDialog.vue @@ -0,0 +1,149 @@ +<template> + <div> + <el-dialog + v-bind="$attrs" + :close-on-click-modal="false" + :modal-append-to-body="false" + v-on="$listeners" + @open="onOpen" + @close="onClose" + > + <el-row :gutter="0"> + <el-form + ref="elForm" + :model="formData" + :rules="rules" + size="small" + label-width="100px" + > + <el-col :span="24"> + <el-form-item + label="选项名" + prop="label" + > + <el-input + v-model="formData.label" + placeholder="请输入选项名" + clearable + /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item + label="选项值" + prop="value" + > + <el-input + v-model="formData.value" + placeholder="请输入选项值" + clearable + > + <el-select + slot="append" + v-model="dataType" + :style="{width: '100px'}" + > + <el-option + v-for="(item, index) in dataTypeOptions" + :key="index" + :label="item.label" + :value="item.value" + :disabled="item.disabled" + /> + </el-select> + </el-input> + </el-form-item> + </el-col> + </el-form> + </el-row> + <div slot="footer"> + <el-button + type="primary" + @click="handleConfirm" + > + 确定 + </el-button> + <el-button @click="close"> + 取消 + </el-button> + </div> + </el-dialog> + </div> +</template> +<script> +import { isNumberStr } from '@/utils/index' + +export default { + components: {}, + inheritAttrs: false, + props: [], + data() { + return { + id: 100, + formData: { + label: undefined, + value: undefined + }, + rules: { + label: [ + { + required: true, + message: '请输入选项名', + trigger: 'blur' + } + ], + value: [ + { + required: true, + message: '请输入选项值', + trigger: 'blur' + } + ] + }, + dataType: 'string', + dataTypeOptions: [ + { + label: '字符串', + value: 'string' + }, + { + label: '数字', + value: 'number' + } + ] + } + }, + computed: {}, + watch: { + // eslint-disable-next-line func-names + 'formData.value': function (val) { + this.dataType = isNumberStr(val) ? 'number' : 'string' + } + }, + created() {}, + mounted() {}, + methods: { + onOpen() { + this.formData = { + label: undefined, + value: undefined + } + }, + onClose() {}, + close() { + this.$emit('update:visible', false) + }, + handleConfirm() { + this.$refs.elForm.validate(valid => { + if (!valid) return + if (this.dataType === 'number') { + this.formData.value = parseFloat(this.formData.value) + } + this.formData.id = this.id++ + this.$emit('commit', this.formData) + this.close() + }) + } + } +} +</script> diff --git a/ruoyi-ui/src/views/tool/build/index.vue b/ruoyi-ui/src/views/tool/build/index.vue new file mode 100644 index 0000000..2bd298b --- /dev/null +++ b/ruoyi-ui/src/views/tool/build/index.vue @@ -0,0 +1,768 @@ +<template> + <div class="container"> + <div class="left-board"> + <div class="logo-wrapper"> + <div class="logo"> + <img :src="logo" alt="logo"> Form Generator + </div> + </div> + <el-scrollbar class="left-scrollbar"> + <div class="components-list"> + <div class="components-title"> + <svg-icon icon-class="component" />输入型组件 + </div> + <draggable + class="components-draggable" + :list="inputComponents" + :group="{ name: 'componentsGroup', pull: 'clone', put: false }" + :clone="cloneComponent" + draggable=".components-item" + :sort="false" + @end="onEnd" + > + <div + v-for="(element, index) in inputComponents" :key="index" class="components-item" + @click="addComponent(element)" + > + <div class="components-body"> + <svg-icon :icon-class="element.tagIcon" /> + {{ element.label }} + </div> + </div> + </draggable> + <div class="components-title"> + <svg-icon icon-class="component" />选择型组件 + </div> + <draggable + class="components-draggable" + :list="selectComponents" + :group="{ name: 'componentsGroup', pull: 'clone', put: false }" + :clone="cloneComponent" + draggable=".components-item" + :sort="false" + @end="onEnd" + > + <div + v-for="(element, index) in selectComponents" + :key="index" + class="components-item" + @click="addComponent(element)" + > + <div class="components-body"> + <svg-icon :icon-class="element.tagIcon" /> + {{ element.label }} + </div> + </div> + </draggable> + <div class="components-title"> + <svg-icon icon-class="component" /> 布局型组件 + </div> + <draggable + class="components-draggable" :list="layoutComponents" + :group="{ name: 'componentsGroup', pull: 'clone', put: false }" :clone="cloneComponent" + draggable=".components-item" :sort="false" @end="onEnd" + > + <div + v-for="(element, index) in layoutComponents" :key="index" class="components-item" + @click="addComponent(element)" + > + <div class="components-body"> + <svg-icon :icon-class="element.tagIcon" /> + {{ element.label }} + </div> + </div> + </draggable> + </div> + </el-scrollbar> + </div> + + <div class="center-board"> + <div class="action-bar"> + <el-button icon="el-icon-download" type="text" @click="download"> + 导出vue文件 + </el-button> + <el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy"> + 复制代码 + </el-button> + <el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty"> + 清空 + </el-button> + </div> + <el-scrollbar class="center-scrollbar"> + <el-row class="center-board-row" :gutter="formConf.gutter"> + <el-form + :size="formConf.size" + :label-position="formConf.labelPosition" + :disabled="formConf.disabled" + :label-width="formConf.labelWidth + 'px'" + > + <draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup"> + <draggable-item + v-for="(element, index) in drawingList" + :key="element.renderKey" + :drawing-list="drawingList" + :element="element" + :index="index" + :active-id="activeId" + :form-conf="formConf" + @activeItem="activeFormItem" + @copyItem="drawingItemCopy" + @deleteItem="drawingItemDelete" + /> + </draggable> + <div v-show="!drawingList.length" class="empty-info"> + 从左侧拖入或点选组件进行表单设计 + </div> + </el-form> + </el-row> + </el-scrollbar> + </div> + + <right-panel + :active-data="activeData" + :form-conf="formConf" + :show-field="!!drawingList.length" + @tag-change="tagChange" + /> + + <code-type-dialog + :visible.sync="dialogVisible" + title="选择生成类型" + :show-file-name="showFileName" + @confirm="generate" + /> + <input id="copyNode" type="hidden"> + </div> +</template> + +<script> +import draggable from 'vuedraggable' +import beautifier from 'js-beautify' +import ClipboardJS from 'clipboard' +import render from '@/utils/generator/render' +import RightPanel from './RightPanel' +import { inputComponents, selectComponents, layoutComponents, formConf } from '@/utils/generator/config' +import { beautifierConf, titleCase } from '@/utils/index' +import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/html' +import { makeUpJs } from '@/utils/generator/js' +import { makeUpCss } from '@/utils/generator/css' +import drawingDefault from '@/utils/generator/drawingDefault' +import logo from '@/assets/logo/logo.png' +import CodeTypeDialog from './CodeTypeDialog' +import DraggableItem from './DraggableItem' + +let oldActiveId +let tempActiveData + +export default { + components: { + draggable, + render, + RightPanel, + CodeTypeDialog, + DraggableItem + }, + data() { + return { + logo, + idGlobal: 100, + formConf, + inputComponents, + selectComponents, + layoutComponents, + labelWidth: 100, + drawingList: drawingDefault, + drawingData: {}, + activeId: drawingDefault[0].formId, + drawerVisible: false, + formData: {}, + dialogVisible: false, + generateConf: null, + showFileName: false, + activeData: drawingDefault[0] + } + }, + created() { + // 防止 firefox 下 拖拽 会新打卡一个选项卡 + document.body.ondrop = event => { + event.preventDefault() + event.stopPropagation() + } + }, + watch: { + // eslint-disable-next-line func-names + 'activeData.label': function (val, oldVal) { + if ( + this.activeData.placeholder === undefined + || !this.activeData.tag + || oldActiveId !== this.activeId + ) { + return + } + this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val + }, + activeId: { + handler(val) { + oldActiveId = val + }, + immediate: true + } + }, + mounted() { + const clipboard = new ClipboardJS('#copyNode', { + text: trigger => { + const codeStr = this.generateCode() + this.$notify({ + title: '成功', + message: '代码已复制到剪切板,可粘贴。', + type: 'success' + }) + return codeStr + } + }) + clipboard.on('error', e => { + this.$message.error('代码复制失败') + }) + }, + methods: { + activeFormItem(element) { + this.activeData = element + this.activeId = element.formId + }, + onEnd(obj, a) { + if (obj.from !== obj.to) { + this.activeData = tempActiveData + this.activeId = this.idGlobal + } + }, + addComponent(item) { + const clone = this.cloneComponent(item) + this.drawingList.push(clone) + this.activeFormItem(clone) + }, + cloneComponent(origin) { + const clone = JSON.parse(JSON.stringify(origin)) + clone.formId = ++this.idGlobal + clone.span = formConf.span + clone.renderKey = +new Date() // 改变renderKey后可以实现强制更新组件 + if (!clone.layout) clone.layout = 'colFormItem' + if (clone.layout === 'colFormItem') { + clone.vModel = `field${this.idGlobal}` + clone.placeholder !== undefined && (clone.placeholder += clone.label) + tempActiveData = clone + } else if (clone.layout === 'rowFormItem') { + delete clone.label + clone.componentName = `row${this.idGlobal}` + clone.gutter = this.formConf.gutter + tempActiveData = clone + } + return tempActiveData + }, + AssembleFormData() { + this.formData = { + fields: JSON.parse(JSON.stringify(this.drawingList)), + ...this.formConf + } + }, + generate(data) { + const func = this[`exec${titleCase(this.operationType)}`] + this.generateConf = data + func && func(data) + }, + execRun(data) { + this.AssembleFormData() + this.drawerVisible = true + }, + execDownload(data) { + const codeStr = this.generateCode() + const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' }) + this.$download.saveAs(blob, data.fileName) + }, + execCopy(data) { + document.getElementById('copyNode').click() + }, + empty() { + this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then( + () => { + this.drawingList = [] + } + ) + }, + drawingItemCopy(item, parent) { + let clone = JSON.parse(JSON.stringify(item)) + clone = this.createIdAndKey(clone) + parent.push(clone) + this.activeFormItem(clone) + }, + createIdAndKey(item) { + item.formId = ++this.idGlobal + item.renderKey = +new Date() + if (item.layout === 'colFormItem') { + item.vModel = `field${this.idGlobal}` + } else if (item.layout === 'rowFormItem') { + item.componentName = `row${this.idGlobal}` + } + if (Array.isArray(item.children)) { + item.children = item.children.map(childItem => this.createIdAndKey(childItem)) + } + return item + }, + drawingItemDelete(index, parent) { + parent.splice(index, 1) + this.$nextTick(() => { + const len = this.drawingList.length + if (len) { + this.activeFormItem(this.drawingList[len - 1]) + } + }) + }, + generateCode() { + const { type } = this.generateConf + this.AssembleFormData() + const script = vueScript(makeUpJs(this.formData, type)) + const html = vueTemplate(makeUpHtml(this.formData, type)) + const css = cssStyle(makeUpCss(this.formData)) + return beautifier.html(html + script + css, beautifierConf.html) + }, + download() { + this.dialogVisible = true + this.showFileName = true + this.operationType = 'download' + }, + run() { + this.dialogVisible = true + this.showFileName = false + this.operationType = 'run' + }, + copy() { + this.dialogVisible = true + this.showFileName = false + this.operationType = 'copy' + }, + tagChange(newTag) { + newTag = this.cloneComponent(newTag) + newTag.vModel = this.activeData.vModel + newTag.formId = this.activeId + newTag.span = this.activeData.span + delete this.activeData.tag + delete this.activeData.tagIcon + delete this.activeData.document + Object.keys(newTag).forEach(key => { + if (this.activeData[key] !== undefined + && typeof this.activeData[key] === typeof newTag[key]) { + newTag[key] = this.activeData[key] + } + }) + this.activeData = newTag + this.updateDrawingList(newTag, this.drawingList) + }, + updateDrawingList(newTag, list) { + const index = list.findIndex(item => item.formId === this.activeId) + if (index > -1) { + list.splice(index, 1, newTag) + } else { + list.forEach(item => { + if (Array.isArray(item.children)) this.updateDrawingList(newTag, item.children) + }) + } + } + } +} +</script> + +<style lang='scss'> +.editor-tabs{ + background: #121315; + .el-tabs__header{ + margin: 0; + border-bottom-color: #121315; + .el-tabs__nav{ + border-color: #121315; + } + } + .el-tabs__item{ + height: 32px; + line-height: 32px; + color: #888a8e; + border-left: 1px solid #121315 !important; + background: #363636; + margin-right: 5px; + user-select: none; + } + .el-tabs__item.is-active{ + background: #1e1e1e; + border-bottom-color: #1e1e1e!important; + color: #fff; + } + .el-icon-edit{ + color: #f1fa8c; + } + .el-icon-document{ + color: #a95812; + } +} + +// home +.right-scrollbar { + .el-scrollbar__view { + padding: 12px 18px 15px 15px; + } +} +.left-scrollbar .el-scrollbar__wrap { + box-sizing: border-box; + overflow-x: hidden !important; + margin-bottom: 0 !important; +} +.center-tabs{ + .el-tabs__header{ + margin-bottom: 0!important; + } + .el-tabs__item{ + width: 50%; + text-align: center; + } + .el-tabs__nav{ + width: 100%; + } +} +.reg-item{ + padding: 12px 6px; + background: #f8f8f8; + position: relative; + border-radius: 4px; + .close-btn{ + position: absolute; + right: -6px; + top: -6px; + display: block; + width: 16px; + height: 16px; + line-height: 16px; + background: rgba(0, 0, 0, 0.2); + border-radius: 50%; + color: #fff; + text-align: center; + z-index: 1; + cursor: pointer; + font-size: 12px; + &:hover{ + background: rgba(210, 23, 23, 0.5) + } + } + & + .reg-item{ + margin-top: 18px; + } +} +.action-bar{ + & .el-button+.el-button { + margin-left: 15px; + } + & i { + font-size: 20px; + vertical-align: middle; + position: relative; + top: -1px; + } +} + +.custom-tree-node{ + width: 100%; + font-size: 14px; + .node-operation{ + float: right; + } + i[class*="el-icon"] + i[class*="el-icon"]{ + margin-left: 6px; + } + .el-icon-plus{ + color: #409EFF; + } + .el-icon-delete{ + color: #157a0c; + } +} + +.left-scrollbar .el-scrollbar__view{ + overflow-x: hidden; +} + +.el-rate{ + display: inline-block; + vertical-align: text-top; +} +.el-upload__tip{ + line-height: 1.2; +} + +$selectedColor: #f6f7ff; +$lighterBlue: #409EFF; + +.container { + position: relative; + width: 100%; + height: 100%; +} + +.components-list { + padding: 8px; + box-sizing: border-box; + height: 100%; + .components-item { + display: inline-block; + width: 48%; + margin: 1%; + transition: transform 0ms !important; + } +} +.components-draggable{ + padding-bottom: 20px; +} +.components-title{ + font-size: 14px; + color: #222; + margin: 6px 2px; + .svg-icon{ + color: #666; + font-size: 18px; + } +} + +.components-body { + padding: 8px 10px; + background: $selectedColor; + font-size: 12px; + cursor: move; + border: 1px dashed $selectedColor; + border-radius: 3px; + .svg-icon{ + color: #777; + font-size: 15px; + } + &:hover { + border: 1px dashed #787be8; + color: #787be8; + .svg-icon { + color: #787be8; + } + } +} + +.left-board { + width: 260px; + position: absolute; + left: 0; + top: 0; + height: 100vh; +} +.left-scrollbar{ + height: calc(100vh - 42px); + overflow: hidden; +} +.center-scrollbar { + height: calc(100vh - 42px); + overflow: hidden; + border-left: 1px solid #f1e8e8; + border-right: 1px solid #f1e8e8; + box-sizing: border-box; +} +.center-board { + height: 100vh; + width: auto; + margin: 0 350px 0 260px; + box-sizing: border-box; +} +.empty-info{ + position: absolute; + top: 46%; + left: 0; + right: 0; + text-align: center; + font-size: 18px; + color: #ccb1ea; + letter-spacing: 4px; +} +.action-bar{ + position: relative; + height: 42px; + text-align: right; + padding: 0 15px; + box-sizing: border-box;; + border: 1px solid #f1e8e8; + border-top: none; + border-left: none; + .delete-btn{ + color: #F56C6C; + } +} +.logo-wrapper{ + position: relative; + height: 42px; + background: #fff; + border-bottom: 1px solid #f1e8e8; + box-sizing: border-box; +} +.logo{ + position: absolute; + left: 12px; + top: 6px; + line-height: 30px; + color: #00afff; + font-weight: 600; + font-size: 17px; + white-space: nowrap; + > img{ + width: 30px; + height: 30px; + vertical-align: top; + } + .github{ + display: inline-block; + vertical-align: sub; + margin-left: 15px; + > img{ + height: 22px; + } + } +} + +.center-board-row { + padding: 12px 12px 15px 12px; + box-sizing: border-box; + & > .el-form { + // 69 = 12+15+42 + height: calc(100vh - 69px); + } +} +.drawing-board { + height: 100%; + position: relative; + .components-body { + padding: 0; + margin: 0; + font-size: 0; + } + .sortable-ghost { + position: relative; + display: block; + overflow: hidden; + &::before { + content: " "; + position: absolute; + left: 0; + right: 0; + top: 0; + height: 3px; + background: rgb(89, 89, 223); + z-index: 2; + } + } + .components-item.sortable-ghost { + width: 100%; + height: 60px; + background-color: $selectedColor; + } + .active-from-item { + & > .el-form-item{ + background: $selectedColor; + border-radius: 6px; + } + & > .drawing-item-copy, & > .drawing-item-delete{ + display: initial; + } + & > .component-name{ + color: $lighterBlue; + } + } + .el-form-item{ + margin-bottom: 15px; + } +} +.drawing-item{ + position: relative; + cursor: move; + &.unfocus-bordered:not(.activeFromItem) > div:first-child { + border: 1px dashed #ccc; + } + .el-form-item{ + padding: 12px 10px; + } +} +.drawing-row-item{ + position: relative; + cursor: move; + box-sizing: border-box; + border: 1px dashed #ccc; + border-radius: 3px; + padding: 0 2px; + margin-bottom: 15px; + .drawing-row-item { + margin-bottom: 2px; + } + .el-col{ + margin-top: 22px; + } + .el-form-item{ + margin-bottom: 0; + } + .drag-wrapper{ + min-height: 80px; + } + &.active-from-item{ + border: 1px dashed $lighterBlue; + } + .component-name{ + position: absolute; + top: 0; + left: 0; + font-size: 12px; + color: #bbb; + display: inline-block; + padding: 0 6px; + } +} +.drawing-item, .drawing-row-item{ + &:hover { + & > .el-form-item{ + background: $selectedColor; + border-radius: 6px; + } + & > .drawing-item-copy, & > .drawing-item-delete{ + display: initial; + } + } + & > .drawing-item-copy, & > .drawing-item-delete{ + display: none; + position: absolute; + top: -10px; + width: 22px; + height: 22px; + line-height: 22px; + text-align: center; + border-radius: 50%; + font-size: 12px; + border: 1px solid; + cursor: pointer; + z-index: 1; + } + & > .drawing-item-copy{ + right: 56px; + border-color: $lighterBlue; + color: $lighterBlue; + background: #fff; + &:hover{ + background: $lighterBlue; + color: #fff; + } + } + & > .drawing-item-delete{ + right: 24px; + border-color: #F56C6C; + color: #F56C6C; + background: #fff; + &:hover{ + background: #F56C6C; + color: #fff; + } + } +} +</style> diff --git a/ruoyi-ui/src/views/tool/gen/basicInfoForm.vue b/ruoyi-ui/src/views/tool/gen/basicInfoForm.vue new file mode 100644 index 0000000..7029529 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/basicInfoForm.vue @@ -0,0 +1,60 @@ +<template> + <el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px"> + <el-row> + <el-col :span="12"> + <el-form-item label="表名称" prop="tableName"> + <el-input placeholder="请输入仓库名称" v-model="info.tableName" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="表描述" prop="tableComment"> + <el-input placeholder="请输入" v-model="info.tableComment" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="实体类名称" prop="className"> + <el-input placeholder="请输入" v-model="info.className" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="作者" prop="functionAuthor"> + <el-input placeholder="请输入" v-model="info.functionAuthor" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="备注" prop="remark"> + <el-input type="textarea" :rows="3" v-model="info.remark"></el-input> + </el-form-item> + </el-col> + </el-row> + </el-form> +</template> + +<script> +export default { + props: { + info: { + type: Object, + default: null + } + }, + data() { + return { + rules: { + tableName: [ + { required: true, message: "请输入表名称", trigger: "blur" } + ], + tableComment: [ + { required: true, message: "请输入表描述", trigger: "blur" } + ], + className: [ + { required: true, message: "请输入实体类名称", trigger: "blur" } + ], + functionAuthor: [ + { required: true, message: "请输入作者", trigger: "blur" } + ] + } + }; + } +}; +</script> diff --git a/ruoyi-ui/src/views/tool/gen/editTable.vue b/ruoyi-ui/src/views/tool/gen/editTable.vue new file mode 100644 index 0000000..951497a --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/editTable.vue @@ -0,0 +1,234 @@ +<template> + <el-card> + <el-tabs v-model="activeName"> + <el-tab-pane label="基本信息" name="basic"> + <basic-info-form ref="basicInfo" :info="info" /> + </el-tab-pane> + <el-tab-pane label="字段信息" name="columnInfo"> + <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight"> + <el-table-column label="序号" type="index" min-width="5%" class-name="allowDrag" /> + <el-table-column + label="字段列名" + prop="columnName" + min-width="10%" + :show-overflow-tooltip="true" + /> + <el-table-column label="字段描述" min-width="10%"> + <template slot-scope="scope"> + <el-input v-model="scope.row.columnComment"></el-input> + </template> + </el-table-column> + <el-table-column + label="物理类型" + prop="columnType" + min-width="10%" + :show-overflow-tooltip="true" + /> + <el-table-column label="Java类型" min-width="11%"> + <template slot-scope="scope"> + <el-select v-model="scope.row.javaType"> + <el-option label="Long" value="Long" /> + <el-option label="String" value="String" /> + <el-option label="Integer" value="Integer" /> + <el-option label="Double" value="Double" /> + <el-option label="BigDecimal" value="BigDecimal" /> + <el-option label="Date" value="Date" /> + <el-option label="Boolean" value="Boolean" /> + </el-select> + </template> + </el-table-column> + <el-table-column label="java属性" min-width="10%"> + <template slot-scope="scope"> + <el-input v-model="scope.row.javaField"></el-input> + </template> + </el-table-column> + + <el-table-column label="插入" min-width="5%"> + <template slot-scope="scope"> + <el-checkbox true-label="1" false-label="0" v-model="scope.row.isInsert"></el-checkbox> + </template> + </el-table-column> + <el-table-column label="编辑" min-width="5%"> + <template slot-scope="scope"> + <el-checkbox true-label="1" false-label="0" v-model="scope.row.isEdit"></el-checkbox> + </template> + </el-table-column> + <el-table-column label="列表" min-width="5%"> + <template slot-scope="scope"> + <el-checkbox true-label="1" false-label="0" v-model="scope.row.isList"></el-checkbox> + </template> + </el-table-column> + <el-table-column label="查询" min-width="5%"> + <template slot-scope="scope"> + <el-checkbox true-label="1" false-label="0" v-model="scope.row.isQuery"></el-checkbox> + </template> + </el-table-column> + <el-table-column label="查询方式" min-width="10%"> + <template slot-scope="scope"> + <el-select v-model="scope.row.queryType"> + <el-option label="=" value="EQ" /> + <el-option label="!=" value="NE" /> + <el-option label=">" value="GT" /> + <el-option label=">=" value="GTE" /> + <el-option label="<" value="LT" /> + <el-option label="<=" value="LTE" /> + <el-option label="LIKE" value="LIKE" /> + <el-option label="BETWEEN" value="BETWEEN" /> + </el-select> + </template> + </el-table-column> + <el-table-column label="必填" min-width="5%"> + <template slot-scope="scope"> + <el-checkbox true-label="1" false-label="0" v-model="scope.row.isRequired"></el-checkbox> + </template> + </el-table-column> + <el-table-column label="显示类型" min-width="12%"> + <template slot-scope="scope"> + <el-select v-model="scope.row.htmlType"> + <el-option label="文本框" value="input" /> + <el-option label="文本域" value="textarea" /> + <el-option label="下拉框" value="select" /> + <el-option label="单选框" value="radio" /> + <el-option label="复选框" value="checkbox" /> + <el-option label="日期控件" value="datetime" /> + <el-option label="图片上传" value="imageUpload" /> + <el-option label="文件上传" value="fileUpload" /> + <el-option label="富文本控件" value="editor" /> + </el-select> + </template> + </el-table-column> + <el-table-column label="字典类型" min-width="12%"> + <template slot-scope="scope"> + <el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择"> + <el-option + v-for="dict in dictOptions" + :key="dict.dictType" + :label="dict.dictName" + :value="dict.dictType"> + <span style="float: left">{{ dict.dictName }}</span> + <span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span> + </el-option> + </el-select> + </template> + </el-table-column> + </el-table> + </el-tab-pane> + <el-tab-pane label="生成信息" name="genInfo"> + <gen-info-form ref="genInfo" :info="info" :tables="tables" :menus="menus"/> + </el-tab-pane> + </el-tabs> + <el-form label-width="100px"> + <el-form-item style="text-align: center;margin-left:-100px;margin-top:10px;"> + <el-button type="primary" @click="submitForm()">提交</el-button> + <el-button @click="close()">返回</el-button> + </el-form-item> + </el-form> + </el-card> +</template> + +<script> +import { getGenTable, updateGenTable } from "@/api/tool/gen"; +import { optionselect as getDictOptionselect } from "@/api/system/dict/type"; +import { listMenu as getMenuTreeselect } from "@/api/system/menu"; +import basicInfoForm from "./basicInfoForm"; +import genInfoForm from "./genInfoForm"; +import Sortable from 'sortablejs' + +export default { + name: "GenEdit", + components: { + basicInfoForm, + genInfoForm + }, + data() { + return { + // 选中选项卡的 name + activeName: "columnInfo", + // 表格的高度 + tableHeight: document.documentElement.scrollHeight - 245 + "px", + // 表信息 + tables: [], + // 表列信息 + columns: [], + // 字典信息 + dictOptions: [], + // 菜单信息 + menus: [], + // 表详细信息 + info: {} + }; + }, + created() { + const tableId = this.$route.params && this.$route.params.tableId; + if (tableId) { + // 获取表详细信息 + getGenTable(tableId).then(res => { + this.columns = res.data.rows; + this.info = res.data.info; + this.tables = res.data.tables; + }); + /** 查询字典下拉列表 */ + getDictOptionselect().then(response => { + this.dictOptions = response.data; + }); + /** 查询菜单下拉列表 */ + getMenuTreeselect().then(response => { + this.menus = this.handleTree(response.data, "menuId"); + }); + } + }, + methods: { + /** 提交按钮 */ + submitForm() { + const basicForm = this.$refs.basicInfo.$refs.basicInfoForm; + const genForm = this.$refs.genInfo.$refs.genInfoForm; + Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => { + const validateResult = res.every(item => !!item); + if (validateResult) { + const genTable = Object.assign({}, basicForm.model, genForm.model); + genTable.columns = this.columns; + genTable.params = { + treeCode: genTable.treeCode, + treeName: genTable.treeName, + treeParentCode: genTable.treeParentCode, + parentMenuId: genTable.parentMenuId + }; + updateGenTable(genTable).then(res => { + this.$modal.msgSuccess(res.msg); + if (res.code === 200) { + this.close(); + } + }); + } else { + this.$modal.msgError("表单校验未通过,请重新检查提交内容"); + } + }); + }, + getFormPromise(form) { + return new Promise(resolve => { + form.validate(res => { + resolve(res); + }); + }); + }, + /** 关闭按钮 */ + close() { + const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: this.$route.query.pageNum } }; + this.$tab.closeOpenPage(obj); + } + }, + mounted() { + const el = this.$refs.dragTable.$el.querySelectorAll(".el-table__body-wrapper > table > tbody")[0]; + const sortable = Sortable.create(el, { + handle: ".allowDrag", + onEnd: evt => { + const targetRow = this.columns.splice(evt.oldIndex, 1)[0]; + this.columns.splice(evt.newIndex, 0, targetRow); + for (let index in this.columns) { + this.columns[index].sort = parseInt(index) + 1; + } + } + }); + } +}; +</script> diff --git a/ruoyi-ui/src/views/tool/gen/genInfoForm.vue b/ruoyi-ui/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 0000000..3f8d67f --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,299 @@ +<template> + <el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px"> + <el-row> + <el-col :span="12"> + <el-form-item prop="tplCategory"> + <span slot="label">生成模板</span> + <el-select v-model="info.tplCategory" @change="tplSelectChange"> + <el-option label="单表(增删改查)" value="crud" /> + <el-option label="树表(增删改查)" value="tree" /> + <el-option label="主子表(增删改查)" value="sub" /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item prop="packageName"> + <span slot="label"> + 生成包路径 + <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-input v-model="info.packageName" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="moduleName"> + <span slot="label"> + 生成模块名 + <el-tooltip content="可理解为子系统名,例如 system" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-input v-model="info.moduleName" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="businessName"> + <span slot="label"> + 生成业务名 + <el-tooltip content="可理解为功能英文名,例如 user" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-input v-model="info.businessName" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="functionName"> + <span slot="label"> + 生成功能名 + <el-tooltip content="用作类描述,例如 用户" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-input v-model="info.functionName" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item> + <span slot="label"> + 上级菜单 + <el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <treeselect + :append-to-body="true" + v-model="info.parentMenuId" + :options="menus" + :normalizer="normalizer" + :show-count="true" + placeholder="请选择系统菜单" + /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="genType"> + <span slot="label"> + 生成代码方式 + <el-tooltip content="默认为zip压缩包下载,也可以自定义生成路径" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-radio v-model="info.genType" label="0">zip压缩包</el-radio> + <el-radio v-model="info.genType" label="1">自定义路径</el-radio> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="info.genType == '1'"> + <el-form-item prop="genPath"> + <span slot="label"> + 自定义路径 + <el-tooltip content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-input v-model="info.genPath"> + <el-dropdown slot="append"> + <el-button type="primary"> + 最近路径快速选择 + <i class="el-icon-arrow-down el-icon--right"></i> + </el-button> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item @click.native="info.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + </el-input> + </el-form-item> + </el-col> + </el-row> + + <el-row v-show="info.tplCategory == 'tree'"> + <h4 class="form-header">其他信息</h4> + <el-col :span="12"> + <el-form-item> + <span slot="label"> + 树编码字段 + <el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-select v-model="info.treeCode" placeholder="请选择"> + <el-option + v-for="(column, index) in info.columns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item> + <span slot="label"> + 树父编码字段 + <el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-select v-model="info.treeParentCode" placeholder="请选择"> + <el-option + v-for="(column, index) in info.columns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item> + <span slot="label"> + 树名称字段 + <el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-select v-model="info.treeName" placeholder="请选择"> + <el-option + v-for="(column, index) in info.columns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + ></el-option> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row v-show="info.tplCategory == 'sub'"> + <h4 class="form-header">关联信息</h4> + <el-col :span="12"> + <el-form-item> + <span slot="label"> + 关联子表的表名 + <el-tooltip content="关联子表的表名, 如:sys_user" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange"> + <el-option + v-for="(table, index) in tables" + :key="index" + :label="table.tableName + ':' + table.tableComment" + :value="table.tableName" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item> + <span slot="label"> + 子表关联的外键名 + <el-tooltip content="子表关联的外键名, 如:user_id" placement="top"> + <i class="el-icon-question"></i> + </el-tooltip> + </span> + <el-select v-model="info.subTableFkName" placeholder="请选择"> + <el-option + v-for="(column, index) in subColumns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + ></el-option> + </el-select> + </el-form-item> + </el-col> + </el-row> + </el-form> +</template> + +<script> +import Treeselect from "@riophae/vue-treeselect"; +import "@riophae/vue-treeselect/dist/vue-treeselect.css"; + +export default { + components: { Treeselect }, + props: { + info: { + type: Object, + default: null + }, + tables: { + type: Array, + default: null + }, + menus: { + type: Array, + default: [] + }, + }, + data() { + return { + subColumns: [], + rules: { + tplCategory: [ + { required: true, message: "请选择生成模板", trigger: "blur" } + ], + packageName: [ + { required: true, message: "请输入生成包路径", trigger: "blur" } + ], + moduleName: [ + { required: true, message: "请输入生成模块名", trigger: "blur" } + ], + businessName: [ + { required: true, message: "请输入生成业务名", trigger: "blur" } + ], + functionName: [ + { required: true, message: "请输入生成功能名", trigger: "blur" } + ], + } + }; + }, + created() {}, + watch: { + 'info.subTableName': function(val) { + this.setSubTableColumns(val); + } + }, + methods: { + /** 转换菜单数据结构 */ + normalizer(node) { + if (node.children && !node.children.length) { + delete node.children; + } + return { + id: node.menuId, + label: node.menuName, + children: node.children + }; + }, + /** 选择子表名触发 */ + subSelectChange(value) { + this.info.subTableFkName = ''; + }, + /** 选择生成模板触发 */ + tplSelectChange(value) { + if(value !== 'sub') { + this.info.subTableName = ''; + this.info.subTableFkName = ''; + } + }, + /** 设置关联外键 */ + setSubTableColumns(value) { + for (var item in this.tables) { + const name = this.tables[item].tableName; + if (value === name) { + this.subColumns = this.tables[item].columns; + break; + } + } + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/tool/gen/importTable.vue b/ruoyi-ui/src/views/tool/gen/importTable.vue new file mode 100644 index 0000000..3ea9532 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/importTable.vue @@ -0,0 +1,120 @@ +<template> + <!-- 导入表 --> + <el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true"> + <el-form-item label="表名称" prop="tableName"> + <el-input + v-model="queryParams.tableName" + placeholder="请输入表名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="表描述" prop="tableComment"> + <el-input + v-model="queryParams.tableComment" + placeholder="请输入表描述" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + <el-row> + <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px"> + <el-table-column type="selection" width="55"></el-table-column> + <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column> + <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column> + <el-table-column prop="createTime" label="创建时间"></el-table-column> + <el-table-column prop="updateTime" label="更新时间"></el-table-column> + </el-table> + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </el-row> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="handleImportTable">确 定</el-button> + <el-button @click="visible = false">取 消</el-button> + </div> + </el-dialog> +</template> + +<script> +import { listDbTable, importTable } from "@/api/tool/gen"; +export default { + data() { + return { + // 遮罩层 + visible: false, + // 选中数组值 + tables: [], + // 总条数 + total: 0, + // 表数据 + dbTableList: [], + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + tableName: undefined, + tableComment: undefined + } + }; + }, + methods: { + // 显示弹框 + show() { + this.getList(); + this.visible = true; + }, + clickRow(row) { + this.$refs.table.toggleRowSelection(row); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.tables = selection.map(item => item.tableName); + }, + // 查询表数据 + getList() { + listDbTable(this.queryParams).then(res => { + if (res.code === 200) { + this.dbTableList = res.rows; + this.total = res.total; + } + }); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 导入按钮操作 */ + handleImportTable() { + const tableNames = this.tables.join(","); + if (tableNames == "") { + this.$modal.msgError("请选择要导入的表"); + return; + } + importTable({ tables: tableNames }).then(res => { + this.$modal.msgSuccess(res.msg); + if (res.code === 200) { + this.visible = false; + this.$emit("ok"); + } + }); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/tool/gen/index.vue b/ruoyi-ui/src/views/tool/gen/index.vue new file mode 100644 index 0000000..3f1f930 --- /dev/null +++ b/ruoyi-ui/src/views/tool/gen/index.vue @@ -0,0 +1,337 @@ +<template> + <div class="app-container"> + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="表名称" prop="tableName"> + <el-input + v-model="queryParams.tableName" + placeholder="请输入表名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="表描述" prop="tableComment"> + <el-input + v-model="queryParams.tableComment" + placeholder="请输入表描述" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="创建时间"> + <el-date-picker + v-model="dateRange" + style="width: 240px" + value-format="yyyy-MM-dd" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-download" + size="mini" + @click="handleGenTable" + v-hasPermi="['tool:gen:code']" + >生成</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-upload" + size="mini" + @click="openImportTable" + v-hasPermi="['tool:gen:import']" + >导入</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + icon="el-icon-edit" + size="mini" + :disabled="single" + @click="handleEditTable" + v-hasPermi="['tool:gen:edit']" + >修改</el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="danger" + plain + icon="el-icon-delete" + size="mini" + :disabled="multiple" + @click="handleDelete" + v-hasPermi="['tool:gen:remove']" + >删除</el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" align="center" width="55"></el-table-column> + <el-table-column label="序号" type="index" width="50" align="center"> + <template slot-scope="scope"> + <span>{{(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1}}</span> + </template> + </el-table-column> + <el-table-column + label="表名称" + align="center" + prop="tableName" + :show-overflow-tooltip="true" + width="120" + /> + <el-table-column + label="表描述" + align="center" + prop="tableComment" + :show-overflow-tooltip="true" + width="120" + /> + <el-table-column + label="实体" + align="center" + prop="className" + :show-overflow-tooltip="true" + width="120" + /> + <el-table-column label="创建时间" align="center" prop="createTime" width="160" /> + <el-table-column label="更新时间" align="center" prop="updateTime" width="160" /> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template slot-scope="scope"> + <el-button + type="text" + size="small" + icon="el-icon-view" + @click="handlePreview(scope.row)" + v-hasPermi="['tool:gen:preview']" + >预览</el-button> + <el-button + type="text" + size="small" + icon="el-icon-edit" + @click="handleEditTable(scope.row)" + v-hasPermi="['tool:gen:edit']" + >编辑</el-button> + <el-button + type="text" + size="small" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['tool:gen:remove']" + >删除</el-button> + <el-button + type="text" + size="small" + icon="el-icon-refresh" + @click="handleSynchDb(scope.row)" + v-hasPermi="['tool:gen:edit']" + >同步</el-button> + <el-button + type="text" + size="small" + icon="el-icon-download" + @click="handleGenTable(scope.row)" + v-hasPermi="['tool:gen:code']" + >生成代码</el-button> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total>0" + :total="total" + :page.sync="queryParams.pageNum" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + <!-- 预览界面 --> + <el-dialog :title="preview.title" :visible.sync="preview.open" width="80%" top="5vh" append-to-body class="scrollbar"> + <el-tabs v-model="preview.activeName"> + <el-tab-pane + v-for="(value, key) in preview.data" + :label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" + :name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))" + :key="key" + > + <el-link :underline="false" icon="el-icon-document-copy" v-clipboard:copy="value" v-clipboard:success="clipboardSuccess" style="float:right">复制</el-link> + <pre><code class="hljs" v-html="highlightedCode(value, key)"></code></pre> + </el-tab-pane> + </el-tabs> + </el-dialog> + <import-table ref="import" @ok="handleQuery" /> + </div> +</template> + +<script> +import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen"; +import importTable from "./importTable"; +import hljs from "highlight.js/lib/highlight"; +import "highlight.js/styles/github-gist.css"; +hljs.registerLanguage("java", require("highlight.js/lib/languages/java")); +hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml")); +hljs.registerLanguage("html", require("highlight.js/lib/languages/xml")); +hljs.registerLanguage("vue", require("highlight.js/lib/languages/xml")); +hljs.registerLanguage("javascript", require("highlight.js/lib/languages/javascript")); +hljs.registerLanguage("sql", require("highlight.js/lib/languages/sql")); + +export default { + name: "Gen", + components: { importTable }, + data() { + return { + // 遮罩层 + loading: true, + // 唯一标识符 + uniqueId: "", + // 选中数组 + ids: [], + // 选中表数组 + tableNames: [], + // 非单个禁用 + single: true, + // 非多个禁用 + multiple: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 表数据 + tableList: [], + // 日期范围 + dateRange: "", + // 查询参数 + queryParams: { + pageNum: 1, + pageSize: 10, + tableName: undefined, + tableComment: undefined + }, + // 预览参数 + preview: { + open: false, + title: "代码预览", + data: {}, + activeName: "domain.java" + } + }; + }, + created() { + this.getList(); + }, + activated() { + const time = this.$route.query.t; + if (time != null && time != this.uniqueId) { + this.uniqueId = time; + this.queryParams.pageNum = Number(this.$route.query.pageNum); + this.getList(); + } + }, + methods: { + /** 查询表集合 */ + getList() { + this.loading = true; + listTable(this.addDateRange(this.queryParams, this.dateRange)).then(response => { + this.tableList = response.rows; + this.total = response.total; + this.loading = false; + } + ); + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNum = 1; + this.getList(); + }, + /** 生成代码操作 */ + handleGenTable(row) { + const tableNames = row.tableName || this.tableNames; + if (tableNames == "") { + this.$modal.msgError("请选择要生成的数据"); + return; + } + if(row.genType === "1") { + genCode(row.tableName).then(response => { + this.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath); + }); + } else { + this.$download.zip("/tool/gen/batchGenCode?tables=" + tableNames, "ruoyi.zip"); + } + }, + /** 同步数据库操作 */ + handleSynchDb(row) { + const tableName = row.tableName; + this.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function() { + return synchDb(tableName); + }).then(() => { + this.$modal.msgSuccess("同步成功"); + }).catch(() => {}); + }, + /** 打开导入表弹窗 */ + openImportTable() { + this.$refs.import.show(); + }, + /** 重置按钮操作 */ + resetQuery() { + this.dateRange = []; + this.resetForm("queryForm"); + this.handleQuery(); + }, + /** 预览按钮 */ + handlePreview(row) { + previewTable(row.tableId).then(response => { + this.preview.data = response.data; + this.preview.open = true; + this.preview.activeName = "domain.java"; + }); + }, + /** 高亮显示 */ + highlightedCode(code, key) { + const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm")); + var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length); + const result = hljs.highlight(language, code || "", true); + return result.value || ' '; + }, + /** 复制代码成功 */ + clipboardSuccess() { + this.$modal.msgSuccess("复制成功"); + }, + // 多选框选中数据 + handleSelectionChange(selection) { + this.ids = selection.map(item => item.tableId); + this.tableNames = selection.map(item => item.tableName); + this.single = selection.length != 1; + this.multiple = !selection.length; + }, + /** 修改按钮操作 */ + handleEditTable(row) { + const tableId = row.tableId || this.ids[0]; + const tableName = row.tableName || this.tableNames[0]; + const params = { pageNum: this.queryParams.pageNum }; + this.$tab.openPage("修改[" + tableName + "]生成配置", '/tool/gen-edit/index/' + tableId, params); + }, + /** 删除按钮操作 */ + handleDelete(row) { + const tableIds = row.tableId || this.ids; + this.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?').then(function() { + return delTable(tableIds); + }).then(() => { + this.getList(); + this.$modal.msgSuccess("删除成功"); + }).catch(() => {}); + } + } +}; +</script> diff --git a/ruoyi-ui/src/views/tool/swagger/index.vue b/ruoyi-ui/src/views/tool/swagger/index.vue new file mode 100644 index 0000000..b8becc6 --- /dev/null +++ b/ruoyi-ui/src/views/tool/swagger/index.vue @@ -0,0 +1,15 @@ +<template> + <i-frame :src="url" /> +</template> +<script> +import iFrame from "@/components/iFrame/index"; +export default { + name: "Swagger", + components: { iFrame }, + data() { + return { + url: process.env.VUE_APP_BASE_API + "/swagger-ui/index.html" + }; + }, +}; +</script> diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js new file mode 100644 index 0000000..5e30cf7 --- /dev/null +++ b/ruoyi-ui/vue.config.js @@ -0,0 +1,134 @@ +'use strict' +const path = require('path') + +function resolve(dir) { + return path.join(__dirname, dir) +} + +const CompressionPlugin = require('compression-webpack-plugin') + +const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 + +const port = process.env.port || process.env.npm_config_port || 80 // 端口 + +// vue.config.js 配置说明 +//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions +// 这里只列一部分,具体配置参考文档 +module.exports = { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + publicPath: process.env.NODE_ENV === "production" ? "/" : "/", + // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) + outputDir: 'dist', + // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) + assetsDir: 'static', + // 是否开启eslint保存检测,有效值:ture | false | 'error' + lintOnSave: process.env.NODE_ENV === 'development', + // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 + productionSourceMap: false, + // webpack-dev-server 相关配置 + devServer: { + host: '0.0.0.0', + port: port, + open: true, + proxy: { + // detail: https://cli.vuejs.org/config/#devserver-proxy + [process.env.VUE_APP_BASE_API]: { + target: `http://localhost:8081`, + changeOrigin: true, + pathRewrite: { + ['^' + process.env.VUE_APP_BASE_API]: '' + } + } + }, + disableHostCheck: true + }, + css: { + loaderOptions: { + sass: { + sassOptions: { outputStyle: "expanded" } + } + } + }, + configureWebpack: { + name: name, + resolve: { + alias: { + '@': resolve('src') + } + }, + plugins: [ + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + new CompressionPlugin({ + cache: false, // 不启用文件缓存 + test: /\.(js|css|html)?$/i, // 压缩文件格式 + filename: '[path].gz[query]', // 压缩后的文件名 + algorithm: 'gzip', // 使用gzip压缩 + minRatio: 0.8 // 压缩率小于1才会压缩 + }) + ], + }, + chainWebpack(config) { + config.plugins.delete('preload') // TODO: need test + config.plugins.delete('prefetch') // TODO: need test + + // set svg-sprite-loader + config.module + .rule('svg') + .exclude.add(resolve('src/assets/icons')) + .end() + config.module + .rule('icons') + .test(/\.svg$/) + .include.add(resolve('src/assets/icons')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ + symbolId: 'icon-[name]' + }) + .end() + + config.when(process.env.NODE_ENV !== 'development', config => { + config + .plugin('ScriptExtHtmlWebpackPlugin') + .after('html') + .use('script-ext-html-webpack-plugin', [{ + // `runtime` must same as runtimeChunk name. default is `runtime` + inline: /runtime\..*\.js$/ + }]) + .end() + + config.optimization.splitChunks({ + chunks: 'all', + cacheGroups: { + libs: { + name: 'chunk-libs', + test: /[\\/]node_modules[\\/]/, + priority: 10, + chunks: 'initial' // only package third parties that are initially dependent + }, + elementUI: { + name: 'chunk-elementUI', // split elementUI into a single package + test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm + priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app + }, + commons: { + name: 'chunk-commons', + test: resolve('src/components'), // can customize your rules + minChunks: 3, // minimum common number + priority: 5, + reuseExistingChunk: true + } + } + }) + + config.optimization.runtimeChunk('single'), + { + from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件 + to: './' //到根目录下 + } + }) + } +} -- Gitblit v1.7.1