From 76cdec07b746d54c0d98b0ae9677d37dd5fddfe4 Mon Sep 17 00:00:00 2001 From: Administrator <895938232@qq.com> Date: Tue, 3 May 2022 14:15:23 +0800 Subject: [PATCH] hm --- .gitignore | 29 + LICENSE | 201 + README.md | 84 + health.sql | 80 + pom.xml | 239 ++ .../health/config/CrossXssFilter.java | 141 + .../health/config/ExceptionManager.java | 43 + .../beansprout/health/config/HTMLWrapper.java | 556 +++ .../MySimpleMappingExceptionResolver.java | 77 + .../health/config/TaskExecutorConfig.java | 30 + .../config/XssHttpServletRequestWrapper.java | 198 + .../health/constant/SysConstant.java | 31 + .../health/controller/BaseController.java | 36 + .../health/controller/HealthController.java | 74 + .../health/controller/MailController.java | 89 + .../health/controller/PageController.java | 71 + .../health/controller/UserController.java | 74 + .../health/mapper/TBodyInfoMapper.java | 43 + .../health/mapper/THealthConfigMapper.java | 12 + .../beansprout/health/mapper/TUserMapper.java | 36 + .../health/model/dto/BodyInfoQuery.java | 28 + .../health/model/dto/BodyInfoSaveDto.java | 46 + .../beansprout/health/model/dto/PageDto.java | 32 + .../health/model/dto/UserLoginDto.java | 33 + .../health/model/dto/UserRegisterDto.java | 36 + .../health/model/dto/UserUpdateInfoDto.java | 39 + .../model/dto/UserUpdatePasswordDto.java | 33 + .../health/model/entity/BaseEntity.java | 24 + .../health/model/entity/BaseInsertEntity.java | 30 + .../health/model/entity/BaseUpdateEntity.java | 38 + .../health/model/entity/TBodyInfo.java | 56 + .../health/model/entity/THealthConfig.java | 35 + .../beansprout/health/model/entity/TUser.java | 30 + .../beansprout/health/model/vo/AuthVo.java | 26 + .../health/model/vo/BodyInfoDetailVo.java | 65 + .../health/model/vo/BodyInfoStatisticsVo.java | 43 + .../health/model/vo/BusinessException.java | 34 + .../beansprout/health/model/vo/PageVo.java | 73 + .../top/beansprout/health/model/vo/R.java | 110 + .../beansprout/health/model/vo/RequestVo.java | 31 + .../health/model/vo/UserInfoVo.java | 36 + .../health/model/vo/UserLoginVo.java | 33 + .../health/service/HealthService.java | 40 + .../health/service/UserService.java | 37 + .../service/impl/HealthServiceImpl.java | 299 ++ .../health/service/impl/UserServiceImpl.java | 151 + .../health/util/CollectionUtils.java | 69 + .../top/beansprout/health/util/DateUtils.java | 286 ++ .../top/beansprout/health/util/JsonUtils.java | 57 + .../top/beansprout/health/util/MailUtils.java | 74 + .../beansprout/health/util/PublicUtils.java | 150 + src/main/resources/application.properties | 4 + src/main/resources/log4j.properties | 32 + src/main/resources/mapper/TBodyInfoMapper.xml | 27 + .../resources/mapper/THealthConfigMapper.xml | 12 + src/main/resources/mapper/TUserMapper.xml | 17 + src/main/resources/spring/mybatis-config.xml | 16 + src/main/resources/spring/spring-dao.xml | 55 + src/main/resources/spring/spring-mvc.xml | 51 + src/main/resources/spring/spring-service.xml | 48 + src/main/resources/spring/spring.xml | 25 + src/main/webapp/WEB-INF/error/401.jsp | 71 + src/main/webapp/WEB-INF/error/403.jsp | 90 + src/main/webapp/WEB-INF/error/404.jsp | 60 + src/main/webapp/WEB-INF/error/500.jsp | 12 + src/main/webapp/WEB-INF/view/base.jsp | 8 + .../webapp/WEB-INF/view/bodyInfoInput.jsp | 215 ++ src/main/webapp/WEB-INF/view/bodyInofList.jsp | 234 ++ .../WEB-INF/view/bodyInofStatistics.jsp | 175 + src/main/webapp/WEB-INF/view/home.jsp | 181 + src/main/webapp/WEB-INF/view/register.jsp | 150 + src/main/webapp/WEB-INF/view/top.jsp | 55 + .../webapp/WEB-INF/view/updatePassword.jsp | 120 + src/main/webapp/WEB-INF/view/userInfo.jsp | 200 + src/main/webapp/WEB-INF/web.xml | 89 + src/main/webapp/index.jsp | 128 + src/main/webapp/static/css/base.css | 3381 +++++++++++++++++ src/main/webapp/static/css/login.css | 120 + .../css/util/bootstrap-datetimepicker.min.css | 9 + src/main/webapp/static/imgs/default.png | Bin 0 -> 54350 bytes src/main/webapp/static/imgs/login.jpg | Bin 0 -> 15369 bytes src/main/webapp/static/js/common-css.js | 13 + src/main/webapp/static/js/common-js.js | 43 + src/main/webapp/static/js/login.js | 175 + .../js/util/bootstrap-datetimepicker.min.js | 1 + .../js/util/bootstrap-datetimepicker.zh-CN.js | 16 + .../static/js/util/jquery.validate.min.js | 4 + version_diary | 10 + 88 files changed, 10065 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 health.sql create mode 100644 pom.xml create mode 100644 src/main/java/top/beansprout/health/config/CrossXssFilter.java create mode 100644 src/main/java/top/beansprout/health/config/ExceptionManager.java create mode 100644 src/main/java/top/beansprout/health/config/HTMLWrapper.java create mode 100644 src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java create mode 100644 src/main/java/top/beansprout/health/config/TaskExecutorConfig.java create mode 100644 src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java create mode 100644 src/main/java/top/beansprout/health/constant/SysConstant.java create mode 100644 src/main/java/top/beansprout/health/controller/BaseController.java create mode 100644 src/main/java/top/beansprout/health/controller/HealthController.java create mode 100644 src/main/java/top/beansprout/health/controller/MailController.java create mode 100644 src/main/java/top/beansprout/health/controller/PageController.java create mode 100644 src/main/java/top/beansprout/health/controller/UserController.java create mode 100644 src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java create mode 100644 src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java create mode 100644 src/main/java/top/beansprout/health/mapper/TUserMapper.java create mode 100644 src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java create mode 100644 src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java create mode 100644 src/main/java/top/beansprout/health/model/dto/PageDto.java create mode 100644 src/main/java/top/beansprout/health/model/dto/UserLoginDto.java create mode 100644 src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java create mode 100644 src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java create mode 100644 src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java create mode 100644 src/main/java/top/beansprout/health/model/entity/BaseEntity.java create mode 100644 src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java create mode 100644 src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java create mode 100644 src/main/java/top/beansprout/health/model/entity/TBodyInfo.java create mode 100644 src/main/java/top/beansprout/health/model/entity/THealthConfig.java create mode 100644 src/main/java/top/beansprout/health/model/entity/TUser.java create mode 100644 src/main/java/top/beansprout/health/model/vo/AuthVo.java create mode 100644 src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java create mode 100644 src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java create mode 100644 src/main/java/top/beansprout/health/model/vo/BusinessException.java create mode 100644 src/main/java/top/beansprout/health/model/vo/PageVo.java create mode 100644 src/main/java/top/beansprout/health/model/vo/R.java create mode 100644 src/main/java/top/beansprout/health/model/vo/RequestVo.java create mode 100644 src/main/java/top/beansprout/health/model/vo/UserInfoVo.java create mode 100644 src/main/java/top/beansprout/health/model/vo/UserLoginVo.java create mode 100644 src/main/java/top/beansprout/health/service/HealthService.java create mode 100644 src/main/java/top/beansprout/health/service/UserService.java create mode 100644 src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java create mode 100644 src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java create mode 100644 src/main/java/top/beansprout/health/util/CollectionUtils.java create mode 100644 src/main/java/top/beansprout/health/util/DateUtils.java create mode 100644 src/main/java/top/beansprout/health/util/JsonUtils.java create mode 100644 src/main/java/top/beansprout/health/util/MailUtils.java create mode 100644 src/main/java/top/beansprout/health/util/PublicUtils.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/log4j.properties create mode 100644 src/main/resources/mapper/TBodyInfoMapper.xml create mode 100644 src/main/resources/mapper/THealthConfigMapper.xml create mode 100644 src/main/resources/mapper/TUserMapper.xml create mode 100644 src/main/resources/spring/mybatis-config.xml create mode 100644 src/main/resources/spring/spring-dao.xml create mode 100644 src/main/resources/spring/spring-mvc.xml create mode 100644 src/main/resources/spring/spring-service.xml create mode 100644 src/main/resources/spring/spring.xml create mode 100644 src/main/webapp/WEB-INF/error/401.jsp create mode 100644 src/main/webapp/WEB-INF/error/403.jsp create mode 100644 src/main/webapp/WEB-INF/error/404.jsp create mode 100644 src/main/webapp/WEB-INF/error/500.jsp create mode 100644 src/main/webapp/WEB-INF/view/base.jsp create mode 100644 src/main/webapp/WEB-INF/view/bodyInfoInput.jsp create mode 100644 src/main/webapp/WEB-INF/view/bodyInofList.jsp create mode 100644 src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp create mode 100644 src/main/webapp/WEB-INF/view/home.jsp create mode 100644 src/main/webapp/WEB-INF/view/register.jsp create mode 100644 src/main/webapp/WEB-INF/view/top.jsp create mode 100644 src/main/webapp/WEB-INF/view/updatePassword.jsp create mode 100644 src/main/webapp/WEB-INF/view/userInfo.jsp create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.jsp create mode 100644 src/main/webapp/static/css/base.css create mode 100644 src/main/webapp/static/css/login.css create mode 100644 src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css create mode 100644 src/main/webapp/static/imgs/default.png create mode 100644 src/main/webapp/static/imgs/login.jpg create mode 100644 src/main/webapp/static/js/common-css.js create mode 100644 src/main/webapp/static/js/common-js.js create mode 100644 src/main/webapp/static/js/login.js create mode 100644 src/main/webapp/static/js/util/bootstrap-datetimepicker.min.js create mode 100644 src/main/webapp/static/js/util/bootstrap-datetimepicker.zh-CN.js create mode 100644 src/main/webapp/static/js/util/jquery.validate.min.js create mode 100644 version_diary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b1d0e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Compiled class file +*.class +classes/ + +# Log file +*.log +target/ + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar +*.iml + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea/ +.settings/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..639c818 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# health-manager + +#### 介绍 +个人健康信息管理系统 + +#### 软件架构 +spring+springmvc+mybatis+mysql+jsp+bootstrap + +#### 安装教程 +1. 数据库导入sql文件 +2. eclipse导入maven项目 +3. 设置项目project facets,配置版本Dynamic Web Model为[3.0,) +4. 配置web容器,导入项目到容器中,启动容器 + +#### 使用说明 +1. 访问地址:(http://localhost:{web容器端口}/{项目名称}/) + +#### 项目效果 +![alt 登录](https://service.beansprout.top/image/health-manager/login.jpg) +![alt 首页](https://service.beansprout.top/image/health-manager/home.jpg) +![alt 健康列表](https://service.beansprout.top/image/health-manager/health_list.jpg) +![alt 健康统计](https://service.beansprout.top/image/health-manager/health_statistics.jpg) + +#### 附录 +##### Mybatis字段类型映射 +``` +JDBCType | JavaType +---------------------------- +CHAR String +VARCHAR String +LONGVARCHAR String +NUMERIC java.math.BigDecimal +DECIMAL java.math.BigDecimal +BIT boolean +BOOLEAN boolean +TINYINT byte +SMALLINT short +INTEGER int +BIGINT long +REAL float +FLOAT double +DOUBLE double +BINARY byte[] +VARBINARY byte[] +LONGVARBINARY byte[] +DATE java.sql.Date +TIME java.sql.Time +TIMESTAMP java.sql.Timestamp +CLOB Clob +BLOB Blob +ARRAY Array +DISTINCT mapping of underlying type +STRUCT Struct +REF Ref +DATALINK java.net.URL[color=red][/color] +``` + +##### SpringMVC 入参校验注解 +``` +校验注解 可校验类型 具体类型 +@AssertTrue Boolean、boolean 属性必须是true +@AssertFalse Boolean、boolean 属性必须是false +@Null 基本类型除外 属性必须为null +@NotNull 基本类型除外 属性必须不能为null +@NotEmpty CharSequence、Collection、Map 属性不能为null,字符串和集合长度不能为0(无法校验空字符串) +@NotBlank CharSequence 属性不能为null,并不能为空字符串 +@Size CharSequence、Collection、Map 属性长度必须在指定范围 +@Length CharSequence 属性长度必须在指定范围 +@Min Number 属性必须大于指定最小值 +@Max Number 属性必须小于指定最大值 +@Range Number 属性在指定返回内 +@Past Date、Calender 属性时间必须大于当前时间 +@Future Date、Calender 属性时间必须小于当前时间 +@Email CharSequence 属性必须是合法邮件格式 +@Pattern CharSequence 属性必须匹配正则表达式 +``` + +#### 码云特技 +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目 +5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) \ No newline at end of file diff --git a/health.sql b/health.sql new file mode 100644 index 0000000..496822c --- /dev/null +++ b/health.sql @@ -0,0 +1,80 @@ +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1 + Source Server Type : MySQL + Source Server Version : 50644 + Source Host : localhost:3306 + Source Schema : health + + Target Server Type : MySQL + Target Server Version : 50644 + File Encoding : 65001 + + Date: 03/05/2020 15:46:09 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_body_info +-- ---------------------------- +DROP TABLE IF EXISTS `t_body_info`; +CREATE TABLE `t_body_info` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `creator` int(11) NULL DEFAULT NULL COMMENT '创建id', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `updater` int(11) NULL DEFAULT NULL COMMENT '修改id', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `low_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '舒张压', + `high_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '收缩压', + `heart_rate` int(10) NULL DEFAULT NULL COMMENT '心率', + `temperature` double(10, 2) NULL DEFAULT NULL COMMENT '体温', + `appetite` int(11) NULL DEFAULT NULL COMMENT '食欲', + `weight` double(10, 2) NULL DEFAULT NULL COMMENT '体重', + `number_of_step` bigint(20) NULL DEFAULT NULL COMMENT '步数', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '身体信息表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for t_health_config +-- ---------------------------- +DROP TABLE IF EXISTS `t_health_config`; +CREATE TABLE `t_health_config` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `creator` int(11) NULL DEFAULT NULL COMMENT '创建人id', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `updater` int(11) NULL DEFAULT NULL COMMENT '修改人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `min_low_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最小低血压', + `max_low_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最高低血压', + `min_high_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最小高血压', + `max_high_blood_pressure` int(10) NULL DEFAULT NULL COMMENT '最大高血压', + `min_heart_rate` int(10) NULL DEFAULT NULL COMMENT '最小心率', + `max_heart_rate` int(10) NULL DEFAULT NULL COMMENT '最大心率', + `min_temperature` double(10, 2) NULL DEFAULT NULL COMMENT '最低体温', + `max_temperature` double(10, 2) NULL DEFAULT NULL COMMENT '最高体温', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '健康信息配置表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for t_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_user`; +CREATE TABLE `t_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `creator` int(11) NULL DEFAULT NULL COMMENT '创建人', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `updater` int(11) NULL DEFAULT NULL COMMENT '修改人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `nick_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '用户昵称', + `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '登录账户', + `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '登录密码', + `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '邮箱', + `head_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '头像', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `user_name`(`user_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '用户表' ROW_FORMAT = Compact; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..826ec24 --- /dev/null +++ b/pom.xml @@ -0,0 +1,239 @@ + + 4.0.0 + top.beansprout + health + 0.0.1-SNAPSHOT + war + health-manager + 个人健康信息管理系统 + + + UTF-8 + 1.8 + 5.1.8.RELEASE + 2.10.3 + + + + + + org.springframework + spring-core + ${spring.version} + + + + org.springframework + spring-context + ${spring.version} + + + + + org.springframework + spring-tx + ${spring.version} + + + + + org.springframework + spring-webmvc + ${spring.version} + + + + org.springframework + spring-web + ${spring.version} + + + + org.springframework + spring-context-support + ${spring.version} + + + + + org.springframework + spring-aspects + ${spring.version} + + + + org.springframework + spring-aop + ${spring.version} + + + + aopalliance + aopalliance + 1.0 + + + + org.aspectj + aspectjrt + 1.6.8 + + + org.aspectj + aspectjweaver + 1.6.8 + + + + + org.springframework + spring-test + ${spring.version} + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + javax.servlet.jsp + jsp-api + 2.2 + provided + + + javax.servlet.jsp.jstl + jstl-api + 1.2 + + + org.glassfish.web + jstl-impl + 1.2 + + + + + org.springframework + spring-orm + ${spring.version} + + + + org.springframework + spring-jdbc + ${spring.version} + + + mysql + mysql-connector-java + 8.0.16 + + + com.alibaba + druid + 1.1.21 + + + org.mybatis + mybatis + 3.5.3 + + + org.mybatis + mybatis-spring + 2.0.4 + + + + com.github.pagehelper + pagehelper + 5.1.0 + + + + + org.projectlombok + lombok + 1.18.8 + provider + + + + org.slf4j + slf4j-log4j12 + 1.7.30 + + + org.slf4j + jcl-over-slf4j + 1.7.30 + + + org.slf4j + jul-to-slf4j + 1.7.30 + + + + org.slf4j + slf4j-api + 1.7.30 + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + org.apache.commons + commons-lang3 + 3.7 + + + + org.hibernate.validator + hibernate-validator + 6.0.7.Final + + + commons-io + commons-io + 2.6 + + + commons-fileupload + commons-fileupload + 1.4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/config/CrossXssFilter.java b/src/main/java/top/beansprout/health/config/CrossXssFilter.java new file mode 100644 index 0000000..cb665a1 --- /dev/null +++ b/src/main/java/top/beansprout/health/config/CrossXssFilter.java @@ -0,0 +1,141 @@ +package top.beansprout.health.config; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +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 org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; +import top.beansprout.health.constant.SysConstant; +import top.beansprout.health.model.vo.RequestVo; +import top.beansprout.health.model.vo.RequestVo.RequestVoBuilder; +import top.beansprout.health.model.vo.UserLoginVo; +import top.beansprout.health.util.JsonUtils; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: CrossXssFilter

+ *

Description: 对用户输入的表单信息进行检测过滤

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/22 22:47 + */ +@Slf4j +public class CrossXssFilter implements Filter { + + /** 忽略资源地址 **/ + private final String[] ignores = new String[] { "/druid", "/static" }; + /** 忽略页面地址 **/ + private final String[] filtrationViews = new String[] { "/login", "/user/login", "/register", "/user/register", + "/logout", "/user/logout" }; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) + throws IOException, ServletException { + final HttpServletRequest request = init(servletRequest); + if (!isRequestValid(request)) { + chain.doFilter(servletRequest, servletResponse); + return; + } + servletRequest.setCharacterEncoding("UTF-8"); + servletResponse.setContentType("text/html;charset=utf-8"); + // sql,xss过滤 + log.info("URI:{}", request.getRequestURI()); + log.info("METHOD:{}", request.getMethod()); + log.info("ParameterMap:{}", JsonUtils.toJson(request.getParameterMap())); + if (isOauthValid(request)) { + // 获取身份 + final Object user = request.getSession().getAttribute(SysConstant.INIT_FIELD_USER_VO); + if (PublicUtils.isBlank(user)) { + final HttpServletResponse response = (HttpServletResponse) servletResponse; + if ((request.getHeader("x-requested-with") != null) + && "XMLHttpRequest".equals(request.getHeader("x-requested-with"))) { + // ajax请求 + response.setHeader("sessionstatus", "timeout"); + response.setStatus(403); + response.addHeader("loginPath", "login"); + chain.doFilter(request, response); + return; + } + // 页面请求 + response.setHeader("X-Frame-Options", "DENY"); + response.sendRedirect("login"); + return; + } + } + final XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper(request); + chain.doFilter(xssHttpServletRequestWrapper, servletResponse); + } + + /** 初始化 **/ + private HttpServletRequest init(ServletRequest servletRequest) { + final HttpServletRequest request = (HttpServletRequest) servletRequest; + + final String basePath = PublicUtils.join(request.getScheme(), "://", request.getServerName(), + ":" + request.getServerPort(), request.getContextPath(), "/"); + + final RequestVoBuilder requestVo = RequestVo.builder().basePath(basePath); + final Object userStr = request.getSession().getAttribute(SysConstant.INIT_FIELD_USER_VO); + if (PublicUtils.isNotBlank(userStr)) { + requestVo.user((UserLoginVo) userStr); + } + request.setAttribute(SysConstant.INIT_FIELD_REQUEST_VO, JsonUtils.toJson(requestVo.build())); + return request; + } + + /** 校验合法地址请求 **/ + private boolean isRequestValid(HttpServletRequest request) { + URI uri; + try { + uri = new URI(request.getRequestURL().toString()); + } catch (final URISyntaxException ex) { + return false; + } + + if (uri.getHost() == null) + return false; + if (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https")) + return false; + // 忽略指定地址 + final String path = StringUtils.removeStart(uri.getPath(), request.getContextPath()); + for (final String ignore : ignores) { + if (path.startsWith(ignore)) + return false; + } + // 忽略swagger的根路径 + if (path.equalsIgnoreCase("/")) + return false; + return true; + } + + /** 是否校验合法身份请求 **/ + private boolean isOauthValid(HttpServletRequest request) { + // 忽略指定地址 + final String path = StringUtils.removeStart(request.getRequestURI(), request.getContextPath()); + for (final String filtrationView : filtrationViews) { + if (path.startsWith(filtrationView)) + return false; + } + return true; + } + + @Override + public void destroy() { + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/config/ExceptionManager.java b/src/main/java/top/beansprout/health/config/ExceptionManager.java new file mode 100644 index 0000000..1afb95e --- /dev/null +++ b/src/main/java/top/beansprout/health/config/ExceptionManager.java @@ -0,0 +1,43 @@ +package top.beansprout.health.config; + +import java.util.List; + +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import top.beansprout.health.model.vo.R; + +/** + *

Title: ExceptionManager

+ *

Description:

+ * @author cyy + * @date 2020年4月28日 + */ +@ControllerAdvice +public class ExceptionManager { + + @ResponseBody + @ExceptionHandler({ BindException.class, MethodArgumentNotValidException.class }) + public R handleException(Exception e) { + List fieldErrs = null; + if (e instanceof BindException) { + fieldErrs = ((BindException) e).getBindingResult().getFieldErrors(); + } + if (e instanceof MethodArgumentNotValidException) { + fieldErrs = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors(); + } + Object r = e.getMessage(); + if (fieldErrs != null) { + for (final FieldError err : fieldErrs) { + r = err.getDefaultMessage(); + break; + } + } + return R.budil().result(false).message(r.toString()).data(r); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/config/HTMLWrapper.java b/src/main/java/top/beansprout/health/config/HTMLWrapper.java new file mode 100644 index 0000000..3d4eaa1 --- /dev/null +++ b/src/main/java/top/beansprout/health/config/HTMLWrapper.java @@ -0,0 +1,556 @@ +package top.beansprout.health.config; + +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; + +import lombok.extern.slf4j.Slf4j; + +/** + *

Title: HTMLWrapper

+ *

Description: HTML内容过滤

+ * HTML filtering utility for protecting against XSS (Cross Site Scripting). + * + * This code is licensed LGPLv3 + * + * This code is a Java port of the original work in PHP by Cal Hendersen. + * http://code.iamcal.com/php/lib_filter/ + * + * The trickiest part of the translation was handling the differences in regex handling + * between PHP and Java. These resources were helpful in the process: + * + * http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html + * http://us2.php.net/manual/en/reference.pcre.pattern.modifiers.php + * http://www.regular-expressions.info/modifiers.html + * + * A note on naming conventions: instance variables are prefixed with a "v"; global + * constants are in all caps. + * + * Sample use: + * String input = ... + * String clean = new HTMLWrapper().filter( input ); + * + * The class is not thread safe. Create a new instance if in doubt. + * + * If you find bugs or have suggestions on improvement (especially regarding + * performance), please contact us. The latest version of this + * source, and our contact details, can be found at http://xss-html-filter.sf.net + * + * @author beansprout + * @date 2020/3/22 23:06 + * @version 1.0 + */ +@Slf4j +public final class HTMLWrapper { + + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("<"); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + private boolean vDebug = false; + /** + * flag determining whether to try to make tags when presented with "unbalanced" + * angle brackets (e.g. "" becomes " text "). If set to false, + * unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLWrapper() { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[]{"img"}; + vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"}; + vDisallowed = new String[]{}; + vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp. + vProtocolAtts = new String[]{"src", "href"}; + vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"}; + vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"}; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = true; + } + + /** + * Set debug flag to true. Otherwise use default settings. See the default constructor. + * + * @param debug turn debug on with a true argument + */ + public HTMLWrapper(final boolean debug) { + this(); + vDebug = debug; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLWrapper(final Map conf) { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() { + vTagCounts.clear(); + } + + private void debug(final String msg) { + if (vDebug) { + log.info(msg); + } + } + + //--------------------------------------------------------------- + // 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; + + debug("************************************************"); + debug(" INPUT: " + input); + + s = escapeComments(s); + debug(" escapeComments: " + s); + + s = balanceHTML(s); + debug(" balanceHTML: " + s); + + s = checkTags(s); + debug(" checkTags: " + s); + + s = processRemoveBlanks(s); + debug("processRemoveBlanks: " + s); + + s = validateEntities(s); + debug(" validateEntites: " + s); + + debug("************************************************\n\n"); + return s; + } + + public boolean isAlwaysMakeTags() { + return alwaysMakeTags; + } + + public boolean isStripComments() { + return stripComment; + } + + private String escapeComments(final String s) { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) { + final String match = m.group(1); //(.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) { + if (alwaysMakeTags) { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } else { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) { + final 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); + + s = buf.toString(); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + for (final String key : vTagCounts.keySet()) { + for (int ii = 0; ii < vTagCounts.get(key); ii++) { + s += ""; + } + } + + return s; + } + + private String processRemoveBlanks(final String s) { + String result = s; + for (final String tag : vRemoveBlanks) { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) { + final 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 ""; + } + } + } + } + + // 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)) { + String params = ""; + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList(); + final List paramValues = new ArrayList(); + while (m2.find()) { + paramNames.add(m2.group(1)); //([a-z0-9]+) + paramValues.add(m2.group(3)); //(.*?) + } + while (m3.find()) { + paramNames.add(m3.group(1)); //([a-z0-9]+) + paramValues.add(m3.group(3)); //([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + +// debug( "paramName='" + paramName + "'" ); +// debug( "paramValue='" + paramValue + "'" ); +// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) { + if (inArray(paramName, vProtocolAtts)) { + paramValue = processParamProtocol(paramValue); + } + params += " " + paramName + "=\"" + paramValue + "\""; + } + } + + 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, s.length()); + if (s.startsWith("#//")) { + s = "#" + s.substring(3, s.length()); + } + } + } + + 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) { + final StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + final 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) { + final StringBuffer buf = new StringBuffer(); + final 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); //(<|$) + m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, """, 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 (final 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/src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java b/src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java new file mode 100644 index 0000000..799b200 --- /dev/null +++ b/src/main/java/top/beansprout/health/config/MySimpleMappingExceptionResolver.java @@ -0,0 +1,77 @@ +package top.beansprout.health.config; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; + +import top.beansprout.health.model.vo.BusinessException; +import top.beansprout.health.model.vo.R; +import top.beansprout.health.util.JsonUtils; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: MySimpleMappingExceptionResolver

+ *

Description: 异常处理

+ * @author cyy + * @date 2020年4月23日 + */ +public class MySimpleMappingExceptionResolver implements HandlerExceptionResolver { + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, + Exception exception) { + // 判断是否ajax请求 + if (!((request.getHeader("accept").indexOf("application/json") > -1) + || ((request.getHeader("X-Requested-With") != null) + && (request.getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1)))) { + // 这里需要手动将异常打印出来,由于没有配置log,实际生产环境应该打印到log里面 + exception.printStackTrace(); + if (exception instanceof BusinessException) { + final BusinessException be = (BusinessException) exception; + String path = be.getR().getPath(); + if (PublicUtils.isBlank(path)) { + path = request.getHeader("Referer"); + path = StringUtils.removeStart(path, request.getHeader("Origin")); + path = StringUtils.removeStart(path, request.getContextPath()); + } + return R.failed(path, be.getR()); + } else + // 对于非ajax请求,我们都统一跳转到error.jsp页面 + return R.failed(R.budil().result(false).message("系统异常!")); + } else { + // 如果是ajax请求,JSON格式返回 + if (exception instanceof BusinessException) { + final BusinessException be = (BusinessException) exception; + response(response, be.getR()); + } else { + response(response, R.budil().result(false).message("系统异常!")); + } + } + return null; + } + + @SuppressWarnings("deprecation") + private void response(HttpServletResponse response, Object data) { + response.setContentType("application/json;charset=UTF-8"); + PrintWriter writer = null; + try { + response.setStatus(HttpStatus.OK.value()); + writer = response.getWriter(); + writer.write(JsonUtils.toJson(data)); + writer.flush(); + } catch (final IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(writer); + } + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/config/TaskExecutorConfig.java b/src/main/java/top/beansprout/health/config/TaskExecutorConfig.java new file mode 100644 index 0000000..9e58ca1 --- /dev/null +++ b/src/main/java/top/beansprout/health/config/TaskExecutorConfig.java @@ -0,0 +1,30 @@ +package top.beansprout.health.config; + +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +public class TaskExecutorConfig implements AsyncConfigurer { // 1 + + @Override + public Executor getAsyncExecutor() { // 2 + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + taskExecutor.setCorePoolSize(10); + taskExecutor.setMaxPoolSize(15); + taskExecutor.setQueueCapacity(25); + taskExecutor.initialize(); + return taskExecutor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new SimpleAsyncUncaughtExceptionHandler(); + } +} diff --git a/src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java b/src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..6f94e85 --- /dev/null +++ b/src/main/java/top/beansprout/health/config/XssHttpServletRequestWrapper.java @@ -0,0 +1,198 @@ +package top.beansprout.health.config; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: XssHttpServletRequestWrapper

+ *

Description: XSS过滤处理

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/22 22:51 + */ +@Slf4j +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private HttpServletRequest orgRequest; + private final String requestUri; + private static HTMLWrapper htmlWrapper; + @Getter + private String requestBody; + + private static String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+"; + private static Set notAllowedKeyWords = new HashSet<>(0); + private static String replacedString = "INVALID"; + // 指定uri需要进行html过滤 + private static String []htmlUri = { }; + private static List ignoreParams = new ArrayList<>(); + + static { + final String keyStr[] = key.split("\\|"); + for (final String str : keyStr) { + notAllowedKeyWords.add(str); + } + htmlWrapper = new HTMLWrapper(true); + } + + public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + orgRequest = request; + if (request instanceof XssHttpServletRequestWrapper) { + orgRequest = ((XssHttpServletRequestWrapper) request).getOrgRequest(); + } + requestUri = orgRequest.getRequestURI(); + requestBody = getAnalysisRequestBody(); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 为空,直接返回 + if (PublicUtils.isBlank(requestBody)) + return super.getInputStream(); + + // xss过滤 + requestBody = xssEncode(requestBody); + final ByteArrayInputStream bis = new ByteArrayInputStream(requestBody.getBytes("UTF-8")); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() { + return bis.read(); + } + }; + } + + @Override + public String getParameter(String name) { + final String value = super.getParameter(name); + if (value == null) + return null; + return xssEncode(value); + } + + @Override + public String[] getParameterValues(String name) { + final String[] parameters = super.getParameterValues(name); + if ((parameters == null) || (parameters.length == 0)) + return null; + final int count = parameters.length; + final String[] encodedValues = new String[count]; + for (int i = 0; i < count; i++) { + encodedValues[i] = xssEncode(parameters[i]); + } + return encodedValues; + } + + @Override + public Map getParameterMap() { + final Map parameters = super.getParameterMap(); + if (parameters == null) + return null; + final Map result = new HashMap<>(); + for (final String key : parameters.keySet()) { + final String encodedKey = xssEncode(key); + final int count = parameters.get(key).length; + final String[] encodedValues = new String[count]; + for (int i = 0; i < count; i++) { + encodedValues[i] = xssEncode(parameters.get(key)[i]); + } + result.put(encodedKey, encodedValues); + } + return result; + } + + /** + * 覆盖getHeader方法,将参数名和参数值都做xss过滤。 + * 如果需要获得原始的值,则通过super.getHeaders(name)来获取 + * getHeaderNames 也可能需要覆盖 + */ + @Override + public String getHeader(String name) { + final String value = super.getHeader(name); + if (value == null) + return null; + // 忽略指定字段不进行xss + if (ignoreParams.contains(name)) return value; + return xssEncode(value); + } + + /** 获取请求body json 内容 **/ + private String getAnalysisRequestBody() throws IOException { + final String contentType = super.getHeader("Content-Type"); + if (PublicUtils.isBlank(contentType) || !MediaType.APPLICATION_JSON_VALUE.contentEquals(contentType)) + return null; + + return IOUtils.toString(super.getInputStream(), "UTF-8"); + } + + /** 获取最原始request **/ + private HttpServletRequest getOrgRequest() { + return orgRequest; + } + + /** 过滤 */ + private String xssEncode(String value) { + // You'll need to remove the spaces from the html entities below + String result = value.replaceAll("<", "<").replaceAll(">", ">"); + result = result.replaceAll("<", "& lt;").replaceAll(">", "& gt;"); + result = result.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;"); + result = result.replaceAll("'", "& #39;"); + result = result.replaceAll("eval\\((.*)\\)", ""); + result = result.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\""); + result = result.replaceAll("script", ""); + result = sqlKeyFilterWords(result); + for (int i = 0; i < htmlUri.length; i ++) { + if (requestUri.contains(htmlUri[i])) { + result = htmlWrapper.filter(result); + } + } + return result; + } + + /** sql注入过滤 **/ + private String sqlKeyFilterWords(String value) { + String paramValue = value; + for (final String keyword : notAllowedKeyWords) { + if ((paramValue.length() > (keyword.length() + 1)) + && (paramValue.contains(" " + keyword) || paramValue.contains(keyword + " ") || paramValue.contains(" " + keyword + " "))) { + paramValue = StringUtils.replace(paramValue, keyword, replacedString); + log.warn("CrosXssFilter 请求参数中包含不允许sql的关键词({});参数:{};过滤后的参数:{}", keyword, value, paramValue); + } + } + return paramValue; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/constant/SysConstant.java b/src/main/java/top/beansprout/health/constant/SysConstant.java new file mode 100644 index 0000000..91905c2 --- /dev/null +++ b/src/main/java/top/beansprout/health/constant/SysConstant.java @@ -0,0 +1,31 @@ +package top.beansprout.health.constant; + +import javax.servlet.http.HttpServletRequest; + +import top.beansprout.health.model.vo.RequestVo; +import top.beansprout.health.util.JsonUtils; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: SysConstant

+ *

Description: 系统常量

+ * @author cyy + * @date 2020年4月23日 + */ +public class SysConstant { + + public static final String CHARACHER = "UTF-8"; + + public static final String INIT_FIELD_REQUEST_IP = "ip"; + public static final String INIT_FIELD_REQUEST_REQUEST_ID = "requestId"; + public static final String INIT_FIELD_REQUEST_USER_AGENT = "userAgent"; + public static final String INIT_FIELD_REQUEST_BASE_PATH = "basePath"; + public static final String INIT_FIELD_REQUEST_VO = "requestVo"; + public static final String INIT_FIELD_USER_VO = "user"; + + /** 获取初始化数据 **/ + public static RequestVo getRequestVo(HttpServletRequest request) { + return JsonUtils.toObj(PublicUtils.getAttribute(request, INIT_FIELD_REQUEST_VO), RequestVo.class); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/controller/BaseController.java b/src/main/java/top/beansprout/health/controller/BaseController.java new file mode 100644 index 0000000..ec37435 --- /dev/null +++ b/src/main/java/top/beansprout/health/controller/BaseController.java @@ -0,0 +1,36 @@ +package top.beansprout.health.controller; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Autowired; + +import top.beansprout.health.constant.SysConstant; +import top.beansprout.health.model.vo.BusinessException; +import top.beansprout.health.model.vo.UserLoginVo; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: BaseController

+ *

Description: 基本信息处理

+ * + * @Auther: cyy + * @Date: 2020年4月27日 上午12:36:51 + * @Version: 1.0.0 + */ +public class BaseController { + + @Autowired + private HttpServletRequest request; + + public UserLoginVo getUserInfo() { + final Object userObject = request.getSession().getAttribute(SysConstant.INIT_FIELD_USER_VO); + if (PublicUtils.isNotBlank(userObject)) + return (UserLoginVo) userObject; + throw new BusinessException("login", "身份信息已过期,请重新登录"); + } + + public int getUserId() { + return getUserInfo().getId(); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/controller/HealthController.java b/src/main/java/top/beansprout/health/controller/HealthController.java new file mode 100644 index 0000000..998727d --- /dev/null +++ b/src/main/java/top/beansprout/health/controller/HealthController.java @@ -0,0 +1,74 @@ +package top.beansprout.health.controller; + +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.stereotype.Controller; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import top.beansprout.health.model.dto.BodyInfoQuery; +import top.beansprout.health.model.dto.BodyInfoSaveDto; +import top.beansprout.health.model.vo.R; +import top.beansprout.health.service.HealthService; + +/** + *

Title: HealthController

+ *

Description: 健康管理接口

+ * + * @author cyy + * @date 2020年4月27日 + */ +@Controller +@RequestMapping("/health") +public class HealthController extends BaseController { + + @Autowired + private HealthService healthService; + + // 保存身体信息 + @ResponseBody + @PostMapping("/saveBodyInfo") + public R saveBodyInfo(@RequestBody @Valid BodyInfoSaveDto bodyInfoSaveDto) { + healthService.saveBodyInfo(getUserId(), bodyInfoSaveDto); + return R.okAsAjax(); + } + + // 身体信息列表 + @ResponseBody + @GetMapping("/bodyInfoList") + public R bodyInfoList(@Valid BodyInfoQuery bodyInfoQuery) { + return R.okAsAjax(healthService.bodyInfoList(getUserId(), bodyInfoQuery)); + } + + // 删除身体信息 + @ResponseBody + @DeleteMapping("/{id}") + public R saveBodyInfo(@PathVariable int id) { + healthService.deleteBodyInfo(getUserId(), id); + return R.okAsAjax(); + } + + // 获取身体信息 + @ResponseBody + @GetMapping("/{id}") + public R getBodyInfo(@PathVariable int id) { + return R.okAsAjax(healthService.getBodyInfo(getUserId(), id)); + } + + // 身体信息统计 + @ResponseBody + @GetMapping("/bodyInfoStatistics") + public R bodyInfoStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) { + return R.okAsAjax(healthService.getBodyStatistics(getUserId(), date)); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/controller/MailController.java b/src/main/java/top/beansprout/health/controller/MailController.java new file mode 100644 index 0000000..c47001b --- /dev/null +++ b/src/main/java/top/beansprout/health/controller/MailController.java @@ -0,0 +1,89 @@ +package top.beansprout.health.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import top.beansprout.health.model.dto.BodyInfoSaveDto; +import top.beansprout.health.model.dto.UserUpdateInfoDto; +import top.beansprout.health.model.entity.TBodyInfo; +import top.beansprout.health.model.vo.R; +import top.beansprout.health.service.HealthService; +import top.beansprout.health.util.MailUtils; + +import javax.validation.Valid; +import java.util.List; + +/** + *

Title: MailController

+ *

Description: 发送邮箱接口

+ * + * @author ateenliu + * @date 2022年4月6日 + */ +@Controller +@RequestMapping("/mail") +public class MailController extends BaseController { + + @Autowired + private HealthService healthService; + + @ResponseBody + @PostMapping(value = "/verification") + public R sendVerificationMail(@Valid String email) { + MailUtils mailUtils = new MailUtils(); + String verificationCode = mailUtils.createVertifyCode(); + String mailSubject = "个人健康管理平台验证码"; + String mailBody = "欢迎使用个人健康管理平台,您的邮箱验证码为:"+verificationCode; + if(mailUtils.sendingMimeMail(email,mailSubject,mailBody)) + { + System.out.println("邮件发送成功!"); + return R.okAsAjax(verificationCode); + } + else { + System.out.println("邮件发送失败!"); + return R.okAsAjax(); + } + + } + + @ResponseBody + @PostMapping(value = "/recentPush") + public R recentPushMail(){ + if(getUserInfo().getEmail()==null) + return R.ofAsAjax(true,"暂未绑定邮箱",""); + + List tBodyInfoList = healthService.recentBodyInfoList(getUserId()); + TBodyInfo t = new TBodyInfo(); + MailUtils mailUtils = new MailUtils(); + String mailSubject = "近期健康报告"; + StringBuilder content = new StringBuilder("

您的近期报告如下:

"); + content.append(""); + content.append("" + + ""); + for(int i=0;i"+ "" + + ""+ "" + + ""+ "" + + ""+ ""); + } + content.append("
打卡时间收缩压舒张压心率体温食欲体重步数
"+t.getCreateTime()+""+t.getHighBloodPressure()+""+t.getLowBloodPressure()+""+t.getHeartRate()+""+t.getTemperature()+"度"+t.getAppetiteDes(t.getAppetite())+""+t.getWeight()+"kg"+t.getNumberOfStep()+"步
"); + content.append(""); + + + if(mailUtils.sendingMimeMail(getUserInfo().getEmail(), mailSubject,content.toString())) + { + System.out.println("邮件发送成功!"); + return R.okAsAjax(); + } + else { + System.out.println("邮件发送失败!"); + return R.okAsAjax(); + } + } + +} diff --git a/src/main/java/top/beansprout/health/controller/PageController.java b/src/main/java/top/beansprout/health/controller/PageController.java new file mode 100644 index 0000000..eacc217 --- /dev/null +++ b/src/main/java/top/beansprout/health/controller/PageController.java @@ -0,0 +1,71 @@ +package top.beansprout.health.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + *

Title: PageController

+ *

Description: 页面管理

+ * + * @Auther: cyy + * @Date: 2020年4月23日 下午11:15:18 + * @Version: 1.0.0 + */ +@Controller +public class PageController { + + // 根页面 + @GetMapping("/") + public String rootView() { + return "redirect:/login"; + } + + // 登录页面 + @GetMapping("/login") + public String loginView() { + return "../../index"; + } + + // 注册页面 + @GetMapping("/register") + public String registerView() { + return "register"; + } + + // 主页面 + @GetMapping("/home") + public String homeView() { + return "home"; + } + + // 用户信息页面 + @GetMapping("/userInfo") + public String userInfoView() { + return "userInfo"; + } + + // 用户信息页面 + @GetMapping("/updatePassword") + public String updatePasswordView() { + return "updatePassword"; + } + + // 用户身体信息录入页面 + @GetMapping("/bodyInfoInput") + public String bodyInfoInputView() { + return "bodyInfoInput"; + } + + // 用户身体信息列表页面 + @GetMapping("/bodyInofList") + public String bodyInofListView() { + return "bodyInofList"; + } + + // 用户身体信息统计页面 + @GetMapping("/bodyInofStatistics") + public String bodyInofStatisticsView() { + return "bodyInofStatistics"; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/controller/UserController.java b/src/main/java/top/beansprout/health/controller/UserController.java new file mode 100644 index 0000000..19d0bac --- /dev/null +++ b/src/main/java/top/beansprout/health/controller/UserController.java @@ -0,0 +1,74 @@ +package top.beansprout.health.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import top.beansprout.health.model.dto.UserLoginDto; +import top.beansprout.health.model.dto.UserRegisterDto; +import top.beansprout.health.model.dto.UserUpdateInfoDto; +import top.beansprout.health.model.dto.UserUpdatePasswordDto; +import top.beansprout.health.model.vo.R; +import top.beansprout.health.service.UserService; + +/** + *

Title: UserController

+ *

Description: 用户管理接口

+ * + * @author cyy + * @date 2020年4月23日 + */ +@Controller +@RequestMapping("/user") +public class UserController extends BaseController { + + @Autowired + private UserService userService; + + // 登录 + @ResponseBody + @PostMapping("/login") + public R login(@Valid UserLoginDto userLoginDto) { + return R.okAsAjax(userService.login(userLoginDto)); + } + + // 注册 + @ResponseBody + @PostMapping("/register") + public R register(@Valid UserRegisterDto userRegisterDto) { + userService.register(userRegisterDto); + return R.okAsAjax(); + } + + // 登出 + @GetMapping("/logout") + public String logout(HttpServletRequest request) { + userService.logout(request); + return "redirect:/login?target=redirect"; + } + + // 修改密码 + @ResponseBody + @PutMapping("/updatePassword") + public R updatePassword(HttpServletRequest request, @RequestBody @Valid UserUpdatePasswordDto updatePasswordDto) { + userService.updatePassword(request, getUserId(), updatePasswordDto); + return R.okAsAjax(); + } + + // 修改用户信息 + @ResponseBody + @PostMapping(value = "/updateUserInfo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R updateUserInfo(@Valid UserUpdateInfoDto userUpdateInfoDto) { + return R.okAsAjax(userService.updateUserInfo(getUserId(), userUpdateInfoDto)); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java b/src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java new file mode 100644 index 0000000..73ee646 --- /dev/null +++ b/src/main/java/top/beansprout/health/mapper/TBodyInfoMapper.java @@ -0,0 +1,43 @@ +package top.beansprout.health.mapper; + +import java.util.Date; +import java.util.List; + +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import top.beansprout.health.model.entity.TBodyInfo; + +/** + *

Title: TBodyInfoMapper

+ *

Description: 身体健康操作

+ * + * @author cyy + * @date 2020年4月27日 + */ +public interface TBodyInfoMapper { + + @Insert("INSERT INTO t_body_info (creator, create_time, updater, update_time, low_blood_pressure, " + + "high_blood_pressure, heart_rate, temperature, appetite, weight, number_of_step) VALUE " + + "( #{creator},NOW(),#{updater},NOW(),#{lowBloodPressure},#{highBloodPressure}," + + "#{heartRate},#{temperature},#{appetite},#{weight},#{numberOfStep} )") + int insertByOne(TBodyInfo bodyInfo); + + @Select("SELECT COUNT(*) FROM t_body_info WHERE creator = #{creator} AND create_time >= CURDATE() AND create_time <= CONCAT(CURDATE(), '23:59:59')") + int countByOneAsToDay(@Param("creator") int userId); + + List selectByUserId(@Param("creator") int userId, @Param("minDate") Date minDate, + @Param("maxDate") Date maxDate); + + @Delete("DELETE FROM t_body_info WHERE id = #{id} AND creator = #{creator}") + int deleteByOne(@Param("id") int id, @Param("creator") int userId); + + @Select("SELECT * FROM t_body_info WHERE id = #{id} AND creator = #{creator}") + TBodyInfo selectByUserOne(@Param("id") int id, @Param("creator") int userId); + + @Select("SELECT * FROM t_body_info WHERE creator = #{creator} LIMIT 7 ") + List selectRecentByUserId(@Param("creator") int userId); + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java b/src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java new file mode 100644 index 0000000..713ca05 --- /dev/null +++ b/src/main/java/top/beansprout/health/mapper/THealthConfigMapper.java @@ -0,0 +1,12 @@ +package top.beansprout.health.mapper; + +/** + *

Title: THealthConfigMapper

+ *

Description: 健康配置操作

+ * + * @author cyy + * @date 2020年4月27日 + */ +public interface THealthConfigMapper { + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/mapper/TUserMapper.java b/src/main/java/top/beansprout/health/mapper/TUserMapper.java new file mode 100644 index 0000000..82b3dd9 --- /dev/null +++ b/src/main/java/top/beansprout/health/mapper/TUserMapper.java @@ -0,0 +1,36 @@ +package top.beansprout.health.mapper; + +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import top.beansprout.health.model.entity.TUser; + +/** + *

Title: TUserMapper

+ *

Description: 用户数据操作

+ * + * @Auther: cyy + * @Date: 2020年4月23日 下午9:24:05 + * @Version: 1.0.0 + */ +public interface TUserMapper { + + @Select("SELECT * FROM t_user WHERE user_name = #{userName}") + TUser selectByUserName(@Param("userName") String userName); + + @Select("SELECT * FROM t_user WHERE id = #{userId}") + TUser selectById(@Param("userId") int id); + + @Insert("INSERT INTO t_user (creator, create_time, updater, update_time, nick_name, user_name, password, head_url) " + + "VALUE (0, NOW(), 0, NOW(), #{userName}, #{userName}, #{password}, #{headUrl})") + int insertByOne(TUser tUser); + + @Update("UPDATE t_user SET updater = #{id}, update_time = NOW(), password = #{password} WHERE id = #{id}") + int updateByOne(TUser tUser); + + @Update("UPDATE t_user SET updater = #{id}, update_time = NOW(), nick_name = #{nickName}, email = #{email}, head_url = #{headUrl} WHERE id = #{id}") + int updateByUserInfoOne(TUser tUser); + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java b/src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java new file mode 100644 index 0000000..a411f91 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/BodyInfoQuery.java @@ -0,0 +1,28 @@ +package top.beansprout.health.model.dto; + +import java.util.Date; + +import org.springframework.format.annotation.DateTimeFormat; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: BodyInfoQuery

+ *

Description: 身体信息查询

+ * + * @author cyy + * @date 2020年4月27日 + */ +@Setter +@Getter +public class BodyInfoQuery extends PageDto { + + private static final long serialVersionUID = 1L; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date minDate; + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date maxDate; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java b/src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java new file mode 100644 index 0000000..d3cbbeb --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/BodyInfoSaveDto.java @@ -0,0 +1,46 @@ +package top.beansprout.health.model.dto; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.constraints.Range; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: BodyInfoSaveDto

+ *

Description: 身体信息保存

+ * + * @author cyy + * @date 2020年4月27日 + */ +@Setter +@Getter +public class BodyInfoSaveDto implements Serializable { + + private static final long serialVersionUID = 80577054311531170L; + + @NotNull(message = "低血压不能为空") + @Range(min = 0, max = 200, message = "低血压只能在0到200之间") + private Integer lowBloodPressure; + @NotNull(message = "高血压不能为空") + @Range(min = 0, max = 200, message = "高血压只能在0到200之间") + private Integer highBloodPressure; + @NotNull(message = "心率不能为空") + @Range(min = 40, max = 200, message = "心率只能在40到200之间") + private Integer heartRate; + @NotNull(message = "体温不能为空") + @Range(min = 33, max = 45, message = "体温只能在33到45之间") + private Double temperature; + @NotNull(message = "食欲不能为空") + @Range(min = 1, max = 6, message = "食欲只能在1到6之间") + private Integer appetite; + @NotNull(message = "体重不能为空") + @Range(min = 0, max = 200, message = "体重只能在33到40之间") + private Double weight; + // 步数 + private int numberOfStep; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/PageDto.java b/src/main/java/top/beansprout/health/model/dto/PageDto.java new file mode 100644 index 0000000..571b989 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/PageDto.java @@ -0,0 +1,32 @@ +package top.beansprout.health.model.dto; + +import java.io.Serializable; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: PageDto

+ *

Description: 列表请求

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/22 22:32 + */ +@Setter +@Getter +public class PageDto implements Serializable { + + private static final long serialVersionUID = -1796187511693230421L; + + @Min(value = 1, message = "当前页最小页码为1") + private int page = 1; + + @Min(value = 1, message = "每页展示条数最小为1") + @Max(value = 100, message = "每页展示条数最大为100") + private int pageSize = 10; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/UserLoginDto.java b/src/main/java/top/beansprout/health/model/dto/UserLoginDto.java new file mode 100644 index 0000000..5e8ccce --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/UserLoginDto.java @@ -0,0 +1,33 @@ +package top.beansprout.health.model.dto; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import org.hibernate.validator.constraints.Length; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: UserLoginDto

+ *

Description: 用户登录

+ * + * @Auther: cyy + * @Date: 2020年4月25日 下午8:11:06 + * @Version: 1.0.0 + */ +@Setter +@Getter +public class UserLoginDto implements Serializable { + + private static final long serialVersionUID = 6865880222397602631L; + + @NotBlank(message = "账户不能为空") + @Length(max = 20, message = "账户不能超过20位") + private String userName; + @NotBlank(message = "密码不能为空") + @Length(max = 20, message = "密码不能超过20位") + private String passWord; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java b/src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java new file mode 100644 index 0000000..28720df --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/UserRegisterDto.java @@ -0,0 +1,36 @@ +package top.beansprout.health.model.dto; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import org.hibernate.validator.constraints.Length; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: UserRegisterDto

+ *

Description: 用户注册

+ * + * @Auther: cyy + * @Date: 2020年4月26日 上午12:54:09 + * @Version: 1.0.0 + */ +@Setter +@Getter +public class UserRegisterDto implements Serializable { + + private static final long serialVersionUID = -4003225859721099921L; + + @NotBlank(message = "用户名或姓名不能为空") + @Length(min = 2, max = 10, message = "用户名或姓名必须在2~10位之间") + private String userName; + @NotBlank(message = "密码不能为空") + @Length(min = 6, max = 10, message = "密码必须在6~10位之间") + private String passWord; + @NotBlank(message = "确认密码不能为空") + @Length(min = 6, max = 10, message = "确认密码必须在6~10位之间") + private String confirmPassWord; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java b/src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java new file mode 100644 index 0000000..1e9fdb4 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/UserUpdateInfoDto.java @@ -0,0 +1,39 @@ +package top.beansprout.health.model.dto; + +import java.io.Serializable; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.constraints.Length; +import org.springframework.web.multipart.MultipartFile; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: UserUpdateInfoDto

+ *

Description: 用户信息修改

+ * + * @author cyy + * @date 2020年4月30日 + */ +@Setter +@Getter +public class UserUpdateInfoDto implements Serializable { + + private static final long serialVersionUID = 7815632813092576575L; + + // 昵称 + @NotBlank(message = "昵称不能为空") + @Length(min = 2, max = 10, message = "昵称只能为2~10个字符") + private String nickName; + // 邮件 + @Email(message = "邮箱格式不正确") + private String email; + // 头像 + @NotNull(message = "头像不能为空") + private MultipartFile headUrl; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java b/src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java new file mode 100644 index 0000000..e4f3d1f --- /dev/null +++ b/src/main/java/top/beansprout/health/model/dto/UserUpdatePasswordDto.java @@ -0,0 +1,33 @@ +package top.beansprout.health.model.dto; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import org.hibernate.validator.constraints.Length; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: UserUpdatePasswordDto

+ *

Description: 用户修改密码

+ * + * @Auther: cyy + * @Date: 2020年4月27日 上午12:32:39 + * @Version: 1.0.0 + */ +@Setter +@Getter +public class UserUpdatePasswordDto implements Serializable { + + private static final long serialVersionUID = 8674837059264557849L; + + @NotBlank(message = "密码不能为空") + @Length(min = 6, max = 10, message = "密码必须在6~10位之间") + private String passWord; + @NotBlank(message = "确认密码不能为空") + @Length(min = 6, max = 10, message = "确认密码必须在6~10位之间") + private String confirmPassWord; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/entity/BaseEntity.java b/src/main/java/top/beansprout/health/model/entity/BaseEntity.java new file mode 100644 index 0000000..e8dde47 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/entity/BaseEntity.java @@ -0,0 +1,24 @@ +package top.beansprout.health.model.entity; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: BaseEntity

+ *

Description: 实体类对象的父类-公共字段

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/22 16:50 + */ +@Setter +@Getter +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 4402927657355642335L; + + private int id; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java b/src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java new file mode 100644 index 0000000..049e75c --- /dev/null +++ b/src/main/java/top/beansprout/health/model/entity/BaseInsertEntity.java @@ -0,0 +1,30 @@ +package top.beansprout.health.model.entity; + +import java.util.Date; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: BaseUpdateEntity

+ *

Description: 实体类插入对象的父类-公共字段

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/23 23:52 + */ +@Setter +@Getter +public abstract class BaseInsertEntity extends BaseEntity { + + private static final long serialVersionUID = -7314584145079382702L; + + private Long creator;// 创建人id + private Date createTime;// 创建时间 + + public void init(long userId) { + this.creator = userId; + this.createTime = new Date(); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java b/src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java new file mode 100644 index 0000000..6e63e16 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/entity/BaseUpdateEntity.java @@ -0,0 +1,38 @@ +package top.beansprout.health.model.entity; + +import java.util.Date; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: BaseUpdateEntity

+ *

Description: 实体类用户对象的父类-公共字段

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/23 23:52 + */ +@Setter +@Getter +public abstract class BaseUpdateEntity extends BaseEntity { + + private static final long serialVersionUID = 1620758104430484420L; + + private int creator;// 创建人id + private Date createTime;// 创建时间 + private int updater;// 更新者id + private Date updateTime;// 更新时间 + + public void init(int userId) { + this.creator = userId; + this.createTime = new Date(); + this.update(userId); + } + + public void update(int userId) { + this.updater = userId; + this.updateTime = new Date(); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/entity/TBodyInfo.java b/src/main/java/top/beansprout/health/model/entity/TBodyInfo.java new file mode 100644 index 0000000..a4a9b9b --- /dev/null +++ b/src/main/java/top/beansprout/health/model/entity/TBodyInfo.java @@ -0,0 +1,56 @@ +package top.beansprout.health.model.entity; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + *

Title: TBodyInfo

+ *

Description: 身体信息表

+ * @author cyy + * @date 2020年4月27日 + */ +@Setter +@Getter +public class TBodyInfo extends BaseUpdateEntity { + + private static final long serialVersionUID = -7707228772072459981L; + + // 打开时间 + private Date createTime; + + // 舒张压 + private int lowBloodPressure; + // 收缩压 + private int highBloodPressure; + // 心率 + private int heartRate; + // 体温 + private double temperature; + // 食欲 + private int appetite; + // 体重 + private double weight; + // 步数 + private int numberOfStep; + + public String getAppetiteDes(int appetite){ + switch (appetite){ + case 1: + return "非常差"; + case 2: + return "差"; + case 3: + return "一般"; + case 4: + return "良好"; + case 5: + return "很好"; + case 6: + return "非常好"; + } + return ""; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/entity/THealthConfig.java b/src/main/java/top/beansprout/health/model/entity/THealthConfig.java new file mode 100644 index 0000000..cacfb3c --- /dev/null +++ b/src/main/java/top/beansprout/health/model/entity/THealthConfig.java @@ -0,0 +1,35 @@ +package top.beansprout.health.model.entity; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: THealthConfig

+ *

Description: 健康信息配置表

+ * @author cyy + * @date 2020年4月27日 + */ +@Setter +@Getter +public class THealthConfig extends BaseUpdateEntity { + + private static final long serialVersionUID = 6928075281678415808L; + + // 最小低血压 + private int minLowBloodPressure; + // 最大低血压 + private int maxLowBloodPressure; + // 最小高血压 + private int minHighBloodPressure; + // 最大高血压 + private int maxHighBloodPressure; + // 最小心率 + private int minHeartRate; + // 最大心率 + private int maxHeartRate; + // 最小体温 + private double minTemperature; + // 最大体温 + private double maxTemperature; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/entity/TUser.java b/src/main/java/top/beansprout/health/model/entity/TUser.java new file mode 100644 index 0000000..1105b63 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/entity/TUser.java @@ -0,0 +1,30 @@ +package top.beansprout.health.model.entity; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: TUser

+ *

Description: 用户表

+ * + * @author beansprout + * @date 2020年4月22日 + */ +@Setter +@Getter +public class TUser extends BaseUpdateEntity { + + private static final long serialVersionUID = 7849174171033853904L; + + // 用户名 + private String nickName; + // 账户 + private String userName; + // 密码 + private String password; + // 邮箱 + private String email; + // 头像 + private String headUrl; + +} diff --git a/src/main/java/top/beansprout/health/model/vo/AuthVo.java b/src/main/java/top/beansprout/health/model/vo/AuthVo.java new file mode 100644 index 0000000..1253a15 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/AuthVo.java @@ -0,0 +1,26 @@ +package top.beansprout.health.model.vo; + +import javax.mail.Authenticator; +import javax.mail.PasswordAuthentication; + +/** + *

Title: AuthVo

+ *

Description: 发送邮箱用户

+ * + * @author ateenliu + * @date 2022年4月6日 + */ + +public class AuthVo extends Authenticator { + private String username = ""; + private String password = ""; + + public AuthVo(String username, String password) { + this.username = username; + this.password = password; + } + public PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + +} diff --git a/src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java b/src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java new file mode 100644 index 0000000..e51c4c3 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/BodyInfoDetailVo.java @@ -0,0 +1,65 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; +import java.util.Date; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: BodyInfoDetailVo

+ *

Description: 身体信息详情

+ * + * @author cyy + * @date 2020年4月28日 + */ +@Setter +@Getter +public class BodyInfoDetailVo implements Serializable { + + private static final long serialVersionUID = -2831120593213278473L; + + // id + private int id; + // 打卡时间 + private Date createTime; + + // 舒张压 + private int lowBloodPressure; + // 舒张压状态 + private int lowBloodPressureStatus; + // 舒张压描述 + private String lowBloodPressureDesc; + + // 收缩压 + private int highBloodPressure; + // 收缩压状态 + private int highBloodPressureStatus; + // 收缩压描述 + private String highBloodPressureDesc; + + // 血压 + private String bloodPressureDesc; + + // 心率 + private int heartRate; + // 心率状态 + private int heartRateStatus; + // 心率描述 + private String heartRateDesc; + + // 体温 + private double temperature; + // 体温状态 + private int temperatureStatus; + // 体温描述 + private String temperatureDesc; + + // 食欲 + private int appetite; + // 体重 + private double weight; + // 步数 + private int numberOfStep; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java b/src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java new file mode 100644 index 0000000..5e895a8 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/BodyInfoStatisticsVo.java @@ -0,0 +1,43 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + *

Title: BodyInfoStatisticsVo

+ *

Description: 健康信息统计

+ * + * @Auther: cyy + * @Date: 2020年4月28日 下午11:32:05 + * @Version: 1.0.0 + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class BodyInfoStatisticsVo implements Serializable { + + private static final long serialVersionUID = 8627681890953284164L; + + // 类别名字 + private List typeNames; + // 类别数据 + private List bodyInfoVos; + + @Setter + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class BodyInfoVo { + // 类别名 + private String typeName; + // 数据 + private List datas; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/BusinessException.java b/src/main/java/top/beansprout/health/model/vo/BusinessException.java new file mode 100644 index 0000000..8bbf34f --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/BusinessException.java @@ -0,0 +1,34 @@ +package top.beansprout.health.model.vo; + +import lombok.Getter; + +/** + *

Title: BusinessException

+ *

Description: 业务异常

+ * @author cyy + * @date 2020年4月23日 + */ +@Getter +public class BusinessException extends RuntimeException { + + private static final long serialVersionUID = -5846808029174051527L; + + private final R r; + + public BusinessException() { + r = R.budil().result(false).message("系统异常"); + } + + public BusinessException(String message) { + r = R.budil().result(false).message(message); + } + + public BusinessException(String path, String message) { + r = R.budil().result(false).message(message).path(path); + } + + public BusinessException(String message, Object data) { + r = R.budil().result(false).message(message).data(data); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/PageVo.java b/src/main/java/top/beansprout/health/model/vo/PageVo.java new file mode 100644 index 0000000..253d76f --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/PageVo.java @@ -0,0 +1,73 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; +import java.util.List; + +import com.github.pagehelper.PageInfo; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: PageVo

+ *

Description: 列表返回

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/22 22:15 + */ +@Setter +@Getter +public class PageVo implements Serializable { + + private static final long serialVersionUID = 7039024431017840683L; + + // 当前页 + private long page; + // 每页展示数量 + private long pageSize; + // 是否是第一页true第一 + private boolean first = false; + // 是否是最后一页true最后 + private boolean last = false; + // 总页数 + private int totalPages; + // 总数据条数 + private int totalElements; + // 当前页数据条数 + private int numberOfElements; + // 数据集 + private List content; + + public PageVo(int page, int pageSize, int totalElements, List content) { + this.pageSize = pageSize; + this.totalElements = totalElements; + this.page = page <= 0 ? 1 : page; + this.totalPages = totalElements == 0 ? 0 + : (totalElements % pageSize) == 0 ? totalElements / pageSize : (totalElements / pageSize) + 1; + this.first = page == 1 ? true : false; + this.last = ((page == this.totalPages) || (content.size() < 1)) ? true : false; + this.content = content; + this.numberOfElements = this.content.size(); + } + + public PageVo(PageInfo pageInfo) { + this.page = pageInfo.getPageNum(); + this.pageSize = pageInfo.getPageSize(); + this.totalElements = (int) pageInfo.getTotal(); + this.totalPages = pageInfo.getPages(); + this.first = page == 1 ? true : false; + this.content = pageInfo.getList(); + this.last = ((page == this.totalPages) || (content.size() < 1)) ? true : false; + this.numberOfElements = this.content.size(); + } + + public static PageVo of(int page, int pageSize, int totalElements, List content) { + return new PageVo(page, pageSize, totalElements, content); + } + + public static PageVo of(PageInfo pageInfo) { + return new PageVo(pageInfo); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/R.java b/src/main/java/top/beansprout/health/model/vo/R.java new file mode 100644 index 0000000..d2d9c7c --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/R.java @@ -0,0 +1,110 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.web.servlet.ModelAndView; + +import lombok.Getter; +import lombok.Setter; +import top.beansprout.health.constant.SysConstant; + +/** + *

Title: R

+ *

Description: 返回结果

+ * @author cyy + * @date 2020年4月23日 + */ +@Setter +@Getter +public class R extends HashMap implements Serializable { + + private static final long serialVersionUID = -7261945562236369523L; + + public static final String FIELD_RESULT = "result"; + public static final String FIELD_MESSAGE = "message"; + public static final String FIELD_DATA = "data"; + public static final String FIELD_PATH = "path"; + + public static final String FAILED_FIELD_REQUEST_ID = SysConstant.INIT_FIELD_REQUEST_REQUEST_ID; + public static final String FAILED_FIELD_TIME_STAMP = "timestamp"; + public static final String FAILED_FIELD_PATH = "path"; + public static final String FAILED_FIELD_ERROR = "error"; + public static final String FAILED_FIELD_ERRORS = "errors"; + + // 结果 + private boolean result; + // 描述 + private String message; + // 数据 + private Object data; + // 页面 + private String path; + + public static R budil() { + return new R(); + } + + public R result(boolean result) { + this.put(FIELD_RESULT, result); + this.result = result; + return this; + } + + public R message(String message) { + this.put(FIELD_MESSAGE, message); + this.message = message; + return this; + } + + public R data(Object data) { + this.put(FIELD_DATA, data); + this.data = data; + return this; + } + + public R path(String path) { + this.put(FIELD_PATH, path); + this.path = path; + return this; + } + + public static ModelAndView ok() { + return of("/", null); + } + + public static R okAsAjax() { + return okAsAjax(null); + } + + public static ModelAndView ok(Map data) { + return of("/", data); + } + + public static R okAsAjax(Object data) { + return ofAsAjax(true, "成功", data); + } + + public static ModelAndView failed() { + return of("/500", null); + } + + public static ModelAndView failed(Map data) { + return of("/500", data); + } + + public static ModelAndView failed(String path, Map data) { + return of(path, data); + } + + public static ModelAndView of(String path, Map data) { + final ModelAndView modelAndView = new ModelAndView(path, data); + return modelAndView; + } + + public static R ofAsAjax(boolean result, String message, Object data) { + return budil().result(result).message(message).data(data); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/RequestVo.java b/src/main/java/top/beansprout/health/model/vo/RequestVo.java new file mode 100644 index 0000000..e2b6885 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/RequestVo.java @@ -0,0 +1,31 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + *

Title: RequestVo

+ *

Description: 请求数据

+ * @author cyy + * @date 2020年4月23日 + */ +@Setter +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RequestVo implements Serializable { + + private static final long serialVersionUID = -2460516900904070899L; + + // 基本项目地址 + private String basePath; + // 用户信息 + private UserLoginVo user; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/UserInfoVo.java b/src/main/java/top/beansprout/health/model/vo/UserInfoVo.java new file mode 100644 index 0000000..45b3137 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/UserInfoVo.java @@ -0,0 +1,36 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + *

Title: UserInfoVo

+ *

Description: 用户信息

+ * + * @author cyy + * @date 2020年4月30日 + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class UserInfoVo implements Serializable { + + private static final long serialVersionUID = 8389631895704876733L; + + // 用户id + private int id; + // 用户名 + private String nickName; + // 账户 + private String userName; + // 邮箱 + private String email; + // 头像 + private String headUrl; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/model/vo/UserLoginVo.java b/src/main/java/top/beansprout/health/model/vo/UserLoginVo.java new file mode 100644 index 0000000..40496d5 --- /dev/null +++ b/src/main/java/top/beansprout/health/model/vo/UserLoginVo.java @@ -0,0 +1,33 @@ +package top.beansprout.health.model.vo; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.Setter; + +/** + *

Title: UserLoginVo

+ *

Description:

+ * + * @Auther: cyy + * @Date: 2020年4月25日 下午8:49:19 + * @Version: 1.0.0 + */ +@Setter +@Getter +public class UserLoginVo implements Serializable { + + private static final long serialVersionUID = -7027379021523096696L; + + // 用户id + private int id; + // 用户名 + private String nickName; + // 账户 + private String userName; + // 邮箱 + private String email; + // 头像 + private String headUrl; + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/service/HealthService.java b/src/main/java/top/beansprout/health/service/HealthService.java new file mode 100644 index 0000000..595b26c --- /dev/null +++ b/src/main/java/top/beansprout/health/service/HealthService.java @@ -0,0 +1,40 @@ +package top.beansprout.health.service; + +import java.util.Date; +import java.util.List; + +import top.beansprout.health.model.dto.BodyInfoQuery; +import top.beansprout.health.model.dto.BodyInfoSaveDto; +import top.beansprout.health.model.entity.TBodyInfo; +import top.beansprout.health.model.vo.BodyInfoDetailVo; +import top.beansprout.health.model.vo.BodyInfoStatisticsVo; +import top.beansprout.health.model.vo.PageVo; + +/** + *

Title: HealthService

+ *

Description: 健康业务接口

+ * + * @author cyy + * @date 2020年4月27日 + */ +public interface HealthService { + + /** 保存身体信息 **/ + void saveBodyInfo(int userId, BodyInfoSaveDto bodyInfoSaveDto); + + /** 获取身体信息列表 **/ + PageVo bodyInfoList(int userId, BodyInfoQuery bodyInfoQuery); + + /** 删除身体信息 **/ + void deleteBodyInfo(int userId, int id); + + /** 获取身体信息 **/ + BodyInfoDetailVo getBodyInfo(int userId, int id); + + /** 获取身体信息统计 **/ + BodyInfoStatisticsVo getBodyStatistics(int userId, Date date); + + /** 获取惊奇身体信息统计 **/ + List recentBodyInfoList(int userId); + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/service/UserService.java b/src/main/java/top/beansprout/health/service/UserService.java new file mode 100644 index 0000000..ff2610e --- /dev/null +++ b/src/main/java/top/beansprout/health/service/UserService.java @@ -0,0 +1,37 @@ +package top.beansprout.health.service; + +import javax.servlet.http.HttpServletRequest; + +import top.beansprout.health.model.dto.UserLoginDto; +import top.beansprout.health.model.dto.UserRegisterDto; +import top.beansprout.health.model.dto.UserUpdateInfoDto; +import top.beansprout.health.model.dto.UserUpdatePasswordDto; +import top.beansprout.health.model.vo.UserInfoVo; +import top.beansprout.health.model.vo.UserLoginVo; + +/** + *

Title: UserService

+ *

Description: 用户业务接口

+ * + * @Auther: cyy + * @Date: 2020年4月25日 下午8:01:14 + * @Version: 1.0.0 + */ +public interface UserService { + + /** 登录逻辑 **/ + UserLoginVo login(UserLoginDto userLoginDto); + + /** 注册逻辑 **/ + void register(UserRegisterDto userRegisterDto); + + /** 登出 **/ + void logout(HttpServletRequest request); + + /** 修改密码 **/ + void updatePassword(HttpServletRequest request, int userId, UserUpdatePasswordDto updatePasswordDto); + + /** 修改用户信息 **/ + UserInfoVo updateUserInfo(int userId, UserUpdateInfoDto userUpdateInfoDto); + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java b/src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java new file mode 100644 index 0000000..752b636 --- /dev/null +++ b/src/main/java/top/beansprout/health/service/impl/HealthServiceImpl.java @@ -0,0 +1,299 @@ +package top.beansprout.health.service.impl; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; + +import top.beansprout.health.mapper.TBodyInfoMapper; +import top.beansprout.health.mapper.THealthConfigMapper; +import top.beansprout.health.model.dto.BodyInfoQuery; +import top.beansprout.health.model.dto.BodyInfoSaveDto; +import top.beansprout.health.model.entity.TBodyInfo; +import top.beansprout.health.model.vo.BodyInfoDetailVo; +import top.beansprout.health.model.vo.BodyInfoStatisticsVo; +import top.beansprout.health.model.vo.BodyInfoStatisticsVo.BodyInfoVo; +import top.beansprout.health.model.vo.BusinessException; +import top.beansprout.health.model.vo.PageVo; +import top.beansprout.health.service.HealthService; +import top.beansprout.health.util.DateUtils; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: HealthServiceImpl

+ *

Description: 健康业务逻辑操作

+ * + * @author cyy + * @date 2020年4月27日 + */ +@Service +public class HealthServiceImpl implements HealthService { + + @Autowired + private THealthConfigMapper healthConfigMapper; + @Autowired + private TBodyInfoMapper bodyInfoMapper; + + @Override + @Transactional + public void saveBodyInfo(int userId, BodyInfoSaveDto bodyInfoSaveDto) { + // 判断今天是否已经录入过 + final int countByOneAsToDay = bodyInfoMapper.countByOneAsToDay(userId); + if (countByOneAsToDay > 0) + throw new BusinessException("今天已经打过卡了"); + + final TBodyInfo tBodyInfo = new TBodyInfo(); + PublicUtils.copyBean(bodyInfoSaveDto, tBodyInfo); + tBodyInfo.init(userId); + bodyInfoMapper.insertByOne(tBodyInfo); + } + + @Override + public List recentBodyInfoList(int userId){ + List tBodyInfoList = bodyInfoMapper.selectRecentByUserId(userId); + return tBodyInfoList; + } + + @Override + public PageVo bodyInfoList(int userId, BodyInfoQuery bodyInfoQuery) { + if ((bodyInfoQuery.getMinDate() != null) && (bodyInfoQuery.getMaxDate() != null)) { + if (bodyInfoQuery.getMinDate().after(bodyInfoQuery.getMaxDate())) + throw new BusinessException("最小时间不能大于最大时间"); + } + PageHelper.startPage(bodyInfoQuery.getPage(), bodyInfoQuery.getPageSize()); + final PageInfo pageInfo = new PageInfo<>( + bodyInfoMapper.selectByUserId(userId, bodyInfoQuery.getMinDate(), bodyInfoQuery.getMaxDate())); + return PageVo.of(pageInfo); + } + + @Override + @Transactional + public void deleteBodyInfo(int userId, int id) { + bodyInfoMapper.deleteByOne(id, userId); + } + + @Override + public BodyInfoDetailVo getBodyInfo(int userId, int id) { + final TBodyInfo bodyInfo = bodyInfoMapper.selectByUserOne(id, userId); + if (bodyInfo == null) + throw new BusinessException("该身体信息不存在"); + + BodyInfoDetailVo bodyInfoDetailVo = new BodyInfoDetailVo(); + PublicUtils.copyBean(bodyInfo, bodyInfoDetailVo); + bodyInfoDetailVo = bodyInfoCompare(bodyInfoDetailVo); + return bodyInfoDetailVo; + } + + /** 健康信息比较 **/ + private BodyInfoDetailVo bodyInfoCompare(BodyInfoDetailVo bodyInfoDetailVo) { + // TODO + // 血压 + bodyInfoDetailVo = bloodPressureCompare(bodyInfoDetailVo); + // 心率 + final int heartRate = bodyInfoDetailVo.getHeartRate(); + bodyInfoDetailVo.setHeartRateStatus(heartRateStatus(heartRate)); + bodyInfoDetailVo.setHeartRateDesc(statusDesc(bodyInfoDetailVo.getHeartRateStatus())); + // 体温 + final double temperature = bodyInfoDetailVo.getTemperature(); + bodyInfoDetailVo.setTemperatureStatus(temperatureStatus(temperature)); + bodyInfoDetailVo.setTemperatureDesc(statusDesc(bodyInfoDetailVo.getTemperatureStatus())); + return bodyInfoDetailVo; + } + + /** 血压判断 **/ + private BodyInfoDetailVo bloodPressureCompare(BodyInfoDetailVo bodyInfoDetailVo) { + // 收缩压 + // 正常范围收缩压90~139mmHg + final int minHighBloodPressure = 90; + final int maxHighBloodPressure = 139; + // 舒张压 + // 舒张压60~89mmHg + final int minLowBloodPressure = 60; + final int maxLowBloodPressure = 89; + // 高血压:成人收缩压≥140mmHg和(或)舒张压≥90mmHg + // 低血压:血压低于90/60mmHg + + if (bodyInfoDetailVo.getLowBloodPressure() > maxLowBloodPressure) { + bodyInfoDetailVo.setLowBloodPressureStatus(1); + bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.getLowBloodPressureStatus())); + } else if (bodyInfoDetailVo.getLowBloodPressure() < minLowBloodPressure) { + bodyInfoDetailVo.setLowBloodPressureStatus(-1); + bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.getLowBloodPressureStatus())); + } else { + bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo.getLowBloodPressureStatus())); + } + + if (bodyInfoDetailVo.getHighBloodPressure() > maxHighBloodPressure) { + bodyInfoDetailVo.setHighBloodPressureStatus(1); + bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.getHighBloodPressureStatus())); + } else if (bodyInfoDetailVo.getHighBloodPressure() < minHighBloodPressure) { + bodyInfoDetailVo.setHighBloodPressureStatus(-1); + bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.getHighBloodPressureStatus())); + } else { + bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo.getHighBloodPressureStatus())); + } + + if ((bodyInfoDetailVo.getLowBloodPressureStatus() == 1) + && (bodyInfoDetailVo.getHighBloodPressureStatus() == 1)) { + bodyInfoDetailVo.setBloodPressureDesc("该用户为高血压"); + } else if ((bodyInfoDetailVo.getLowBloodPressureStatus() == -1) + && (bodyInfoDetailVo.getHighBloodPressureStatus() == -1)) { + bodyInfoDetailVo.setBloodPressureDesc("该用户为低血压"); + } else if ((bodyInfoDetailVo.getLowBloodPressureStatus() == 0) + && (bodyInfoDetailVo.getHighBloodPressureStatus() == 0)) { + bodyInfoDetailVo.setBloodPressureDesc("该用户血压正常"); + } + + /* + * if ((bodyInfoDetailVo.getLowBloodPressure() > maxLowBloodPressure) || + * (bodyInfoDetailVo.getHighBloodPressure() > maxHighBloodPressure)) { + * bodyInfoDetailVo.setLowBloodPressureStatus(1); + * bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo. + * getLowBloodPressureStatus())); + * bodyInfoDetailVo.setHighBloodPressureStatus(1); + * bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo. + * getHighBloodPressureStatus())); } else if + * ((bodyInfoDetailVo.getLowBloodPressure() < minLowBloodPressure) || + * (bodyInfoDetailVo.getHighBloodPressure() < minHighBloodPressure)) { + * bodyInfoDetailVo.setLowBloodPressureStatus(-1); + * bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo. + * getLowBloodPressureStatus())); + * bodyInfoDetailVo.setHighBloodPressureStatus(-1); + * bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo. + * getHighBloodPressureStatus())); } else { + * bodyInfoDetailVo.setLowBloodPressureDesc(statusDesc(bodyInfoDetailVo. + * getLowBloodPressureStatus())); + * bodyInfoDetailVo.setHighBloodPressureDesc(statusDesc(bodyInfoDetailVo. + * getHighBloodPressureStatus())); } + */ + return bodyInfoDetailVo; + } + + /** 心率判断 **/ + private int heartRateStatus(int heartRate) { + final int min = 60; + final int max = 80; + final int max1 = 100; + final int max2 = 120; + if (heartRate < min) + return -1; + else if ((heartRate >= min) && (heartRate <= max)) + return 0; + else if ((heartRate > max) && (heartRate < max1)) + return 1; + else if ((heartRate > max1) && (heartRate < max2)) + return 2; + else + return 4; + } + + /** 体温判断 **/ + private int temperatureStatus(double temperature) { + final Double min = 36.1; + final Double max = 37.3; + final Double max1 = 38.1; + final Double max2 = 39.1; + final Double max3 = 41.1; + if (temperature < min) + return -1; + else if ((temperature >= min) && (temperature <= max)) + return 0; + else if ((temperature > max) && (temperature < max1)) + return 1; + else if ((temperature > max1) && (temperature < max2)) + return 2; + else if ((temperature > max2) && (temperature < max3)) + return 3; + else + return 4; + } + + /** 状态描述语 **/ + private String statusDesc(int status) { + switch (status) { + case -1: + return "低"; + case 1: + return "高"; + case 2: + return "很高"; + case 3: + return "严重"; + case 4: + return "非常严重"; + default: + return "正常"; + } + } + + @Override + public BodyInfoStatisticsVo getBodyStatistics(int userId, Date date) { + LocalDateTime now = null; + if (date == null) { + now = LocalDateTime.now(); + } else { + now = DateUtils.convertDateToLDT(date); + } + final LocalDateTime startWeekDateTime = DateUtils.getDayStart(DateUtils.getWeekBegin(now)); + final Date startWeek = DateUtils.convertLDTToDate(startWeekDateTime); + final Date endWeek = DateUtils.convertLDTToDate(DateUtils.getDayEnd(DateUtils.getWeekEnd(now))); + final List bodyInfos = bodyInfoMapper.selectByUserId(userId, startWeek, endWeek); + + final List bodyInfoVos = fillBodyInfoVo(startWeekDateTime, bodyInfos); + + return new BodyInfoStatisticsVo(bodyInfoVos.stream().map(BodyInfoVo::getTypeName).collect(Collectors.toList()), + bodyInfoVos); + } + + /** 填充数据 **/ + private List fillBodyInfoVo(LocalDateTime startWeekDateTime, List bodyInfos) { + final List bodyInfoVos = new ArrayList<>(); + final List lowBloodPressures = new ArrayList<>(); + final List highBloodPressures = new ArrayList<>(); + final List heartRates = new ArrayList<>(); + final List temperatures = new ArrayList<>(); + final List numberOfSetps = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + final Long targetTime = DateUtils.getMilliByTime(DateUtils.plus(startWeekDateTime, i, ChronoUnit.DAYS)); + boolean isSign = false; + for (final TBodyInfo tBodyInfo : bodyInfos) { + if (targetTime.equals(DateUtils.getMilliByTime( + DateUtils.getDayStart(DateUtils.convertDateToLDT(tBodyInfo.getCreateTime()))))) { + // 已打卡 + isSign = true; + lowBloodPressures.add(tBodyInfo.getLowBloodPressure()); + highBloodPressures.add(tBodyInfo.getHighBloodPressure()); + heartRates.add(tBodyInfo.getHeartRate()); + temperatures.add(tBodyInfo.getTemperature()); + numberOfSetps.add(tBodyInfo.getNumberOfStep()); + break; + } + } + if (!isSign) { + // 未打卡 + lowBloodPressures.add(0); + highBloodPressures.add(0); + heartRates.add(0); + temperatures.add(0); + numberOfSetps.add(0); + } + } + bodyInfoVos.add(new BodyInfoVo("舒张压", lowBloodPressures)); + bodyInfoVos.add(new BodyInfoVo("收缩压", highBloodPressures)); + bodyInfoVos.add(new BodyInfoVo("心率", heartRates)); + bodyInfoVos.add(new BodyInfoVo("体温", temperatures)); + bodyInfoVos.add(new BodyInfoVo("步数", numberOfSetps)); + + return bodyInfoVos; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java b/src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..5b97b5a --- /dev/null +++ b/src/main/java/top/beansprout/health/service/impl/UserServiceImpl.java @@ -0,0 +1,151 @@ +package top.beansprout.health.service.impl; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import top.beansprout.health.constant.SysConstant; +import top.beansprout.health.mapper.TUserMapper; +import top.beansprout.health.model.dto.UserLoginDto; +import top.beansprout.health.model.dto.UserRegisterDto; +import top.beansprout.health.model.dto.UserUpdateInfoDto; +import top.beansprout.health.model.dto.UserUpdatePasswordDto; +import top.beansprout.health.model.entity.TUser; +import top.beansprout.health.model.vo.BusinessException; +import top.beansprout.health.model.vo.UserInfoVo; +import top.beansprout.health.model.vo.UserLoginVo; +import top.beansprout.health.service.UserService; +import top.beansprout.health.util.PublicUtils; + +/** + *

Title: UserServiceImpl

+ *

Description: 用户业务逻辑操作

+ * + * @Auther: cyy + * @Date: 2020年4月25日 下午8:02:42 + * @Version: 1.0.0 + */ +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private HttpServletRequest request; + + @Autowired + private TUserMapper userMapper; + + @Override + public UserLoginVo login(UserLoginDto userLoginDto) { + final TUser user = userMapper.selectByUserName(userLoginDto.getUserName()); + if (PublicUtils.isBlank(user)) + throw new BusinessException("用户不存在"); + if (!user.getPassword().equalsIgnoreCase(userLoginDto.getPassWord())) + throw new BusinessException("密码不正确"); + + final UserLoginVo userLoginVo = new UserLoginVo(); + PublicUtils.copyBean(user, userLoginVo); + request.getSession().setAttribute(SysConstant.INIT_FIELD_USER_VO, userLoginVo); + return userLoginVo; + } + + @Override + @Transactional + public void register(UserRegisterDto userRegisterDto) { + if (!userRegisterDto.getPassWord().equalsIgnoreCase(userRegisterDto.getConfirmPassWord())) + throw new BusinessException("两次密码不一致"); + final TUser user = userMapper.selectByUserName(userRegisterDto.getUserName()); + if (PublicUtils.isNotBlank(user)) + throw new BusinessException("该账户已经注册"); + final TUser tUser = new TUser(); + tUser.setUserName(userRegisterDto.getUserName()); + tUser.setNickName(tUser.getUserName()); + tUser.setPassword(userRegisterDto.getPassWord()); + tUser.setHeadUrl("static/imgs/default.png"); //默认头像 + userMapper.insertByOne(tUser); + } + + @Override + public void logout(HttpServletRequest request) { + // 清除session + final Enumeration em = request.getSession().getAttributeNames(); + while (em.hasMoreElements()) { + request.getSession().removeAttribute(em.nextElement().toString()); + } + request.getSession().invalidate(); + // 请求初始化信息 + request.removeAttribute(SysConstant.INIT_FIELD_REQUEST_VO); + } + + @Override + public void updatePassword(HttpServletRequest request, int userId, UserUpdatePasswordDto updatePasswordDto) { + if (!updatePasswordDto.getPassWord().equalsIgnoreCase(updatePasswordDto.getConfirmPassWord())) + throw new BusinessException("两次密码不一致"); + final TUser user = userMapper.selectById(userId); + if (PublicUtils.isBlank(user)) + throw new BusinessException("该账户不存在"); + user.setPassword(updatePasswordDto.getPassWord()); + userMapper.updateByOne(user); + // 清空session数据 + logout(request); + } + + @Override + public UserInfoVo updateUserInfo(int userId, UserUpdateInfoDto userUpdateInfoDto) { + // 原始文件名 + String sourceName = userUpdateInfoDto.getHeadUrl().getOriginalFilename(); + final String fileType = sourceName.substring(sourceName.lastIndexOf(".")); + if (!".jpg".equals(fileType.toLowerCase()) && !".png".equals(fileType.toLowerCase())) + throw new BusinessException("只能上传jpg、png格式的图片"); + + // 获取文件上传的路径,在webapp下的static/upload/中 + final String base = "E:\\Workspaces\\HM_upload\\"; + final File fileDir = new File(base); + if (!fileDir.exists()) { + fileDir.mkdirs(); + } + + // 讲文件上传到upload目录 + sourceName = PublicUtils.join(PublicUtils.randomString(10), fileType); + final File upload = new File(PublicUtils.join(base, sourceName)); + //System.out.println(upload.toString()); + try { + userUpdateInfoDto.getHeadUrl().transferTo(upload); + } catch (final IOException e) { + throw new BusinessException("上传错误"); + } + + final String relativePath = PublicUtils.join("/HM_upload/", sourceName); + + final TUser user = userMapper.selectById(userId); + if (PublicUtils.isBlank(user)) + throw new BusinessException("该账户不存在"); + + user.setNickName(userUpdateInfoDto.getNickName()); + user.setEmail(userUpdateInfoDto.getEmail()); + user.setHeadUrl(relativePath); + userMapper.updateByUserInfoOne(user); + + final UserInfoVo userInfo = new UserInfoVo(); + PublicUtils.copyBean(user, userInfo); + refreshSessionUserInfo(userInfo); + + return userInfo; + } + + /** 刷新session中的用户信息 **/ + private void refreshSessionUserInfo(UserInfoVo userInfo) { + final HttpSession session = request.getSession(); + final UserLoginVo userLoginVo = (UserLoginVo) session.getAttribute(SysConstant.INIT_FIELD_USER_VO); + PublicUtils.copyBean(userInfo, userLoginVo); + + session.setAttribute(SysConstant.INIT_FIELD_USER_VO, userLoginVo); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/util/CollectionUtils.java b/src/main/java/top/beansprout/health/util/CollectionUtils.java new file mode 100644 index 0000000..aaa591e --- /dev/null +++ b/src/main/java/top/beansprout/health/util/CollectionUtils.java @@ -0,0 +1,69 @@ +package top.beansprout.health.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + *

Title: CollectionUtils

+ *

Description: 集合工具类

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/19 22:37 + */ +public class CollectionUtils { + + /** 判断集合是否为null或者size=0 **/ + public static boolean isEmpty(Collection coll) { + return CollectionUtils.isEmpty(coll); + } + + public static boolean isNotEmpty(Collection coll) { + return !isEmpty(coll); + } + + /** + * 将Iterable映射成列表 + * + * @param src + * @param mapper + * @return + */ + public static List map(final Iterable src, final Function mapper) { + final List r = new ArrayList<>(); + for (final T t : src) { + r.add(mapper.apply(t)); + } + return r; + } + + /** + * 数组转集合 + * @param key + * @return + */ + public static List arrayToList(T... key) { + return Arrays.asList(key); + } + + /** + * element 转换成指定集合 + * + * @param sourceCollection 源集合 + * @param collectionFactory 目标集合 + * @return DEST + */ + public static , DEST extends Collection> DEST + transformElements(SOURCETYPE sourceCollection, Supplier collectionFactory) { + final DEST dest = collectionFactory.get(); + sourceCollection.forEach(t -> { + dest.add(t); + }); + return dest; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/util/DateUtils.java b/src/main/java/top/beansprout/health/util/DateUtils.java new file mode 100644 index 0000000..e9afc26 --- /dev/null +++ b/src/main/java/top/beansprout/health/util/DateUtils.java @@ -0,0 +1,286 @@ +package top.beansprout.health.util; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.time.temporal.TemporalUnit; +import java.util.Date; + +import lombok.extern.slf4j.Slf4j; + +/** + *

Title: DateUtils

+ *

Description: 时间工具类

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/19 23:14 + */ +@Slf4j +public class DateUtils { + + /** HH:mm **/ + public static final String HHmm = "HH:mm"; + /** HH:mm:ss **/ + public static final String HHmmss = "HH:mm:ss"; + /** HH:mm:ss a **/ + public static final String HHmmssa = "HH:mm:ss a"; + + /** yyyyMM **/ + public static final String yyyyMM = "yyyyMM"; + /** yyyyMMdd **/ + public static final String yyyyMMdd = "yyyyMMdd"; + /** yyyyMMddHH **/ + public static final String yyyyMMddHH = "yyyyMMddHH"; + /** yyyyMMddHHmm **/ + public static final String yyyyMMddHHmm = "yyyyMMddHHmm"; + /** yyyyMMddHHmmss **/ + public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss"; + /** yyyyMMddHHmmss.SSS **/ + public static final String yyyyMMddHHmmssSSS = "yyyyMMddHHmmss.SSS"; + /** yyyyMMddTHHmmss.SSSZ **/ + public static final String yyyyMMddTHHmmssSSSZ = "yyyyMMdd'T'HHmmss.SSS'Z'"; + + /** MM-dd HH:mm **/ + public static final String MMddHHmm_LINE = "MM-dd HH:mm"; + /** yyyy-MM **/ + public static final String yyyyMM_LINE = "yyyy-MM"; + /** yyyy-MM-dd **/ + public static final String yyyyMMdd_LINE = "yyyy-MM-dd"; + /** yyyy-MM-dd HH **/ + public static final String yyyyMMddHH_LINE = "yyyy-MM-dd HH"; + /** yyyy-MM-dd HH:mm **/ + public static final String yyyyMMddHHmm_LINE = "yyyy-MM-dd HH:mm"; + /** yyyy-MM-dd HH:mm:ss **/ + public static final String yyyyMMddHHmmss_LINE = "yyyy-MM-dd HH:mm:ss"; + /** yyyy-MM-ddTHH:mm:ss **/ + public static final String yyyyMMddTHHmmss_LINE = "yyyy-MM-dd'T'HH:mm:ss"; + /** yyyy-MM-dd HH:mm:ss.SSS **/ + public static final String yyyyMMddHHmmssSSS_LINE = "yyyy-MM-dd HH:mm:ss.SSS"; + /** yyyy-MM-ddTHH:mm:ss.SSSZ **/ + public static final String yyyyMMddTHHmmssSSSZ_LINE = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + /** yyyy.MM **/ + public static final String yyyyMM_POINT = "yyyy.MM"; + /** yyyy.MM.dd **/ + public static final String yyyyMMdd_POINT = "yyyy.MM.dd"; + /** yyyy.MM.dd HH **/ + public static final String yyyyMMddHH_POINT = "yyyy.MM.dd HH"; + /** yyyy.MM.dd HH:mm **/ + public static final String yyyyMMddHHmm_POINT = "yyyy.MM.dd HH:mm"; + /** yyyy.MM.dd HH:mm:ss **/ + public static final String yyyyMMddHHmmss_POINT = "yyyy.MM.dd HH:mm:ss"; + /** yyyy.MM.dd HH:mm:ss.SSS **/ + public static final String yyyyMMddHHmmssSSS_POINT = "yyyy.MM.dd HH:mm:ss.SSS"; + /** yyyy.MM.ddTHH:mm:ss.SSSZ **/ + public static final String yyyyMMddTHHmmssSSSZ_POINT = "yyyy.MM.dd'T'HH:mm:ss.SSS'Z'"; + + /** MM/dd/yyyy, HH:mm:ss a **/ + public static final String MMddyyyyHHmmssa_SLASH = "MM/dd/yyyy, HH:mm:ss a"; + /** MM月MM **/ + public static final String yyyyMM_SLASH = "yyyy/MM"; + /** yyyy/MM/dd **/ + public static final String yyyyMMdd_SLASH = "yyyy/MM/dd"; + /** yyyy/MM/dd HH **/ + public static final String yyyyMMddHH_SLASH = "yyyy/MM/dd HH"; + /** yyyy/MM/dd HH:mm **/ + public static final String yyyyMMddHHmm_SLASH = "yyyy/MM/dd HH:mm"; + /** yyyy/MM/dd HH:mm:ss **/ + public static final String yyyyMMddHHmmss_SLASH = "yyyy/MM/dd HH:mm:ss"; + /** yyyy/MM/dd HH:mm:ss.SSS **/ + public static final String yyyyMMddHHmmssSSS_SLASH = "yyyy/MM/dd HH:mm:ss.SSS"; + /** yyyy/MM/ddTHH:mm:ss.SSSZ **/ + public static final String yyyyMMddTHHmmssSSSZ_SLASH = "yyyy/MM/dd'T'HH:mm:ss.SSS'Z'"; + + /** yyyy年MM月 **/ + public static final String yyyyMM_UNIT = "yyyy年MM月"; + /** yyyy年MM月dd日 **/ + public static final String yyyyMMdd_UNIT = "yyyy年MM月dd日"; + /** yyyy年MM月dd日 HH **/ + public static final String yyyyMMddHH_UNIT = "yyyy年MM月dd日 HH"; + /** yyyy年MM月dd日 HH:mm **/ + public static final String yyyyMMddHHmm_UNIT = "yyyy年MM月dd日 HH:mm"; + /** yyyy年MM月dd日 HH:mm:ss **/ + public static final String yyyyMMddHHmmss_UNIT = "yyyy年MM月dd日 HH:mm:ss"; + /** yyyy年MM月dd日 HH:mm:ss.SSS **/ + public static final String yyyyMMddHHmmssSSS_UNIT = "yyyy年MM月dd日 HH:mm:ss.SSS"; + /** yyyy年MM月dd日THH:mm:ss.SSSZ **/ + public static final String yyyyMMddTHHmmssSSSZ_UNIT = "yyyy年MM月dd日'T'HH:mm:ss.SSS'Z'"; + + public static final long SECONDS_TO_MINUTE = 60; + public static final long MINUTES_TO_HOUR = 60; + public static final long HOUR_TO_DAY = 24; + public static final long DAYS_TO_WEEK = 7; + public static final long DAYS_TO_MONTH = 31; + public static final long DAYS_TO_YEAR = 365; + public static final long MONTHES_TO_YEAR = 12; + + public static final long SECONDS_TO_HOUR = SECONDS_TO_MINUTE * MINUTES_TO_HOUR; + public static final long SECONDS_TO_DAY = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY; + public static final long SECONDS_TO_WEEK = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY * DAYS_TO_WEEK; + public static final long SECONDS_TO_MONTH = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY * DAYS_TO_MONTH;// 暂定义位每月31天 + public static final long SECONDS_TO_YEAR = SECONDS_TO_MINUTE * MINUTES_TO_HOUR * HOUR_TO_DAY * DAYS_TO_YEAR; + + private static final String[] weekDaysName = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" }; + private static final String[] weekDaysName_en = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", + "Saturday" }; + private static final String[] monthName = { "一月份", "二月份", "三月份", "四月份", "五月份", "六月份", "七月份", "八月份", "九月份", "十月份", + "十一月份", "十二月份" }; + + /** + * 格式化时间为LocalDateTime + * @param str + * @param pattern + * @return + */ + public static LocalDateTime parse(String str, String pattern) { + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.parse(str, formatter); + } + + /** + * 格式化时间为Date + * @param str + * @param pattern + * @return + */ + public static Date parseDate(String str, String pattern) { + return convertLDTToDate(parse(str, pattern)); + } + + /** + * Date转换为LocalDateTime + * @param date + * @return + */ + public static LocalDateTime convertDateToLDT(Date date) { + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + } + + /** + * LocalDateTime转换为Date + * + * @param time + * @return + */ + public static Date convertLDTToDate(LocalDateTime time) { + return Date.from(time.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * 获取指定日期的毫秒 + * @param time + * @return + */ + public static Long getMilliByTime(LocalDateTime time) { + return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + /** + * 获取指定日期的秒 + * @param time + * @return + */ + public static Long getSecondsByTime(LocalDateTime time) { + return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond(); + } + + /** + * 获取指定时间的指定格式 + * @param time + * @param pattern + * @return + */ + public static String formatTime(LocalDateTime time, String pattern) { + return time.format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 获取当前时间的指定格式 + * @param pattern + * @return + */ + public static String formatNow(String pattern) { + return formatTime(LocalDateTime.now(), pattern); + } + + /** + * 日期加上一个数,根据field不同加不同值,field为ChronoUnit.* + * @param time + * @param number + * @param field + * @return + */ + public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) { + return time.plus(number, field); + } + + /** + * 日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.* + * @param time + * @param number + * @param field + * @return + */ + public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field) { + return time.minus(number, field); + } + + /** + * 获取两个日期的差 field参数为ChronoUnit.* + * @param startTime + * @param endTime + * @param field 单位(年月日时分秒) + * @return + */ + public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) { + final Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime)); + if (field == ChronoUnit.YEARS) + return period.getYears(); + if (field == ChronoUnit.MONTHS) + return period.getYears() * 12 + period.getMonths(); + return field.between(startTime, endTime); + } + + /** + * 获取一天的开始时间,2017,7,22 00:00 + * @param time + * @return + */ + public static LocalDateTime getDayStart(LocalDateTime time) { + return time.withHour(0).withMinute(0).withSecond(0).withNano(0); + } + + /** + * 获取一天的结束时间,2017,7,22 23:59:59.999999999 + * @param time + * @return + */ + public static LocalDateTime getDayEnd(LocalDateTime time) { + return time.withHour(23).withMinute(59).withSecond(59).withNano(999999999); + } + + /** + * 获得指定时间的周一日期 + * @param time + * @return + */ + public static LocalDateTime getWeekBegin(LocalDateTime time) { + return time.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); + } + + /** + * 获得指定时间的周日日期 + * @param time + * @return + */ + public static LocalDateTime getWeekEnd(LocalDateTime time) { + return time.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/util/JsonUtils.java b/src/main/java/top/beansprout/health/util/JsonUtils.java new file mode 100644 index 0000000..53d1b49 --- /dev/null +++ b/src/main/java/top/beansprout/health/util/JsonUtils.java @@ -0,0 +1,57 @@ +package top.beansprout.health.util; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.slf4j.Slf4j; + +/** + *

Title: JsonUtils

+ *

Description: Json工具类,依赖 jackson

+ * @author beansprout + * @date 2020/3/18 23:39 + * @version 1.0 + */ +@Slf4j +public class JsonUtils { + + private final static ObjectMapper objMapper = new ObjectMapper(); + + /** + * Json字符串转化成对象 + * @param jsonString + * @param clazz + * @param + * @return + */ + public static T toObj(String jsonString, Class clazz) { + objMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + try { + return objMapper.readValue(jsonString, clazz); + } catch (final IOException e) { + log.error("Json string conversion object failed {}", e); + } + return null; + } + + /** + * javaBean 转化成json字符串 + * @param obj + * @return + */ + public static String toJson(Object obj) { + if((obj instanceof Integer) || (obj instanceof Long) || (obj instanceof Float) || + (obj instanceof Double) || (obj instanceof Boolean) || (obj instanceof String)) + return String.valueOf(obj); + try { + return objMapper.writeValueAsString(obj); + } catch (final JsonProcessingException e) { + log.error("Json object conversion string failed {}", e); + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/top/beansprout/health/util/MailUtils.java b/src/main/java/top/beansprout/health/util/MailUtils.java new file mode 100644 index 0000000..782e453 --- /dev/null +++ b/src/main/java/top/beansprout/health/util/MailUtils.java @@ -0,0 +1,74 @@ +package top.beansprout.health.util; + +import top.beansprout.health.model.vo.AuthVo; + +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.util.Properties; + +public class MailUtils { + private Properties props; //系统属性 + private Session mailSession; //邮件会话对象 + private MimeMessage mimeMsg; //MIME邮件对象 + + //获取6位随机验证码 + public String createVertifyCode(){ + String[] letters = new String[] { + "q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m", + "A","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M", + "0","1","2","3","4","5","6","7","8","9"}; + String stringBuilder =""; + for (int i = 0; i < 6; i++) { + stringBuilder = stringBuilder + letters[(int)Math.floor(Math.random()*letters.length)]; + } + return stringBuilder; + } + + public MailUtils() { + AuthVo au = new AuthVo("ateenliu@hunnu.edu.cn","jiAQr2Qp84xWn7GD"); + //设置系统属性 + props= System.getProperties(); //获得系统属性对象 + props.put("mail.smtp.host", "smtp.exmail.qq.com"); //设置SMTP主机 + props.put("mail.smtp.port", "465"); //设置服务端口号 + props.put("mail.smtp.auth", "true"); //同时通过验证 + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + //获得邮件会话对象 + mailSession = Session.getInstance(props, au); + } + + public boolean sendingMimeMail(String MailTo, String MailSubject, + String MailBody) { + try { + //创建MIME邮件对象 + mimeMsg=new MimeMessage(mailSession); + //设置发信人 + mimeMsg.setFrom(new InternetAddress("ateenliu@hunnu.edu.cn")); + //设置收信人 + if(MailTo!=null){ + mimeMsg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(MailTo)); + } +// //设置抄送人 +// if(MailCopyTo!=null){ +// mimeMsg.setRecipients(Message.RecipientType.CC,InternetAddress.parse(MailCopyTo)); +// } +// //设置暗送人 +// if(MailBCopyTo!=null){ +// mimeMsg.setRecipients(Message.RecipientType.BCC,InternetAddress.parse(MailBCopyTo)); +// } + //设置邮件主题 + mimeMsg.setSubject(MailSubject,"utf-8"); + //设置邮件内容,将邮件body部分转化为HTML格式 + mimeMsg.setContent(MailBody,"text/html;charset=utf-8"); + //发送邮件 + Transport.send(mimeMsg); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + +} diff --git a/src/main/java/top/beansprout/health/util/PublicUtils.java b/src/main/java/top/beansprout/health/util/PublicUtils.java new file mode 100644 index 0000000..5211a90 --- /dev/null +++ b/src/main/java/top/beansprout/health/util/PublicUtils.java @@ -0,0 +1,150 @@ +package top.beansprout.health.util; + +import java.util.Base64; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.BeanUtils; + +/** + *

Title: PublicUtils

+ *

Description: 公共方法

+ * + * @author beansprout + * @version 1.0 + * @date 2020/3/22 17:41 + */ +public class PublicUtils { + + /** + * 为空 + * @param obj + * @return + */ + public static boolean isBlank(Object obj) { + if (obj == null) + return true; + if (obj instanceof String) + return ((String) obj).trim().equals(""); + if (obj instanceof CharSequence) + return ((CharSequence) obj).length() == 0; + if (obj instanceof Object[]) + return ((Object[]) obj).length == 0; + if (obj instanceof Collection) + return ((Collection) obj).isEmpty(); + if (obj instanceof Map) + return ((Map) obj).isEmpty(); + return false; + } + + /** + * 不为空 + * @param obj + * @return + */ + public static boolean isNotBlank(Object obj) { + return !isBlank(obj); + } + + /** + * 随机指定长度的数字+字母字符串 + * @param leng + * @return + */ + public static String randomString(int leng) { + return RandomStringUtils.randomAlphanumeric(leng); + } + + /** + * 拼接字符串 + * @param values + * @return + */ + public static String join(Object ...values) { + if (values.length <= 0) return ""; + final StringBuilder stringBuilder = new StringBuilder(); + for (final Object value : values) { + stringBuilder.append(value); + } + return stringBuilder.toString(); + } + + /** + * Base64加密 + * @param value 原文 + * @return + */ + public static String encryptBase64(String value) { + if (isBlank(value)) return null; + final byte[] encode = Base64.getEncoder().encode(value.getBytes()); + return new String(encode); + } + + /** + * Base64解密 + * @param encrypt 密文 + * @return + */ + public static String decryptBase64(String encrypt) { + if (isBlank(encrypt)) return null; + final byte[] decode = Base64.getDecoder().decode(encrypt); + return new String(decode); + } + + /** + * 获取请求域参数 + * @param request + * @param key + * @return + */ + public static String getAttribute(HttpServletRequest request, String key) { + return getAttribute(request, key, String.class); + } + + public static T getAttribute(HttpServletRequest request, String key, Class requiredType) { + if ((request == null) || isBlank(key)) return null; + final Object value = request.getAttribute(key); + return isBlank(value) ? null : castValue(value, requiredType); + } + + /** Object类型转换 **/ + private static T castValue(Object value, Class requiredType) { + if ((requiredType == Date.class) && (value instanceof Long)) { + value = new Date((Long)value); + } + + if (value instanceof Integer) { + final int intValue = (Integer)value; + if (requiredType == Long.class) { + value = (long)intValue; + } else if ((requiredType == Short.class) && (-32768 <= intValue) && (intValue <= 32767)) { + value = (short)intValue; + } else if ((requiredType == Byte.class) && (-128 <= intValue) && (intValue <= 127)) { + value = (byte)intValue; + } + } + + if (!requiredType.isInstance(value)) + throw new RuntimeException( + "Expected value to be of type: " + requiredType + ", but was " + value.getClass()); + else + return requiredType.cast(value); + } + + /** + * Bean字段值复制 + * @param source 源 + * @param target 目标 + * @return 目标 + */ + public static T copyBean(T source, T target) { + if (isBlank(source) && isBlank(target)) return null; + BeanUtils.copyProperties(source, target); + return target; + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..77538f0 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,4 @@ +jdbc.driver=com.mysql.cj.jdbc.Driver +jdbc.url=jdbc:mysql://localhost:3306/health?useUnicode=true&characterEncoding=UTF-8 +jdbc.username=health +jdbc.password=health \ No newline at end of file diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..12b5cc8 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,32 @@ +### set log levels ### +log4j.rootLogger=DEBUG , console , debug , error +### console ### +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Target=System.out +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n +### log file ### +#log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender +#log4j.appender.debug.File=log.log +#log4j.appender.debug.Append=true +#log4j.appender.debug.Threshold=INFO +#log4j.appender.debug.layout=org.apache.log4j.PatternLayout +#log4j.appender.debug.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n +### exception ### +log4j.appender.error=org.apache.log4j.DailyRollingFileAppender +log4j.appender.error.File=error-log.log +log4j.appender.error.Append=true +log4j.appender.error.Threshold=ERROR +log4j.appender.error.layout=org.apache.log4j.PatternLayout +log4j.appender.error.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n +###需要声明,然后下方才可以使druid sql输出,否则会抛出log4j.error.key not found +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %l %c%n%p: %m%n +### druid sql ### +log4j.logger.druid.sql=warn,stdout +log4j.logger.druid.sql.DataSource=warn,stdout +log4j.logger.druid.sql.Connection=warn,stdout +log4j.logger.druid.sql.Statement=warn,stdout +log4j.logger.druid.sql.ResultSet=warn,stdout \ No newline at end of file diff --git a/src/main/resources/mapper/TBodyInfoMapper.xml b/src/main/resources/mapper/TBodyInfoMapper.xml new file mode 100644 index 0000000..9b8625f --- /dev/null +++ b/src/main/resources/mapper/TBodyInfoMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/THealthConfigMapper.xml b/src/main/resources/mapper/THealthConfigMapper.xml new file mode 100644 index 0000000..05aafe4 --- /dev/null +++ b/src/main/resources/mapper/THealthConfigMapper.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/TUserMapper.xml b/src/main/resources/mapper/TUserMapper.xml new file mode 100644 index 0000000..65f8c68 --- /dev/null +++ b/src/main/resources/mapper/TUserMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/mybatis-config.xml b/src/main/resources/spring/mybatis-config.xml new file mode 100644 index 0000000..a200d4f --- /dev/null +++ b/src/main/resources/spring/mybatis-config.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-dao.xml b/src/main/resources/spring/spring-dao.xml new file mode 100644 index 0000000..507b330 --- /dev/null +++ b/src/main/resources/spring/spring-dao.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + helperDialect=mysql + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-mvc.xml b/src/main/resources/spring/spring-mvc.xml new file mode 100644 index 0000000..e30d3cc --- /dev/null +++ b/src/main/resources/spring/spring-mvc.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring-service.xml b/src/main/resources/spring/spring-service.xml new file mode 100644 index 0000000..948320a --- /dev/null +++ b/src/main/resources/spring/spring-service.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/spring/spring.xml b/src/main/resources/spring/spring.xml new file mode 100644 index 0000000..28afb2b --- /dev/null +++ b/src/main/resources/spring/spring.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/error/401.jsp b/src/main/webapp/WEB-INF/error/401.jsp new file mode 100644 index 0000000..047766b --- /dev/null +++ b/src/main/webapp/WEB-INF/error/401.jsp @@ -0,0 +1,71 @@ +<%-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--%> +<%@ page session="false" trimDirectiveWhitespaces="true" %> + + + + 401 Unauthorized + + + +

401 Unauthorized

+

+ You are not authorized to view this page. If you have not changed + any configuration files, please examine the file + conf/tomcat-users.xml in your installation. That + file must contain the credentials to let you use this webapp. +

+

+ For example, to add the admin-gui role to a user named + tomcat with a password of s3cret, add the following to the + config file listed above. +

+
+<role rolename="admin-gui"/>
+<user username="tomcat" password="s3cret" roles="admin-gui"/>
+
+

+ Note that for Tomcat 7 onwards, the roles required to use the host manager + application were changed from the single admin role to the + following two roles. You will need to assign the role(s) required for + the functionality you wish to access. +

+
    +
  • admin-gui - allows access to the HTML GUI
  • +
  • admin-script - allows access to the text interface
  • +
+

+ The HTML interface is protected against CSRF but the text interface is not. + To maintain the CSRF protection: +

+
    +
  • Users with the admin-gui role should not be granted the + admin-script role.
  • +
  • If the text interface is accessed through a browser (e.g. for testing + since this interface is intended for tools not humans) then the browser + must be closed afterwards to terminate the session.
  • +
+ + + diff --git a/src/main/webapp/WEB-INF/error/403.jsp b/src/main/webapp/WEB-INF/error/403.jsp new file mode 100644 index 0000000..74e1e2d --- /dev/null +++ b/src/main/webapp/WEB-INF/error/403.jsp @@ -0,0 +1,90 @@ +<%-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--%> +<%@ page session="false" trimDirectiveWhitespaces="true" %> + + + + 403 Access Denied + + + +

403 Access Denied

+

+ You are not authorized to view this page. +

+

+ By default the Host Manager is only accessible from a browser running on the + same machine as Tomcat. If you wish to modify this restriction, you'll need + to edit the Host Manager's context.xml file. +

+

+ If you have already configured the Host Manager application to allow access + and you have used your browsers back button, used a saved book-mark or + similar then you may have triggered the cross-site request forgery (CSRF) + protection that has been enabled for the HTML interface of the Host Manager + application. You will need to reset this protection by returning to the + main Host Manager page. + Once you return to this page, you will be able to continue using the Host + Manager application's HTML interface normally. If you continue to see this + access denied message, check that you have the necessary permissions to + access this application. +

+

If you have not changed + any configuration files, please examine the file + conf/tomcat-users.xml in your installation. That + file must contain the credentials to let you use this webapp. +

+

+ For example, to add the admin-gui role to a user named + tomcat with a password of s3cret, add the following to the + config file listed above. +

+
+<role rolename="admin-gui"/>
+<user username="tomcat" password="s3cret" roles="admin-gui"/>
+
+

+ Note that for Tomcat 7 onwards, the roles required to use the host manager + application were changed from the single admin role to the + following two roles. You will need to assign the role(s) required for + the functionality you wish to access. +

+
    +
  • admin-gui - allows access to the HTML GUI
  • +
  • admin-script - allows access to the text interface
  • +
+

+ The HTML interface is protected against CSRF but the text interface is not. + To maintain the CSRF protection: +

+
    +
  • Users with the admin-gui role should not be granted the + admin-script role.
  • +
  • If the text interface is accessed through a browser (e.g. for testing + since this interface is intended for tools not humans) then the browser + must be closed afterwards to terminate the session.
  • +
+ + + diff --git a/src/main/webapp/WEB-INF/error/404.jsp b/src/main/webapp/WEB-INF/error/404.jsp new file mode 100644 index 0000000..2bf507a --- /dev/null +++ b/src/main/webapp/WEB-INF/error/404.jsp @@ -0,0 +1,60 @@ + + + +404 Not found + + + +

404 Not found

+

+ The page you tried to access (<%=request.getAttribute("javax.servlet.error.request_uri")%>) + ${ baseurl } + does not exist. +

+

The Host Manager application has been re-structured for Tomcat 7 + onwards and some URLs have changed. All URLs used to access the + Manager application should now start with one of the following + options:

+
    +
  • <%=request.getContextPath()%>/html for the HTML GUI
  • +
  • <%=request.getContextPath()%>/text for the text interface
  • +
+

+ Note that the URL for the text interface has changed from "<%=request.getContextPath()%>" + to "<%=request.getContextPath()%>/text". +

+

You probably need to adjust the URL you are using to access the + Host Manager application. However, there is always a chance you have + found a bug in the Host Manager application. If you are sure you have + found a bug, and that the bug has not already been reported, please + report it to the Apache Tomcat team.

+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/error/500.jsp b/src/main/webapp/WEB-INF/error/500.jsp new file mode 100644 index 0000000..522b3d0 --- /dev/null +++ b/src/main/webapp/WEB-INF/error/500.jsp @@ -0,0 +1,12 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +错误页面 + + +<% System.out.print(request.getAttribute("message")); %> + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/view/base.jsp b/src/main/webapp/WEB-INF/view/base.jsp new file mode 100644 index 0000000..5a5a108 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/base.jsp @@ -0,0 +1,8 @@ + +<%-- <%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> --%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> + + diff --git a/src/main/webapp/WEB-INF/view/bodyInfoInput.jsp b/src/main/webapp/WEB-INF/view/bodyInfoInput.jsp new file mode 100644 index 0000000..7405480 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/bodyInfoInput.jsp @@ -0,0 +1,215 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp"%> + + + + + 身体信息录入 + + + + + +
+
+
+
+ 昵称 +
+
+ 舒张压 +
舒张压必须在0~200之间
+
+
+ 收缩压 +
收缩压必须在0~200之间
+
+
+ 心率 +
心率必须在40~200之间
+
+
+ 体温 +
体温必须在33~45度之间
+
+
+ 食欲 +
请选择食欲
+
+
+ 体重KG +
体重必须在0~200之间
+
+
+ 步数 +
+
+ + +
+
+
+
+ + + + diff --git a/src/main/webapp/WEB-INF/view/bodyInofList.jsp b/src/main/webapp/WEB-INF/view/bodyInofList.jsp new file mode 100644 index 0000000..d3090f8 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/bodyInofList.jsp @@ -0,0 +1,234 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp" %> + + + + 用户身体信息列表 + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
序号打卡时间收缩压舒张压心率体温操作
1/2 +
+
+ + + + + diff --git a/src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp b/src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp new file mode 100644 index 0000000..8e21eda --- /dev/null +++ b/src/main/webapp/WEB-INF/view/bodyInofStatistics.jsp @@ -0,0 +1,175 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp" %> + + + + 用户身体信息统计 + + + + + +
+ + + + + +
+
+ + + + + + + + + diff --git a/src/main/webapp/WEB-INF/view/home.jsp b/src/main/webapp/WEB-INF/view/home.jsp new file mode 100644 index 0000000..f8e7963 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/home.jsp @@ -0,0 +1,181 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp"%> + + + + + 首页 + + + + + +
+ +
+ +
+ + + + + +
+ + +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/view/register.jsp b/src/main/webapp/WEB-INF/view/register.jsp new file mode 100644 index 0000000..9c4c065 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/register.jsp @@ -0,0 +1,150 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp" %> + + + + 注册 + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/view/top.jsp b/src/main/webapp/WEB-INF/view/top.jsp new file mode 100644 index 0000000..7b81120 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/top.jsp @@ -0,0 +1,55 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + + 头部 + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/view/updatePassword.jsp b/src/main/webapp/WEB-INF/view/updatePassword.jsp new file mode 100644 index 0000000..72fcadc --- /dev/null +++ b/src/main/webapp/WEB-INF/view/updatePassword.jsp @@ -0,0 +1,120 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp"%> + + + + + 修改密码 + + + + + +
+
+
+
+ +
+
+ +
+ 密码必须在6~10位之间 +
+
+
+ +
+ 两次密码必须相同 且在6~10位之间 +
+
+
+ +
+
+
+
+ + + + diff --git a/src/main/webapp/WEB-INF/view/userInfo.jsp b/src/main/webapp/WEB-INF/view/userInfo.jsp new file mode 100644 index 0000000..aaf3bd7 --- /dev/null +++ b/src/main/webapp/WEB-INF/view/userInfo.jsp @@ -0,0 +1,200 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="base.jsp"%> + + + + + 用户信息 + + + + +
+
+
+
+
+ 昵称 +
昵称必须在2~10之间
+
+
+ 账号 +
+
+ 邮箱 +
邮箱不能为空
+ + +
+
+ 验证 +
验证码不能为空
+
+
+ 头像 +
请选择头像文件
+
+ +
+ + + +
+
+
+
+
+ + + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..eee39cd --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,89 @@ + + + + 个人健康信息管理 + + + + contextConfigLocation + classpath:spring/spring.xml + + + + org.springframework.web.context.ContextLoaderListener + + + + crossXssFilter + top.beansprout.health.config.CrossXssFilter + + + crossXssFilter + /* + + + + + characterEncodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + + encoding + UTF-8 + + + + forceEncoding + true + + + + characterEncodingFilter + /* + + + + + dispatcher + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + classpath:spring/spring-mvc.xml + + 1 + + + dispatcher + / + + + + index.jsp + + + java.lang.Exception + /WEB-INF/error/500.jsp + + + 401 + /WEB-INF/error/401.jsp + + + 403 + /WEB-INF/error/403.jsp + + + 404 + /WEB-INF/error/404.jsp + + + 405 + /WEB-INF/error/404.jsp + + + \ No newline at end of file diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..bfc8fec --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,128 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@include file="WEB-INF/view/base.jsp"%> + + + + 登录 + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/static/css/base.css b/src/main/webapp/static/css/base.css new file mode 100644 index 0000000..934a655 --- /dev/null +++ b/src/main/webapp/static/css/base.css @@ -0,0 +1,3381 @@ +/* +========================================================== +========================================================== + +Bootstrap 4 Admin Template + +https://bootstrapious.com/p/admin-template + +========================================================== +========================================================== +*/ +/* +* ========================================================== +* GENERAL STYLES +* ========================================================== +*/ +body { + overflow-x: hidden; +} + +a, +i, +span { + display: inline-block; + text-decoration: none; + -webkit-transition: all 0.3s; + transition: all 0.3s; +} + +a:hover, a:focus, +i:hover, +i:focus, +span:hover, +span:focus { + text-decoration: none; +} + +section { + padding: 50px 0; +} + +canvas { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.container-fluid { + padding: 0 30px; +} + +@media (max-width: 575px) { + .container-fluid { + padding: 0 15px; + } +} + +header.page-header { + padding: 20px 0; +} + +table { + font-size: 0.9em; + color: #666; +} + +.card-close { + position: absolute; + top: 15px; + right: 15px; +} + +.card-close .dropdown-toggle { + color: #999; + background: none; + border: none; +} + +.card-close .dropdown-toggle:after { + display: none; +} + +.card-close .dropdown-menu { + border: none; + min-width: auto; + font-size: 0.9em; + border-radius: 0; + -webkit-box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.1), -2px -2px 3px rgba(0, 0, 0, 0.1); + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.1), -2px -2px 3px rgba(0, 0, 0, 0.1); +} + +.card-close .dropdown-menu a { + color: #999 !important; +} + +.card-close .dropdown-menu a:hover { + background: #796AEE; + color: #fff !important; +} + +.card-close .dropdown-menu a i { + margin-right: 10px; + -webkit-transition: none; + transition: none; +} + +.content-inner { + position: relative; + width: calc(100% - 250px); + min-height: calc(100vh - 70px); + padding-bottom: 60px; +} + +.content-inner.active { + width: calc(100% - 90px); +} + +.page-header { + background: #fff; + padding: 20px; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1); + position: relative; + z-index: 8; +} + +*[class*="icon-"] { + -webkit-transform: translateY(3px); + transform: translateY(3px); +} + +button, +input { + outline: none !important; +} + +.card { + margin-bottom: 30px; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05); +} + +.card-header { + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.05); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.05); +} + +.card-header h1, +.card-header h2, +.card-header h3, +.card-header h4, +.card-header h5, +.card-header h6 { + margin-bottom: 0; +} + +.breadcrumb-holder { + background: #fff; +} + +.breadcrumb { + background: #fff; + position: relative; + z-index: 7; + border-radius: 0; + padding: 15px 0; + margin-bottom: 0; +} + +.breadcrumb li.breadcrumb-item { + color: #aaa; + font-weight: 300; +} + +/*=== Helpers ===*/ +.text-bold { + font-weight: 700; +} + +.text-small { + font-size: 0.9rem; +} + +.text-xsmall { + font-size: 0.8rem; +} + +.bg-red { + background: #ff7676 !important; + color: #fff; +} + +.bg-red:hover { + color: #fff; +} + +.bg-blue { + background: #85b4f2 !important; + color: #fff; +} + +.bg-blue:hover { + color: #fff; +} + +.bg-yellow { + background: #eef157 !important; + color: #fff; +} + +.bg-yellow:hover { + color: #fff; +} + +.bg-green { + background: #54e69d !important; + color: #fff; +} + +.bg-green:hover { + color: #fff; +} + +.bg-orange { + background: #ffc36d !important; + color: #fff; +} + +.bg-orange:hover { + color: #fff; +} + +.bg-violet { + background: #796AEE !important; + color: #fff; +} + +.bg-violet:hover { + color: #fff; +} + +.bg-gray { + background: #ced4da !important; +} + +.bg-white { + background: #fff !important; +} + +.text-red { + color: #ff7676; +} + +.text-red:hover { + color: #ff7676; +} + +.text-yellow { + color: #eef157; +} + +.text-yellow:hover { + color: #eef157; +} + +.text-green { + color: #54e69d; +} + +.text-green:hover { + color: #54e69d; +} + +.text-orange { + color: #ffc36d; +} + +.text-orange:hover { + color: #ffc36d; +} + +.text-violet { + color: #796AEE; +} + +.text-violet:hover { + color: #796AEE; +} + +.text-blue { + color: #85b4f2; +} + +.text-blue:hover { + color: #85b4f2; +} + +.text-gray { + color: #ced4da; +} + +.text-gray:hover { + color: #ced4da; +} + +.text-uppercase { + letter-spacing: 0.2em; +} + +.lh-2 { + line-height: 2; +} + +.page { + background: #EEF5F9; +} + +.page .text-white { + color: #fff; +} + +.no-padding { + padding: 0 !important; +} + +.no-padding-bottom { + padding-bottom: 0 !important; +} + +.no-padding-top { + padding-top: 0 !important; +} + +.no-margin { + margin: 0 !important; +} + +.no-margin-bottom { + margin-bottom: 0 !important; +} + +.no-margin-top { + margin-top: 0 !important; +} + +.page { + overflow-x: hidden; +} + +.has-shadow { + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -1px 0 2px rgba(0, 0, 0, 0.05); +} + +.badge { + font-weight: 400; +} + +.badge-rounded { + border-radius: 50px; +} + +.list-group-item { + border-right: 0; + border-left: 0; +} + +.list-group-item:first-child, .list-group-item:last-child { + border-radius: 0; +} + +.overflow-hidden { + overflow: hidden; +} + +.tile-link { + position: absolute; + cursor: pointer; + width: 100%; + height: 100%; + left: 0; + top: 0; + z-index: 30; +} + +/* +* ========================================================== +* SIDEBAR +* ========================================================== +*/ +nav.side-navbar { + background: #fff; + min-width: 250px; + max-width: 250px; + color: #686a76; + -webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); + z-index: 9; + /*==== Sidebar Header ====*/ + /*==== Sidebar Menu ====*/ + /*==== Shrinked Sidebar ====*/ +} + +nav.side-navbar a { + color: inherit; + position: relative; + font-size: 0.9em; +} + +nav.side-navbar a[data-toggle="collapse"]::before { + content: '\f104'; + display: inline-block; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + font-family: 'FontAwesome'; + position: absolute; + top: 50%; + right: 20px; +} + +nav.side-navbar a[aria-expanded="true"] { + background: #EEF5F9; +} + +nav.side-navbar a[aria-expanded="true"]::before { + content: '\f107'; +} + +nav.side-navbar a i { + font-size: 1.2em; + margin-right: 10px; + -webkit-transition: none; + transition: none; +} + +nav.side-navbar .sidebar-header { + padding: 30px 15px; +} + +nav.side-navbar .avatar { + width: 55px; + height: 55px; +} + +nav.side-navbar .title { + margin-left: 10px; +} + +nav.side-navbar .title h1 { + color: #333; +} + +nav.side-navbar .title p { + font-size: 0.9em; + font-weight: 200; + margin-bottom: 0; + color: #aaa; +} + +nav.side-navbar span.heading { + text-transform: uppercase; + font-weight: 400; + margin-left: 20px; + color: #ccc; + font-size: 0.8em; +} + +nav.side-navbar ul { + padding: 15px 0; +} + +nav.side-navbar ul li { + /* submenu item active */ +} + +nav.side-navbar ul li a { + padding: 10px 15px; + text-decoration: none; + display: block; + font-weight: 300; + border-left: 4px solid transparent; +} + +nav.side-navbar ul li a:hover { + background: #796AEE; + border-left: 4px solid #3b25e6; + color: #fff; +} + +nav.side-navbar ul li li a { + padding-left: 50px; + background: #EEF5F9; +} + +nav.side-navbar ul li.active > a { + background: #796AEE; + color: #fff; + border-left: 4px solid #3b25e6; +} + +nav.side-navbar ul li.active > a:hover { + background: #796AEE; +} + +nav.side-navbar ul li li.active > a { + background: #8e81f1; +} + +nav.side-navbar ul li ul { + padding: 0; +} + +nav.side-navbar.shrinked { + min-width: 90px; + max-width: 90px; + text-align: center; +} + +nav.side-navbar.shrinked span.heading { + margin: 0; +} + +nav.side-navbar.shrinked ul li a { + padding: 15px 2px; + border: none; + font-size: 0.8em; + color: #aaa; + -webkit-transition: color 0.3s, background 0.3s; + transition: color 0.3s, background 0.3s; +} + +nav.side-navbar.shrinked ul li a[data-toggle="collapse"]::before { + content: '\f107'; + -webkit-transform: translateX(50%); + transform: translateX(50%); + position: absolute; + top: auto; + right: 50%; + bottom: 0; + left: auto; +} + +nav.side-navbar.shrinked ul li a[data-toggle="collapse"][aria-expanded="true"]::before { + content: '\f106'; +} + +nav.side-navbar.shrinked ul li a:hover { + color: #fff; + border: none; +} + +nav.side-navbar.shrinked ul li a:hover i { + color: #fff; +} + +nav.side-navbar.shrinked ul li a i { + margin-right: 0; + margin-bottom: 2px; + display: block; + font-size: 1rem; + color: #333; + -webkit-transition: color 0.3s; + transition: color 0.3s; +} + +nav.side-navbar.shrinked ul li.active > a { + color: #fff; +} + +nav.side-navbar.shrinked ul li.active > a i { + color: #fff; +} + +nav.side-navbar.shrinked .sidebar-header .title { + display: none; +} + +/* SIDEBAR MEDIAQUERIES ----------------------------------- */ +@media (max-width: 1199px) { + nav.side-navbar { + margin-left: -90px; + min-width: 90px; + max-width: 90px; + text-align: center; + overflow: hidden; + } + nav.side-navbar span.heading { + margin: 0; + } + nav.side-navbar ul li a { + padding: 15px 2px; + border: none; + font-size: 0.8em; + color: #aaa; + -webkit-transition: color 0.3s, background 0.3s; + transition: color 0.3s, background 0.3s; + } + nav.side-navbar ul li a[data-toggle="collapse"]::before { + content: '\f107'; + -webkit-transform: translateX(50%); + transform: translateX(50%); + position: absolute; + top: auto; + right: 50%; + bottom: 0; + left: auto; + } + nav.side-navbar ul li a[data-toggle="collapse"][aria-expanded="true"]::before { + content: '\f106'; + } + nav.side-navbar ul li a:hover { + color: #fff; + border: none; + } + nav.side-navbar ul li a:hover i { + color: #fff; + } + nav.side-navbar ul li a i { + margin-right: 0; + margin-bottom: 5px; + display: block; + font-size: 1.6em; + color: #333; + -webkit-transition: color 0.3s; + transition: color 0.3s; + } + nav.side-navbar ul li.active > a { + color: #fff; + } + nav.side-navbar ul li.active > a i { + color: #fff; + } + nav.side-navbar .sidebar-header .title { + display: none; + } + nav.side-navbar.shrinked { + margin-left: 0; + } + .content-inner { + width: 100%; + } + .content-inner.active { + width: calc(100% - 90px); + } +} + +/* +* ========================================================== +* MAIN NAVBAR +* ========================================================== +*/ +nav.navbar { + background: #2f333e; + padding-top: 15px; + padding-bottom: 15px; + color: #fff; + position: relative; + border-radius: 0; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2); + z-index: 10; + padding-left: 0; + padding-right: 0; + /*==== Toggle Sidebar Btn ====*/ + /*==== Navbar Items ====*/ + /*==== Search Box ====*/ + /*==== Dropdowns ====*/ +} + +nav.navbar .badge { + width: 22px; + height: 22px; + line-height: 22px; + text-align: center; + padding: 0; + border-radius: 50%; +} + +nav.navbar .navbar-holder { + width: 100%; +} + +nav.navbar a { + color: inherit; +} + +nav.navbar .container-fluid { + width: 100%; +} + +nav.navbar .menu-btn { + margin-right: 20px; + font-size: 1.2em; + -webkit-transition: all 0.7s; + transition: all 0.7s; +} + +nav.navbar .menu-btn span { + width: 20px; + height: 2px; + background: #fff; + display: block; + margin: 4px auto 0; + -webkit-transition: all 0.3s cubic-bezier(0.81, -0.33, 0.345, 1.375); + transition: all 0.3s cubic-bezier(0.81, -0.33, 0.345, 1.375); +} + +nav.navbar .menu-btn span:nth-of-type(2) { + position: relative; + width: 35px; + -webkit-transform: rotateY(180deg); + transform: rotateY(180deg); +} + +nav.navbar .menu-btn span:nth-of-type(2)::before, nav.navbar .menu-btn span:nth-of-type(2)::after { + content: ''; + width: 6px; + height: 2px; + display: block; + background: #fff; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + position: absolute; + top: 2px; + left: 0; + -webkit-transition: all 0.7s; + transition: all 0.7s; +} + +nav.navbar .menu-btn span:nth-of-type(2)::after { + -webkit-transform: rotate(145deg); + transform: rotate(145deg); + position: absolute; + top: -2px; + left: 0; +} + +nav.navbar .menu-btn.active span:first-of-type { + -webkit-transform: translateY(12px); + transform: translateY(12px); +} + +nav.navbar .menu-btn.active span:nth-of-type(2) { + -webkit-transform: none; + transform: none; +} + +nav.navbar .menu-btn.active span:last-of-type { + -webkit-transform: translateY(-12px); + transform: translateY(-12px); +} + +nav.navbar .nav-link { + position: relative; +} + +nav.navbar .nav-link span.badge-corner { + position: absolute; + top: 0; + right: 0; + font-weight: 400; + font-size: 0.7em; +} + +nav.navbar .nav-link.language span { + margin-left: .3rem; + vertical-align: middle; +} + +nav.navbar .nav-link.logout i { + margin-left: 10px; +} + +nav.navbar .nav-menu { + margin-bottom: 0; +} + +nav.navbar .search-box { + width: 100%; + height: 100%; + position: absolute; + top: 0; + right: 0; + padding: 0; + background: #fff; + z-index: 12; + border-radius: 0; + display: none; +} + +nav.navbar .search-box .dismiss { + position: absolute; + top: 50%; + right: 20px; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + font-size: 1.5em; + color: #999; +} + +nav.navbar .search-box form { + height: 100%; +} + +nav.navbar .search-box form input { + height: 100%; + border: none; + padding: 20px; +} + +nav.navbar .dropdown-toggle::after { + border: none !important; + content: '\f107'; + vertical-align: baseline; + font-family: 'FontAwesome'; + margin-left: .3rem; +} + +nav.navbar .dropdown-menu { + right: 0; + min-width: 250px; + left: auto; + margin-top: 15px; + margin-bottom: 0; + padding: 15px 0; + max-width: 400px; + border-radius: 0; + border: none; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -2px 0 2px rgba(0, 0, 0, 0.1); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1), -2px 0 2px rgba(0, 0, 0, 0.1); +} + +nav.navbar .dropdown-menu .dropdown-item { + background: #fff; + padding: 10px 20px; + font-size: 0.8rem; + color: #777; + width: 100%; +} + +nav.navbar .dropdown-menu .dropdown-item:hover { + background: #f5f5f5; +} + +nav.navbar .dropdown-menu .dropdown-item i { + width: 30px; + height: 30px; + line-height: 30px; + background: #796AEE; + text-align: center; + color: #fff; + border-radius: 50%; + margin-right: 10px; +} + +nav.navbar .dropdown-menu .dropdown-item small { + margin-left: 40px; +} + +nav.navbar .dropdown-menu span { + position: static; + font-size: 0.9em; + color: #999; +} + +nav.navbar .dropdown-menu strong { + font-weight: 700; +} + +nav.navbar .dropdown-menu .msg-profile { + width: 45px; + height: 45px; + margin-right: 10px; +} + +nav.navbar .dropdown-menu h3 { + font-weight: 500; +} + +/* MAIN NAVBAR MEDIAQUERIES ----------------------------------- */ +@media (max-width: 1199px) { + nav.navbar { + /*==== Toggle Sidebar Btn ====*/ + } + nav.navbar .menu-btn { + margin-right: 20px; + font-size: 1.2em; + -webkit-transition: all 0.7s; + transition: all 0.7s; + } + nav.navbar .menu-btn span:first-of-type { + -webkit-transform: translateY(12px); + transform: translateY(12px); + } + nav.navbar .menu-btn span:nth-of-type(2) { + -webkit-transform: none; + transform: none; + } + nav.navbar .menu-btn span:nth-of-type(2)::before, nav.navbar .menu-btn span:nth-of-type(2)::after { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + } + nav.navbar .menu-btn span:nth-of-type(2)::after { + -webkit-transform: rotate(145deg); + transform: rotate(145deg); + position: absolute; + top: -2px; + left: 0; + } + nav.navbar .menu-btn span:last-of-type { + -webkit-transform: translateY(-12px); + transform: translateY(-12px); + } + nav.navbar .menu-btn.active span:first-of-type { + -webkit-transform: none; + transform: none; + } + nav.navbar .menu-btn.active span:nth-of-type(2) { + -webkit-transform: rotateY(180deg); + transform: rotateY(180deg); + } + nav.navbar .menu-btn.active span:last-of-type { + -webkit-transform: none; + transform: none; + } +} + +@media (max-width: 575px) { + nav.navbar { + font-size: 14px; + } + nav.navbar .badge { + width: 19px; + height: 19px; + } + nav.navbar .nav-item > a { + font-size: 13px; + } + nav.navbar .dropdown-menu { + right: auto; + left: -50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + } +} + +/* +* ========================================================== +* DASHBOARD COUNTS SECTION +* ========================================================== +*/ +section.dashboard-counts .icon { + width: 40px; + height: 40px; + line-height: 40px; + text-align: center; + min-width: 40px; + max-width: 40px; + border-radius: 50%; +} + +section.dashboard-counts .title { + font-size: 1.1em; + font-weight: 300; + color: #777; + margin: 0 20px; +} + +section.dashboard-counts .progress { + margin-top: 10px; + height: 4px; +} + +section.dashboard-counts .number { + font-size: 1.8em; + font-weight: 300; +} + +section.dashboard-counts .number strong { + font-weight: 700; +} + +section.dashboard-counts .row { + padding: 30px 15px; + margin: 0; +} + +section.dashboard-counts div[class*='col-'] .item { + border-right: 1px solid #eee; + padding: 15px 0; +} + +section.dashboard-counts div[class*='col-']:last-of-type .item { + border-right: none; +} + +/* DASHBOARD COUNTS MEDIAQUERIES ------------------------ */ +@media (max-width: 1199px) { + section.dashboard-counts div[class*='col-']:nth-of-type(2) .item { + border-right: none; + } +} + +@media (max-width: 575px) { + section.dashboard-counts div[class*='col-'] .item { + border-right: none; + } +} + +/* +* ========================================================== +* DASHBOARD HEADER SECTION +* ========================================================== +*/ +.statistic { + padding: 20px 15px; + margin-bottom: 15px; +} + +.statistic:last-of-type { + margin-bottom: 0; +} + +.statistic strong { + font-size: 1.5em; + color: #333; + font-weight: 700; + line-height: 1; +} + +.statistic small { + color: #aaa; + text-transform: uppercase; +} + +.statistic .icon { + width: 40px; + height: 40px; + line-height: 40px; + text-align: center; + min-width: 40px; + max-width: 40px; + color: #fff; + border-radius: 50%; + margin-right: 15px; +} + +.chart .title { + padding: 15px 0 0 15px; +} + +.chart .title strong { + font-weight: 700; + font-size: 1.2em; +} + +.chart .title small { + color: #aaa; + text-transform: uppercase; +} + +.chart .line-chart { + width: 100%; + height: 100%; + padding: 20px 0; +} + +.chart .line-chart canvas { + width: calc(100% - 30px) !important; +} + +.chart .bar-chart { + margin-bottom: 15px; +} + +.chart .bar-chart canvas { + padding: 15px; + width: 100%; + margin: 0; +} + +/* DASHBOARD HEADER MEDIAQUERIES ------------------------*/ +@media (max-width: 991px) { + section.dashboard-header div[class*='col-'] { + margin-bottom: 20px; + } +} + +/* +* ========================================================== +* PROJECTS SECTION +* ========================================================== +*/ +.project .row { + margin: 0; + padding: 15px 0; + margin-bottom: 15px; +} + +.project div[class*='col-'] { + border-right: 1px solid #eee; +} + +.project .text h3 { + margin-bottom: 0; + color: #555; +} + +.project .text small { + color: #aaa; + font-size: 0.75em; +} + +.project .project-date span { + font-size: 0.9em; + color: #999; +} + +.project .image { + max-width: 50px; + min-width: 50px; + height: 50px; + margin-right: 15px; +} + +.project .time, +.project .comments, +.project .project-progress { + color: #999; + font-size: 0.9em; + margin-right: 20px; +} + +.project .time i, +.project .comments i, +.project .project-progress i { + margin-right: 5px; +} + +.project .project-progress { + width: 200px; +} + +.project .project-progress .progress { + height: 4px; +} + +.project .card { + margin-bottom: 0; +} + +/* PROJECTS SECTION ------------------------------------- */ +@media (max-width: 991px) { + .project .right-col { + margin-top: 20px; + margin-left: 65px; + } + .project .project-progress { + width: 150px; + } +} + +@media (max-width: 480px) { + .project .project-progress { + display: none; + } +} + +/* +* ========================================================== +* CLIENT SECTION +* ========================================================== +*/ +/*====== Work Amount Box ======*/ +.work-amount .chart { + margin: 40px auto; + position: relative; +} + +.work-amount .chart .text { + display: inline-block; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.work-amount .chart strong { + font-size: 1.5rem; +} + +.work-amount .chart span { + color: #999; + font-weight: 300; +} + +.work-amount li span { + font-size: 0.85em; + margin-bottom: 10px; + color: #777; + display: block; +} + +.work-amount li span::before { + content: ''; + display: inline-block; + margin-right: 10px; + width: 7px; + height: 7px; + line-height: 7px; + background: #85b4f2; + border-radius: 50%; +} + +/*====== Client Profile Box ======*/ +.client .client-avatar { + width: 100px; + height: 100px; + margin: 0 auto; + position: relative; +} + +.client .client-avatar .status { + content: ''; + display: block; + width: 18px; + height: 18px; + border: 3px solid #fff; + border-radius: 50%; + position: absolute; + right: 4px; + bottom: 4px; +} + +.client .client-title { + margin-top: 20px; +} + +.client .client-title h3 { + font-weight: 500; + color: #555; +} + +.client .client-title span { + font-size: 0.9em; + color: #aaa; + display: block; +} + +.client .client-title a { + padding: 2px 30px; + border-radius: 40px; + background: #54e69d; + color: #fff; + margin-top: 5px; + font-size: 0.9em; + text-decoration: none; +} + +.client .client-title a:hover { + background: #85b4f2; +} + +.client .client-info { + margin-top: 20px; +} + +.client .client-info strong { + font-weight: 700; +} + +.client .client-social { + margin-top: 20px; +} + +.client .client-social a { + color: #aaa; +} + +/*====== Total Overdue Box ======*/ +.overdue .chart canvas { + width: 100% !important; + z-index: 1; +} + +.overdue .card { + margin-bottom: 0; +} + +.overdue .card-body { + padding: 20px; +} + +.overdue .card-body small { + font-weight: 300; + color: #aaa; +} + +.overdue .card-body h3 { + margin-bottom: 5px; +} + +.overdue .number { + font-size: 1.8em; + font-weight: 400; + color: #555; + margin: 35px 0; +} + +/* +* ========================================================== +* FEEDS SECTION +* ========================================================== +*/ +/*====== Checklist Box ======*/ +.checklist label { + font-size: 0.8em; + color: #999; + line-height: 1.8em; + margin-bottom: 0; +} + +.checklist .item { + padding: 20px; +} + +.checklist .item:nth-of-type(even) { + background: #fafafa; +} + +/*====== Trending Articles Box ======*/ +.articles a { + text-decoration: none !important; + display: block; + margin-bottom: 0; + color: #555; +} + +.articles .badge { + font-size: 0.7em; + padding: 5px 10px; + line-height: 1; + margin-left: 10px; +} + +.articles .item { + padding: 20px; +} + +.articles .item:nth-of-type(even) { + background: #fafafa; +} + +.articles .item .image { + min-width: 50px; + max-width: 50px; + height: 50px; + margin-right: 15px; +} + +.articles .item img { + padding: 3px; + border: 1px solid #28a745; +} + +.articles .item h3 { + color: #555; + font-weight: 400; + margin-bottom: 0; +} + +.articles .item small { + color: #aaa; + font-size: 0.75em; +} + +/* +* ========================================================== +* UPDATES SECTION +* ========================================================== +*/ +/*====== Recent Updates Box ======*/ +.recent-updates .item { + padding: 20px; +} + +.recent-updates .item:nth-of-type(even) { + background: #fafafa; +} + +.recent-updates .icon { + margin-right: 10px; +} + +.recent-updates h5 { + margin-bottom: 5px; + color: #333; + font-weight: 400; +} + +.recent-updates p { + font-size: 0.8em; + color: #aaa; + margin-bottom: 0; +} + +.recent-updates .date { + font-size: 0.9em; + color: #adadad; +} + +.recent-updates .date strong { + font-size: 1.4em; + line-height: 0.8em; + display: block; +} + +.recent-updates .date span { + font-size: 0.9em; + font-weight: 300; +} + +/*====== Daily Feeds Box ======*/ +.daily-feeds .item { + padding: 20px; + border-bottom: 1px solid #eee; +} + +.daily-feeds .feed-profile { + max-width: 50px; + min-width: 50px; + margin-right: 10px; +} + +.daily-feeds h5 { + margin-bottom: 0; + color: #555; +} + +.daily-feeds span { + font-size: 0.8em; + color: #999; +} + +.daily-feeds .full-date { + font-size: 0.85em; + color: #aaa; + font-weight: 300; +} + +.daily-feeds .CTAs { + margin-top: 5px; +} + +.daily-feeds .CTAs a { + font-size: 0.7em; + padding: 3px 8px; + margin-right: 5px; +} + +.daily-feeds .CTAs a i { + margin-right: 5px; +} + +.daily-feeds .quote { + background: #fafafa; + margin-top: 5px; + border-radius: 0; + padding: 15px; + margin-left: 60px; +} + +.daily-feeds .quote small { + font-size: 0.75em; + color: #777; +} + +.daily-feeds .date { + font-size: 0.9em; + color: #aaa; +} + +/*====== Recent Activities Box ======*/ +.recent-activities .item { + padding: 0 15px; + border-bottom: 1px solid #eee; +} + +.recent-activities .item div[class*='col-'] { + padding: 15px; +} + +.recent-activities h5 { + font-weight: 400; + color: #333; +} + +.recent-activities p { + font-size: 0.75em; + color: #999; +} + +.recent-activities .icon { + width: 35px; + height: 35px; + line-height: 35px; + background: #f5f5f5; + text-align: center; + display: inline-block; +} + +.recent-activities .date { + font-size: 0.75em; + color: #999; + padding: 10px; +} + +.recent-activities .date-holder { + padding: 0 !important; + border-right: 1px solid #eee; +} + +/* +* ========================================================== +* FOOTER +* ========================================================== +*/ +footer.main-footer { + position: absolute; + bottom: 0; + width: 100%; + background: #2f333e; + color: #fff; + padding: 20px 10px; +} + +footer.main-footer a { + color: inherit; +} + +footer.main-footer p { + margin: 0; + font-size: 0.8em; +} + +/* FOOTER MEDIAQUERIES --------------------------------- */ +@media (max-width: 575px) { + footer.main-footer div[class*='col'] { + text-align: center !important; + } +} + +section.charts div[class*="col-"] { + margin-bottom: 30px; +} + +section.charts header { + padding: 30px 0; +} + +section.charts div[class*="col-"] { + margin-bottom: 0; +} + +/* +* ========================================================== +* LINE CHART +* ========================================================== +*/ +.line-chart-example .card-block { + padding: 20px; +} + +/* +* ========================================================== +* POLAR CHART +* ========================================================== +*/ +/* +* ========================================================== +* POLAR CHART +* ========================================================== +*/ +.polar-chart-example canvas { + max-width: 350px; + max-height: 350px; + margin: 20px auto; +} + +/* +* ========================================================== +* PIE CHART +* ========================================================== +*/ +.pie-chart-example canvas { + max-width: 350px; + max-height: 350px; + margin: 15px auto; +} + +/* +* ========================================================== +* RADAR CHART +* ========================================================== +*/ +.radar-chart-example canvas { + max-width: 350px; + max-height: 350px; + margin: 20px auto; +} + +/* FORMS MEDIAQUERIES -------------------------- */ +@media (max-width: 991px) { + section.charts h2 { + font-size: 1rem; + } +} + +/* +* ========================================================== +* FORMS +* ========================================================== +*/ +.form-control { + height: calc(2.25rem + 2px); + border: 1px solid #dee2e6; + border-radius: 0; + padding: 0.375rem 0.75rem; +} + +.form-control::-moz-placeholder { + font-size: 0.9em; + font-weight: 300; + font-family: "Poppins", sans-serif; + color: #aaa; +} + +.form-control::-webkit-input-placeholder { + font-size: 0.9em; + font-weight: 300; + font-family: "Poppins", sans-serif; + color: #aaa; +} + +.form-control:-ms-input-placeholder { + font-size: 0.9em; + font-weight: 300; + font-family: "Poppins", sans-serif; + color: #aaa; +} + +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #796AEE; + outline: none; + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25); +} + +.form-control-sm { + height: calc(1.8125rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.form-control-lg { + height: calc(2.875rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; +} + +textarea.form-control { + height: auto; +} + +select.form-control[size], select.form-control[multiple] { + height: auto; +} + +select.form-control option { + color: #999; +} + +.input-group .dropdown-menu { + padding: 15px; + color: #777; + border-radius: 0; +} + +.input-group .dropdown-menu a { + padding: 5px 0; + color: inherit; + text-decoration: none; +} + +.input-group .dropdown-menu a:hover { + color: #796AEE; + background: none; +} + +.input-group-text { + color: #868e96; + background: #f8f9fa; + border-color: #dee2e6; + border-radius: 0; +} + +.input-group-text .checkbox-template, .input-group-text .radio-template { + -webkit-transform: none; + transform: none; +} + +.form-control-label { + font-size: .9rem; + color: #777; +} + +button, input[type='submit'] { + cursor: pointer; + font-family: inherit; + font-weight: 300 !important; +} + +.input-group .dropdown-toggle { + background: #f5f5f5; + color: #777; +} + +.checkbox-template, .radio-template { + -webkit-transform: translateY(3px); + transform: translateY(3px); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + position: relative; +} + +.checkbox-template + label, .radio-template + label { + margin-left: 10px; +} + +.checkbox-template::before, .radio-template::before { + margin-right: 10px; + content: ''; + display: inline-block; + -webkit-transform: translate(-2px, -2px); + transform: translate(-2px, -2px); + width: 18px; + height: 18px; + line-height: 18px; + text-align: center; + background: #dae2e7; + -webkit-transition: all 0.2s; + transition: all 0.2s; +} + +.checkbox-template::after, .radio-template::after { + content: '\f00c'; + width: 12px; + height: 12px; + line-height: 12px; + text-align: center; + display: block; + font-family: 'FontAwesome'; + position: absolute; + top: 1px; + left: 1px; + font-size: 0.7em; + opacity: 0; + -webkit-transition: all 0.2s; + transition: all 0.2s; + color: #fff; +} + +.checkbox-template:checked::before, .radio-template:checked::before { + background: #796AEE; +} + +.checkbox-template:checked::after, .radio-template:checked::after { + opacity: 1; +} + +.radio-template::before { + border-radius: 50%; + -webkit-transform: translate(-3px, -3px); + transform: translate(-3px, -3px); +} + +.radio-template::after { + width: 6px; + height: 6px; + line-height: 6px; + text-align: center; + position: absolute; + top: 3px; + left: 3px; + border-radius: 50%; + content: ''; +} + +.radio-template:checked::after { + background: #fff; +} + +input.input-material { + width: 100%; + border: none; + border-bottom: 1px solid #eee; + padding: 10px 0; +} + +input.input-material.is-invalid { + border-color: #dc3545 !important; +} + +input.input-material:focus { + border-color: #796AEE; +} + +input.input-material ~ label { + color: #aaa; + position: absolute; + top: 14px; + left: 0; + cursor: text; + -webkit-transition: all 0.2s; + transition: all 0.2s; + font-weight: 300; +} + +input.input-material ~ label.active { + font-size: 0.8rem; + top: -10px; + color: #796AEE; +} + +input.input-material.is-invalid ~ label { + color: #dc3545; +} + +.form-group-material { + position: relative; + margin-bottom: 30px; +} + +.modal-content { + border-radius: 0; +} + +.i-checks { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +/* +* ========================================================== +* FORM PAGE +* ========================================================== +*/ +.forms p { + font-size: 0.9em; + color: #555; +} + +.forms form small { + font-size: 0.8em; + color: #999; + font-weight: 300; +} + +.forms .line { + width: 100%; + height: 1px; + border-bottom: 1px dashed #eee; + margin: 30px 0; +} + +/* + +===================== +STYLE SWITCHER FOR DEMO +===================== + +*/ +#style-switch-button { + position: fixed; + top: 120px; + right: 0px; + border-radius: 0; + z-index: 12; +} + +#style-switch { + width: 300px; + padding: 20px; + position: fixed; + top: 160px; + right: 0; + background: #fff; + border: solid 1px #ced4da; + z-index: 12; +} + +#style-switch h4 { + color: #495057; +} + +/* ========================================= + THEMING OF BOOTSTRAP COMPONENTS + ========================================= */ +/* + * 1. NAVBAR + */ +.navbar { + padding: 0.5rem 1rem; +} + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; +} + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, 0.7); +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-dark .navbar-brand { + color: #fff; +} + +.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover { + color: #fff; +} + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); +} + +.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover { + color: rgba(255, 255, 255, 0.75); +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; +} + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); +} + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); +} + +/* + * 2. BUTTONS + */ +.btn { + font-weight: 400; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} + +.btn:focus, .btn.focus { + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.25); +} + +.btn.disabled, .btn:disabled { + opacity: .65; +} + +.btn:not([disabled]):not(.disabled):active, .btn:not([disabled]):not(.disabled).active { + background-image: none; +} + +.btn-primary { + color: color-yiq(#796AEE); + background-color: #796AEE; + border-color: #796AEE; +} + +.btn-primary:hover { + color: color-yiq(#5a48ea); + background-color: #5a48ea; + border-color: #503ce9; +} + +.btn-primary:focus, .btn-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); +} + +.btn-primary.disabled, .btn-primary:disabled { + color: color-yiq(#796AEE); + background-color: #796AEE; + border-color: #796AEE; +} + +.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, +.show > .btn-primary.dropdown-toggle { + color: color-yiq(#503ce9); + background-color: #503ce9; + border-color: #4631e7; +} + +.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, +.show > .btn-primary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); +} + +.btn-secondary { + color: color-yiq(#868e96); + background-color: #868e96; + border-color: #868e96; +} + +.btn-secondary:hover { + color: color-yiq(#727b84); + background-color: #727b84; + border-color: #6c757d; +} + +.btn-secondary:focus, .btn-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + color: color-yiq(#868e96); + background-color: #868e96; + border-color: #868e96; +} + +.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, +.show > .btn-secondary.dropdown-toggle { + color: color-yiq(#6c757d); + background-color: #6c757d; + border-color: #666e76; +} + +.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, +.show > .btn-secondary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-success { + color: color-yiq(#28a745); + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:hover { + color: color-yiq(#218838); + background-color: #218838; + border-color: #1e7e34; +} + +.btn-success:focus, .btn-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-success.disabled, .btn-success:disabled { + color: color-yiq(#28a745); + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, +.show > .btn-success.dropdown-toggle { + color: color-yiq(#1e7e34); + background-color: #1e7e34; + border-color: #1c7430; +} + +.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, +.show > .btn-success.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-info { + color: color-yiq(#17a2b8); + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:hover { + color: color-yiq(#138496); + background-color: #138496; + border-color: #117a8b; +} + +.btn-info:focus, .btn-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-info.disabled, .btn-info:disabled { + color: color-yiq(#17a2b8); + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, +.show > .btn-info.dropdown-toggle { + color: color-yiq(#117a8b); + background-color: #117a8b; + border-color: #10707f; +} + +.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, +.show > .btn-info.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-warning { + color: color-yiq(#ffc107); + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:hover { + color: color-yiq(#e0a800); + background-color: #e0a800; + border-color: #d39e00; +} + +.btn-warning:focus, .btn-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-warning.disabled, .btn-warning:disabled { + color: color-yiq(#ffc107); + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, +.show > .btn-warning.dropdown-toggle { + color: color-yiq(#d39e00); + background-color: #d39e00; + border-color: #c69500; +} + +.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, +.show > .btn-warning.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-danger { + color: color-yiq(#dc3545); + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:hover { + color: color-yiq(#c82333); + background-color: #c82333; + border-color: #bd2130; +} + +.btn-danger:focus, .btn-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-danger.disabled, .btn-danger:disabled { + color: color-yiq(#dc3545); + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, +.show > .btn-danger.dropdown-toggle { + color: color-yiq(#bd2130); + background-color: #bd2130; + border-color: #b21f2d; +} + +.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, +.show > .btn-danger.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-light { + color: color-yiq(#f8f9fa); + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:hover { + color: color-yiq(#e2e6ea); + background-color: #e2e6ea; + border-color: #dae0e5; +} + +.btn-light:focus, .btn-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-light.disabled, .btn-light:disabled { + color: color-yiq(#f8f9fa); + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, +.show > .btn-light.dropdown-toggle { + color: color-yiq(#dae0e5); + background-color: #dae0e5; + border-color: #d3d9df; +} + +.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, +.show > .btn-light.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-dark { + color: color-yiq(#343a40); + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:hover { + color: color-yiq(#23272b); + background-color: #23272b; + border-color: #1d2124; +} + +.btn-dark:focus, .btn-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-dark.disabled, .btn-dark:disabled { + color: color-yiq(#343a40); + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, +.show > .btn-dark.dropdown-toggle { + color: color-yiq(#1d2124); + background-color: #1d2124; + border-color: #171a1d; +} + +.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, +.show > .btn-dark.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-default { + color: color-yiq(#ced4da); + background-color: #ced4da; + border-color: #ced4da; +} + +.btn-default:hover { + color: color-yiq(#b8c1ca); + background-color: #b8c1ca; + border-color: #b1bbc4; +} + +.btn-default:focus, .btn-default.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); + box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); +} + +.btn-default.disabled, .btn-default:disabled { + color: color-yiq(#ced4da); + background-color: #ced4da; + border-color: #ced4da; +} + +.btn-default:not(:disabled):not(.disabled):active, .btn-default:not(:disabled):not(.disabled).active, +.show > .btn-default.dropdown-toggle { + color: color-yiq(#b1bbc4); + background-color: #b1bbc4; + border-color: #aab4bf; +} + +.btn-default:not(:disabled):not(.disabled):active:focus, .btn-default:not(:disabled):not(.disabled).active:focus, +.show > .btn-default.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); + box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); +} + +.btn-outline-primary { + color: #796AEE; + background-color: transparent; + background-image: none; + border-color: #796AEE; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #796AEE; + border-color: #796AEE; +} + +.btn-outline-primary:focus, .btn-outline-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #796AEE; + background-color: transparent; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, +.show > .btn-outline-primary.dropdown-toggle { + color: color-yiq(#796AEE); + background-color: #796AEE; + border-color: #796AEE; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-primary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); +} + +.btn-outline-secondary { + color: #868e96; + background-color: transparent; + background-image: none; + border-color: #868e96; +} + +.btn-outline-secondary:hover { + color: #fff; + background-color: #868e96; + border-color: #868e96; +} + +.btn-outline-secondary:focus, .btn-outline-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #868e96; + background-color: transparent; +} + +.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, +.show > .btn-outline-secondary.dropdown-toggle { + color: color-yiq(#868e96); + background-color: #868e96; + border-color: #868e96; +} + +.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-secondary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); + box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); +} + +.btn-outline-success { + color: #28a745; + background-color: transparent; + background-image: none; + border-color: #28a745; +} + +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:focus, .btn-outline-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent; +} + +.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, +.show > .btn-outline-success.dropdown-toggle { + color: color-yiq(#28a745); + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-success.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-info { + color: #17a2b8; + background-color: transparent; + background-image: none; + border-color: #17a2b8; +} + +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:focus, .btn-outline-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; +} + +.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, +.show > .btn-outline-info.dropdown-toggle { + color: color-yiq(#17a2b8); + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-info.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-warning { + color: #ffc107; + background-color: transparent; + background-image: none; + border-color: #ffc107; +} + +.btn-outline-warning:hover { + color: #fff; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:focus, .btn-outline-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent; +} + +.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, +.show > .btn-outline-warning.dropdown-toggle { + color: color-yiq(#ffc107); + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-warning.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-danger { + color: #dc3545; + background-color: transparent; + background-image: none; + border-color: #dc3545; +} + +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:focus, .btn-outline-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; +} + +.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, +.show > .btn-outline-danger.dropdown-toggle { + color: color-yiq(#dc3545); + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-danger.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-light { + color: #f8f9fa; + background-color: transparent; + background-image: none; + border-color: #f8f9fa; +} + +.btn-outline-light:hover { + color: #fff; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:focus, .btn-outline-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} + +.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, +.show > .btn-outline-light.dropdown-toggle { + color: color-yiq(#f8f9fa); + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-light.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-dark { + color: #343a40; + background-color: transparent; + background-image: none; + border-color: #343a40; +} + +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:focus, .btn-outline-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} + +.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, +.show > .btn-outline-dark.dropdown-toggle { + color: color-yiq(#343a40); + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-dark.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-lg { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +/* + * 3. TYPE + */ +body { + font-family: "Poppins", sans-serif; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + background-color: #fff; +} + +a { + color: #796AEE; + text-decoration: none; +} + +a:focus, a:hover { + color: #3b25e6; + text-decoration: underline; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 700; + line-height: 1.1; + color: inherit; +} + +h1, +.h1 { + font-size: 1.5rem; +} + +h2, +.h2 { + font-size: 1.3rem; +} + +h3, +.h3 { + font-size: 1.2rem; +} + +h4, +.h4 { + font-size: 1.1rem; +} + +h5, +.h5 { + font-size: 1rem; +} + +h6, +.h6 { + font-size: 0.95rem; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.1; +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.1; +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.1; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.1; +} + +hr { + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +small, +.small { + font-size: 80%; + font-weight: 400; +} + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} + +.blockquote { + padding: 0.5rem 1rem; + margin-bottom: 2rem; + font-size: 1.25rem; + border-left: 5px solid #796AEE; +} + +.blockquote-footer { + color: #868e96; +} + +.blockquote-footer::before { + content: "\2014 \00A0"; +} + +.text-primary { + color: #796AEE !important; +} + +a.text-primary:focus, a.text-primary:hover { + color: #503ce9 !important; +} + +/* + * 4. PAGINATION + */ +.page-item:first-child .page-link { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.page-item.active .page-link { + color: #fff; + background-color: #796AEE; + border-color: #796AEE; +} + +.page-item.disabled .page-link { + color: #868e96; + background-color: #fff; + border-color: #ddd; +} + +.page-link { + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #796AEE; + background-color: #fff; + border: 1px solid #ddd; +} + +.page-link:hover { + z-index: 2; + color: #3b25e6; + background-color: #e9ecef; + border-color: #ddd; +} + +.page-link:focus { + z-index: 2; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; + text-decoration: none; +} + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} + +/* +* 5. UTILITIES +*/ +.bg-primary { + background-color: #796AEE !important; +} + +a.bg-primary:focus, a.bg-primary:hover { + background-color: #503ce9 !important; +} + +.bg-secondary { + background-color: #868e96 !important; +} + +a.bg-secondary:focus, a.bg-secondary:hover { + background-color: #6c757d !important; +} + +.bg-success { + background-color: #28a745 !important; +} + +a.bg-success:focus, a.bg-success:hover { + background-color: #1e7e34 !important; +} + +.bg-info { + background-color: #17a2b8 !important; +} + +a.bg-info:focus, a.bg-info:hover { + background-color: #117a8b !important; +} + +.bg-warning { + background-color: #ffc107 !important; +} + +a.bg-warning:focus, a.bg-warning:hover { + background-color: #d39e00 !important; +} + +.bg-danger { + background-color: #dc3545 !important; +} + +a.bg-danger:focus, a.bg-danger:hover { + background-color: #bd2130 !important; +} + +.bg-light { + background-color: #f8f9fa !important; +} + +a.bg-light:focus, a.bg-light:hover { + background-color: #dae0e5 !important; +} + +.bg-dark { + background-color: #343a40 !important; +} + +a.bg-dark:focus, a.bg-dark:hover { + background-color: #1d2124 !important; +} + +.border-primary { + border-color: #796AEE !important; +} + +.border-secondary { + border-color: #868e96 !important; +} + +.border-success { + border-color: #28a745 !important; +} + +.border-info { + border-color: #17a2b8 !important; +} + +.border-warning { + border-color: #ffc107 !important; +} + +.border-danger { + border-color: #dc3545 !important; +} + +.border-light { + border-color: #f8f9fa !important; +} + +.border-dark { + border-color: #343a40 !important; +} + +.text-primary { + color: #796AEE !important; +} + +a.text-primary:focus, a.text-primary:hover { + color: #503ce9 !important; +} + +.text-secondary { + color: #868e96 !important; +} + +a.text-secondary:focus, a.text-secondary:hover { + color: #6c757d !important; +} + +.text-success { + color: #28a745 !important; +} + +a.text-success:focus, a.text-success:hover { + color: #1e7e34 !important; +} + +.text-info { + color: #17a2b8 !important; +} + +a.text-info:focus, a.text-info:hover { + color: #117a8b !important; +} + +.text-warning { + color: #ffc107 !important; +} + +a.text-warning:focus, a.text-warning:hover { + color: #d39e00 !important; +} + +.text-danger { + color: #dc3545 !important; +} + +a.text-danger:focus, a.text-danger:hover { + color: #bd2130 !important; +} + +.text-light { + color: #f8f9fa !important; +} + +a.text-light:focus, a.text-light:hover { + color: #dae0e5 !important; +} + +.text-dark { + color: #343a40 !important; +} + +a.text-dark:focus, a.text-dark:hover { + color: #1d2124 !important; +} + +.badge-primary { + color: color-yiq(#796AEE); + background-color: #796AEE; +} + +.badge-primary[href]:focus, .badge-primary[href]:hover { + color: color-yiq(#796AEE); + text-decoration: none; + background-color: #503ce9; +} + +.badge-secondary { + color: color-yiq(#868e96); + background-color: #868e96; +} + +.badge-secondary[href]:focus, .badge-secondary[href]:hover { + color: color-yiq(#868e96); + text-decoration: none; + background-color: #6c757d; +} + +.badge-success { + color: color-yiq(#28a745); + background-color: #28a745; +} + +.badge-success[href]:focus, .badge-success[href]:hover { + color: color-yiq(#28a745); + text-decoration: none; + background-color: #1e7e34; +} + +.badge-info { + color: color-yiq(#17a2b8); + background-color: #17a2b8; +} + +.badge-info[href]:focus, .badge-info[href]:hover { + color: color-yiq(#17a2b8); + text-decoration: none; + background-color: #117a8b; +} + +.badge-warning { + color: color-yiq(#ffc107); + background-color: #ffc107; +} + +.badge-warning[href]:focus, .badge-warning[href]:hover { + color: color-yiq(#ffc107); + text-decoration: none; + background-color: #d39e00; +} + +.badge-danger { + color: color-yiq(#dc3545); + background-color: #dc3545; +} + +.badge-danger[href]:focus, .badge-danger[href]:hover { + color: color-yiq(#dc3545); + text-decoration: none; + background-color: #bd2130; +} + +.badge-light { + color: color-yiq(#f8f9fa); + background-color: #f8f9fa; +} + +.badge-light[href]:focus, .badge-light[href]:hover { + color: color-yiq(#f8f9fa); + text-decoration: none; + background-color: #dae0e5; +} + +.badge-dark { + color: color-yiq(#343a40); + background-color: #343a40; +} + +.badge-dark[href]:focus, .badge-dark[href]:hover { + color: color-yiq(#343a40); + text-decoration: none; + background-color: #1d2124; +} + +/* +* 6.CODE +*/ +code { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #bd4147; + background-color: #f8f9fa; + border-radius: 0.25rem; +} + +a > code { + padding: 0; + color: inherit; + background-color: inherit; +} + +/* +* 7. NAV +*/ +.nav-link { + padding: 0.5rem 1rem; +} + +.nav-link.disabled { + color: #868e96; +} + +.nav-tabs .nav-item { + margin-bottom: -1px; +} + +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { + border-color: #e9ecef #e9ecef #dee2e6; +} + +.nav-tabs .nav-link.disabled { + color: #868e96; +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; +} + +.nav-pills .nav-link { + border-radius: 0.25rem; +} + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #796AEE; +} + +/* +* 8. CARD +*/ +.card { + background-color: #fff; + border: 0 solid #eee; + border-radius: 0; +} + +.card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.card-body { + padding: 1.25rem; +} + +.card-title { + margin-bottom: 1rem; +} + +.card-subtitle { + margin-top: -0.5rem; +} + +.card-link + .card-link { + margin-left: 1.25rem; +} + +.card-header { + padding: 1rem 1.25rem; + background-color: #fff; + border-bottom: 1px solid #eee; +} + +.card-header:first-child { + border-radius: 0 0 0 0; +} + +.card-header-transparent { + background-color: rgba(0, 0, 0, 0.3); + border-bottom: none; +} + +.card-footer { + padding: 1rem 1.25rem; + background-color: #f8f9fa; + border-top: 1px solid #eee; +} + +.card-footer:last-child { + border-radius: 0 0 0 0; +} + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -1rem; + margin-left: -0.625rem; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} + +.card-img-overlay { + padding: 1.25rem; +} + +.card-img-overlay-opacity { + background: rgba(0, 0, 0, 0.2); +} + +.card-img { + border-radius: 0; +} + +.card-img-top { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.card-img-bottom { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.card-deck .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-deck { + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + margin-right: 15px; + margin-left: 15px; + } +} + +.custom-select { + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid #ced4da; + border-radius: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.custom-select:focus { + border-color: #796AEE; + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); + box-shadow: 0 0 0 0.2rem rgba(121, 106, 238, 0.5); +} + +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; +} + +.custom-select:disabled { + color: #868e96; + background-color: #e9ecef; +} + +.custom-select::-ms-expand { + opacity: 0; +} + +.custom-select-sm { + height: calc(1.8125rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; +} + +.custom-select-lg { + height: calc(2.875rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 125%; +} \ No newline at end of file diff --git a/src/main/webapp/static/css/login.css b/src/main/webapp/static/css/login.css new file mode 100644 index 0000000..f520ab5 --- /dev/null +++ b/src/main/webapp/static/css/login.css @@ -0,0 +1,120 @@ +/* +* ========================================================== +* LOGIN PAGE +* ========================================================== +*/ +.login-page { + position: relative; +} + +.login-page::before { + content: ''; + width: 100%; + height: 100%; + display: block; + z-index: -1; + background: url(../imgs/login.jpg); + background-size: cover; + -webkit-filter: blur(10px); + filter: blur(10px); + z-index: 1; + position: absolute; + top: 0; + right: 0; +} + +.login-page .container { + min-height: 100vh; + z-index: 999; + padding: 20px; + position: relative; +} + +.login-page .form-holder { + width: 100%; + border-radius: 5px; + overflow: hidden; + margin-bottom: 50px; +} + +.login-page .form-holder .info, +.login-page .form-holder .form { + min-height: 70vh; + padding: 40px; + height: 100%; +} + +.login-page .form-holder div[class*='col-'] { + padding: 0; +} + +.login-page .form-holder .info { + background: rgba(121, 106, 238, 0.9); + color: #fff; +} + +.login-page .form-holder .info h1 { + font-size: 2.5em; + font-weight: 600; +} + +.login-page .form-holder .info p { + font-weight: 300; +} + +.login-page .form-holder .form .form-group { + position: relative; + margin-bottom: 30px; +} + +.login-page .form-holder .form .content { + width: 100%; +} + +.login-page .form-holder .form form { + width: 100%; + max-width: 400px; +} + +.login-page .form-holder .form #login, +.login-page .form-holder .form #register { + margin-bottom: 20px; + cursor: pointer; +} + +.login-page .form-holder .form a.forgot-pass, +.login-page .form-holder .form a.signup { + font-size: 0.9em; + color: #85b4f2; +} + +.login-page .form-holder .form small { + color: #aaa; +} + +.login-page .form-holder .form .terms-conditions label { + cursor: pointer; + color: #aaa; + font-size: 0.9em; +} + +.login-page .copyrights { + width: 100%; + z-index: 9999; + position: absolute; + bottom: 0; + left: 0; + color: #fff; +} + +@media (max-width: 991px) { + .login-page .info, + .login-page .form { + min-height: auto !important; + } + + .login-page .info { + padding-top: 100px !important; + padding-bottom: 100px !important; + } +} diff --git a/src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css b/src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css new file mode 100644 index 0000000..78485fe --- /dev/null +++ b/src/main/webapp/static/css/util/bootstrap-datetimepicker.min.css @@ -0,0 +1,9 @@ +/*! + * Datetimepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */.datetimepicker{padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datetimepicker-inline{width:220px}.datetimepicker.datetimepicker-rtl{direction:rtl}.datetimepicker.datetimepicker-rtl table tr td span{float:right}.datetimepicker-dropdown,.datetimepicker-dropdown-left{top:0;left:0}[class*=" datetimepicker-dropdown"]:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);position:absolute}[class*=" datetimepicker-dropdown"]:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute}[class*=" datetimepicker-dropdown-top"]:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);border-bottom:0}[class*=" datetimepicker-dropdown-top"]:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;border-bottom:0}.datetimepicker-dropdown-bottom-left:before{top:-7px;right:6px}.datetimepicker-dropdown-bottom-left:after{top:-6px;right:7px}.datetimepicker-dropdown-bottom-right:before{top:-7px;left:6px}.datetimepicker-dropdown-bottom-right:after{top:-6px;left:7px}.datetimepicker-dropdown-top-left:before{bottom:-7px;right:6px}.datetimepicker-dropdown-top-left:after{bottom:-6px;right:7px}.datetimepicker-dropdown-top-right:before{bottom:-7px;left:6px}.datetimepicker-dropdown-top-right:after{bottom:-6px;left:7px}.datetimepicker>div{display:none}.datetimepicker.minutes div.datetimepicker-minutes{display:block}.datetimepicker.hours div.datetimepicker-hours{display:block}.datetimepicker.days div.datetimepicker-days{display:block}.datetimepicker.months div.datetimepicker-months{display:block}.datetimepicker.years div.datetimepicker-years{display:block}.datetimepicker table{margin:0}.datetimepicker td,.datetimepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0}.table-striped .datetimepicker table tr td,.table-striped .datetimepicker table tr th{background-color:transparent}.datetimepicker table tr td.minute:hover{background:#eee;cursor:pointer}.datetimepicker table tr td.hour:hover{background:#eee;cursor:pointer}.datetimepicker table tr td.day:hover{background:#eee;cursor:pointer}.datetimepicker table tr td.old,.datetimepicker table tr td.new{color:#999}.datetimepicker table tr td.disabled,.datetimepicker table tr td.disabled:hover{background:0;color:#999;cursor:default}.datetimepicker table tr td.today,.datetimepicker table tr td.today:hover,.datetimepicker table tr td.today.disabled,.datetimepicker table tr td.today.disabled:hover{background-color:#fde19a;background-image:-moz-linear-gradient(top,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(top,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(top,#fdd49a,#fdf59a);background-image:-o-linear-gradient(top,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a',endColorstr='#fdf59a',GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.datetimepicker table tr td.today:hover,.datetimepicker table tr td.today:hover:hover,.datetimepicker table tr td.today.disabled:hover,.datetimepicker table tr td.today.disabled:hover:hover,.datetimepicker table tr td.today:active,.datetimepicker table tr td.today:hover:active,.datetimepicker table tr td.today.disabled:active,.datetimepicker table tr td.today.disabled:hover:active,.datetimepicker table tr td.today.active,.datetimepicker table tr td.today:hover.active,.datetimepicker table tr td.today.disabled.active,.datetimepicker table tr td.today.disabled:hover.active,.datetimepicker table tr td.today.disabled,.datetimepicker table tr td.today:hover.disabled,.datetimepicker table tr td.today.disabled.disabled,.datetimepicker table tr td.today.disabled:hover.disabled,.datetimepicker table tr td.today[disabled],.datetimepicker table tr td.today:hover[disabled],.datetimepicker table tr td.today.disabled[disabled],.datetimepicker table tr td.today.disabled:hover[disabled]{background-color:#fdf59a}.datetimepicker table tr td.today:active,.datetimepicker table tr td.today:hover:active,.datetimepicker table tr td.today.disabled:active,.datetimepicker table tr td.today.disabled:hover:active,.datetimepicker table tr td.today.active,.datetimepicker table tr td.today:hover.active,.datetimepicker table tr td.today.disabled.active,.datetimepicker table tr td.today.disabled:hover.active{background-color:#fbf069}.datetimepicker table tr td.active,.datetimepicker table tr td.active:hover,.datetimepicker table tr td.active.disabled,.datetimepicker table tr td.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-ms-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc',endColorstr='#0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.datetimepicker table tr td.active:hover,.datetimepicker table tr td.active:hover:hover,.datetimepicker table tr td.active.disabled:hover,.datetimepicker table tr td.active.disabled:hover:hover,.datetimepicker table tr td.active:active,.datetimepicker table tr td.active:hover:active,.datetimepicker table tr td.active.disabled:active,.datetimepicker table tr td.active.disabled:hover:active,.datetimepicker table tr td.active.active,.datetimepicker table tr td.active:hover.active,.datetimepicker table tr td.active.disabled.active,.datetimepicker table tr td.active.disabled:hover.active,.datetimepicker table tr td.active.disabled,.datetimepicker table tr td.active:hover.disabled,.datetimepicker table tr td.active.disabled.disabled,.datetimepicker table tr td.active.disabled:hover.disabled,.datetimepicker table tr td.active[disabled],.datetimepicker table tr td.active:hover[disabled],.datetimepicker table tr td.active.disabled[disabled],.datetimepicker table tr td.active.disabled:hover[disabled]{background-color:#04c}.datetimepicker table tr td.active:active,.datetimepicker table tr td.active:hover:active,.datetimepicker table tr td.active.disabled:active,.datetimepicker table tr td.active.disabled:hover:active,.datetimepicker table tr td.active.active,.datetimepicker table tr td.active:hover.active,.datetimepicker table tr td.active.disabled.active,.datetimepicker table tr td.active.disabled:hover.active{background-color:#039}.datetimepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datetimepicker .datetimepicker-hours span{height:26px;line-height:26px}.datetimepicker .datetimepicker-hours table tr td span.hour_am,.datetimepicker .datetimepicker-hours table tr td span.hour_pm{width:14.6%}.datetimepicker .datetimepicker-hours fieldset legend,.datetimepicker .datetimepicker-minutes fieldset legend{margin-bottom:inherit;line-height:30px}.datetimepicker .datetimepicker-minutes span{height:26px;line-height:26px}.datetimepicker table tr td span:hover{background:#eee}.datetimepicker table tr td span.disabled,.datetimepicker table tr td span.disabled:hover{background:0;color:#999;cursor:default}.datetimepicker table tr td span.active,.datetimepicker table tr td span.active:hover,.datetimepicker table tr td span.active.disabled,.datetimepicker table tr td span.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-ms-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc',endColorstr='#0044cc',GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.datetimepicker table tr td span.active:hover,.datetimepicker table tr td span.active:hover:hover,.datetimepicker table tr td span.active.disabled:hover,.datetimepicker table tr td span.active.disabled:hover:hover,.datetimepicker table tr td span.active:active,.datetimepicker table tr td span.active:hover:active,.datetimepicker table tr td span.active.disabled:active,.datetimepicker table tr td span.active.disabled:hover:active,.datetimepicker table tr td span.active.active,.datetimepicker table tr td span.active:hover.active,.datetimepicker table tr td span.active.disabled.active,.datetimepicker table tr td span.active.disabled:hover.active,.datetimepicker table tr td span.active.disabled,.datetimepicker table tr td span.active:hover.disabled,.datetimepicker table tr td span.active.disabled.disabled,.datetimepicker table tr td span.active.disabled:hover.disabled,.datetimepicker table tr td span.active[disabled],.datetimepicker table tr td span.active:hover[disabled],.datetimepicker table tr td span.active.disabled[disabled],.datetimepicker table tr td span.active.disabled:hover[disabled]{background-color:#04c}.datetimepicker table tr td span.active:active,.datetimepicker table tr td span.active:hover:active,.datetimepicker table tr td span.active.disabled:active,.datetimepicker table tr td span.active.disabled:hover:active,.datetimepicker table tr td span.active.active,.datetimepicker table tr td span.active:hover.active,.datetimepicker table tr td span.active.disabled.active,.datetimepicker table tr td span.active.disabled:hover.active{background-color:#039}.datetimepicker table tr td span.old{color:#999}.datetimepicker th.switch{width:145px}.datetimepicker th span.glyphicon{pointer-events:none}.datetimepicker thead tr:first-child th,.datetimepicker tfoot th{cursor:pointer}.datetimepicker thead tr:first-child th:hover,.datetimepicker tfoot th:hover{background:#eee}.input-append.date .add-on i,.input-prepend.date .add-on i,.input-group.date .input-group-addon span{cursor:pointer;width:14px;height:14px} \ No newline at end of file diff --git a/src/main/webapp/static/imgs/default.png b/src/main/webapp/static/imgs/default.png new file mode 100644 index 0000000000000000000000000000000000000000..17410cbdf7a8780dd6d135942c48103fed0c4fd6 GIT binary patch literal 54350 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 B_fh}= literal 0 HcmV?d00001 diff --git a/src/main/webapp/static/imgs/login.jpg b/src/main/webapp/static/imgs/login.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e2ede809c9278511cb40ff367c61900518ef18b GIT binary patch literal 15369 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 literal 0 HcmV?d00001 diff --git a/src/main/webapp/static/js/common-css.js b/src/main/webapp/static/js/common-css.js new file mode 100644 index 0000000..5571fdf --- /dev/null +++ b/src/main/webapp/static/js/common-css.js @@ -0,0 +1,13 @@ +/* 移动端设置 */ +document.write(''); +document.write(''); +document.write(''); +/* 移动端兼容 */ +document.write(''); +/* 搜索引擎优化 */ +document.write(''); +document.write(''); + +document.write(''); +/* 最新版本的 Bootstrap 核心 CSS 文件 */ +document.write(''); diff --git a/src/main/webapp/static/js/common-js.js b/src/main/webapp/static/js/common-js.js new file mode 100644 index 0000000..34cf899 --- /dev/null +++ b/src/main/webapp/static/js/common-js.js @@ -0,0 +1,43 @@ +/* bootstrap */ +document.write(''); +document.write(''); +document.write(''); + +// input浮点数处理 +function clearNoNum(obj){ + obj.value = obj.value.replace(/[^\d.]/g,""); //清除“数字”和“.”以外的字符 + obj.value = obj.value.replace(/\.{2,}/g,"."); //只保留第一个. 清除多余的 + obj.value = obj.value.replace(".","$#$").replace(/\./g,"").replace("$#$","."); + obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d).*$/,'$1$2.$3');//只能输入两个小数 + //如果没有小数点,不能为类似 01、02的金额 + if(obj.value.indexOf(".")< 0 && obj.value !=""){ + obj.value= parseFloat(obj.value); + } + //如果有小数点,不能为类似 1.10的金额 + if(obj.value.indexOf(".")> 0 && obj.value.indexOf("0")>2){ + obj.value= parseFloat(obj.value); + } + //如果有小数点,不能为类似 0.00的金额 + if(obj.value.indexOf(".")> 0 && obj.value.lastIndexOf("0")>1){ + obj.value= parseFloat(obj.value); + } +} +function dateFormat(fmt, date) { + let ret; + const opt = { + "Y+": date.getFullYear().toString(), // 年 + "m+": (date.getMonth() + 1).toString(), // 月 + "d+": date.getDate().toString(), // 日 + "H+": date.getHours().toString(), // 时 + "M+": date.getMinutes().toString(), // 分 + "S+": date.getSeconds().toString() // 秒 + // 有其他格式化字符需求可以继续添加,必须转化成字符串 + }; + for (let k in opt) { + ret = new RegExp("(" + k + ")").exec(fmt); + if (ret) { + fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) + }; + }; + return fmt; +} \ No newline at end of file diff --git a/src/main/webapp/static/js/login.js b/src/main/webapp/static/js/login.js new file mode 100644 index 0000000..835bbd4 --- /dev/null +++ b/src/main/webapp/static/js/login.js @@ -0,0 +1,175 @@ +$(document).ready(function () { + + 'use strict'; + + // ------------------------------------------------------- // + // Search Box + // ------------------------------------------------------ // + $('#search').on('click', function (e) { + e.preventDefault(); + $('.search-box').fadeIn(); + }); + $('.dismiss').on('click', function () { + $('.search-box').fadeOut(); + }); + + // ------------------------------------------------------- // + // Card Close + // ------------------------------------------------------ // + $('.card-close a.remove').on('click', function (e) { + e.preventDefault(); + $(this).parents('.card').fadeOut(); + }); + + // ------------------------------------------------------- // + // Tooltips init + // ------------------------------------------------------ // + + $('[data-toggle="tooltip"]').tooltip() + + + // ------------------------------------------------------- // + // Adding fade effect to dropdowns + // ------------------------------------------------------ // + $('.dropdown').on('show.bs.dropdown', function () { + $(this).find('.dropdown-menu').first().stop(true, true).fadeIn(); + }); + $('.dropdown').on('hide.bs.dropdown', function () { + $(this).find('.dropdown-menu').first().stop(true, true).fadeOut(); + }); + + + // ------------------------------------------------------- // + // Sidebar Functionality + // ------------------------------------------------------ // + $('#toggle-btn').on('click', function (e) { + e.preventDefault(); + $(this).toggleClass('active'); + + $('.side-navbar').toggleClass('shrinked'); + $('.content-inner').toggleClass('active'); + $(document).trigger('sidebarChanged'); + + if ($(window).outerWidth() > 1183) { + if ($('#toggle-btn').hasClass('active')) { + $('.navbar-header .brand-small').hide(); + $('.navbar-header .brand-big').show(); + } else { + $('.navbar-header .brand-small').show(); + $('.navbar-header .brand-big').hide(); + } + } + + if ($(window).outerWidth() < 1183) { + $('.navbar-header .brand-small').show(); + } + }); + + // ------------------------------------------------------- // + // Universal Form Validation + // ------------------------------------------------------ // + + $('.form-validate').each(function() { + $(this).validate({ + errorElement: "div", + errorClass: 'is-invalid', + validClass: 'is-valid', + ignore: ':hidden:not(.summernote, .checkbox-template, .form-control-custom),.note-editable.card-block', + errorPlacement: function (error, element) { + // Add the `invalid-feedback` class to the error element + error.addClass("invalid-feedback"); + if (element.prop("type") === "checkbox") { + error.insertAfter(element.siblings("label")); + } + else { + error.insertAfter(element); + } + } + }); + + }); + + // ------------------------------------------------------- // + // Material Inputs + // ------------------------------------------------------ // + + var materialInputs = $('input.input-material'); + + // activate labels for prefilled values + materialInputs.filter(function() { return $(this).val() !== ""; }).siblings('.label-material').addClass('active'); + + // move label on focus + materialInputs.on('focus', function () { + $(this).siblings('.label-material').addClass('active'); + }); + + // remove/keep label on blur + materialInputs.on('blur', function () { + $(this).siblings('.label-material').removeClass('active'); + + if ($(this).val() !== '') { + $(this).siblings('.label-material').addClass('active'); + } else { + $(this).siblings('.label-material').removeClass('active'); + } + }); + + // ------------------------------------------------------- // + // Footer + // ------------------------------------------------------ // + + var contentInner = $('.content-inner'); + + $(document).on('sidebarChanged', function () { + adjustFooter(); + }); + + $(window).on('resize', function () { + adjustFooter(); + }) + + function adjustFooter() { + var footerBlockHeight = $('.main-footer').outerHeight(); + contentInner.css('padding-bottom', footerBlockHeight + 'px'); + } + + // ------------------------------------------------------- // + // External links to new window + // ------------------------------------------------------ // + $('.external').on('click', function (e) { + + e.preventDefault(); + window.open($(this).attr("href")); + }); + + // ------------------------------------------------------ // + // For demo purposes, can be deleted + // ------------------------------------------------------ // + + var stylesheet = $('link#theme-stylesheet'); + $("").insertAfter(stylesheet); + var alternateColour = $('link#new-stylesheet'); + + if ($.cookie("theme_csspath")) { + alternateColour.attr("href", $.cookie("theme_csspath")); + } + + $("#colour").change(function () { + + if ($(this).val() !== '') { + + var theme_csspath = 'css/style.' + $(this).val() + '.css'; + + alternateColour.attr("href", theme_csspath); + + $.cookie("theme_csspath", theme_csspath, { + expires: 365, + path: document.URL.substr(0, document.URL.lastIndexOf('/')) + }); + + } + + return false; + }); + +}); \ No newline at end of file diff --git a/src/main/webapp/static/js/util/bootstrap-datetimepicker.min.js b/src/main/webapp/static/js/util/bootstrap-datetimepicker.min.js new file mode 100644 index 0000000..eba15ce --- /dev/null +++ b/src/main/webapp/static/js/util/bootstrap-datetimepicker.min.js @@ -0,0 +1 @@ +(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a)}else{if(typeof exports==="object"){a(require("jquery"))}else{a(jQuery)}}}(function(d,f){if(!("indexOf" in Array.prototype)){Array.prototype.indexOf=function(k,j){if(j===f){j=0}if(j<0){j+=this.length}if(j<0){j=0}for(var l=this.length;jthis.endDate){o.push("disabled")}else{if(Math.floor(this.date.getUTCMinutes()/this.minuteStep)===Math.floor(n.getUTCMinutes()/this.minuteStep)){o.push("active")}}return o.concat((p?p:[]))};this.onRenderYear=function(o){var q=(j.onRenderYear||function(){return[]})(o);var p=["year"];if(typeof q==="string"){q=[q]}if(this.date.getUTCFullYear()===o.getUTCFullYear()){p.push("active")}var n=o.getUTCFullYear();var r=this.endDate.getUTCFullYear();if(or){p.push("disabled")}return p.concat((q?q:[]))};this.onRenderMonth=function(n){var p=(j.onRenderMonth||function(){return[]})(n);var o=["month"];if(typeof p==="string"){p=[p]}return o.concat((p?p:[]))};this.startDate=new Date(-8639968443048000);this.endDate=new Date(8639968443048000);this.datesDisabled=[];this.daysOfWeekDisabled=[];this.setStartDate(j.startDate||this.element.data("date-startdate"));this.setEndDate(j.endDate||this.element.data("date-enddate"));this.setDatesDisabled(j.datesDisabled||this.element.data("date-dates-disabled"));this.setDaysOfWeekDisabled(j.daysOfWeekDisabled||this.element.data("date-days-of-week-disabled"));this.setMinutesDisabled(j.minutesDisabled||this.element.data("date-minute-disabled"));this.setHoursDisabled(j.hoursDisabled||this.element.data("date-hour-disabled"));this.fillDow();this.fillMonths();this.update();this.showMode();if(this.isInline){this.show()}};g.prototype={constructor:g,_events:[],_attachEvents:function(){this._detachEvents();if(this.isInput){this._events=[[this.element,{focus:d.proxy(this.show,this),keyup:d.proxy(this.update,this),keydown:d.proxy(this.keydown,this)}]]}else{if(this.component&&this.hasInput){this._events=[[this.element.find("input"),{focus:d.proxy(this.show,this),keyup:d.proxy(this.update,this),keydown:d.proxy(this.keydown,this)}],[this.component,{click:d.proxy(this.show,this)}]];if(this.componentReset){this._events.push([this.componentReset,{click:d.proxy(this.reset,this)}])}}else{if(this.element.is("div")){this.isInline=true}else{this._events=[[this.element,{click:d.proxy(this.show,this)}]]}}}for(var j=0,k,l;j=this.startDate&&i<=this.endDate){this.date=i;this.setValue();this.viewDate=this.date;this.fill()}else{this.element.trigger({type:"outOfRange",date:i,startDate:this.startDate,endDate:this.endDate})}},setFormat:function(j){this.format=c.parseFormat(j,this.formatType);var i;if(this.isInput){i=this.element}else{if(this.component){i=this.element.find("input")}}if(i&&i.val()){this.setValue()}},setValue:function(){var i=this.getFormattedDate();if(!this.isInput){if(this.component){this.element.find("input").val(i)}this.element.data("date",i)}else{this.element.val(i)}if(this.linkField){d("#"+this.linkField).val(this.getFormattedDate(this.linkFormat))}},getFormattedDate:function(i){i=i||this.format;return c.formatDate(this.date,i,this.language,this.formatType,this.timezone)},setStartDate:function(i){this.startDate=i||this.startDate;if(this.startDate.valueOf()!==8639968443048000){this.startDate=c.parseDate(this.startDate,this.format,this.language,this.formatType,this.timezone)}this.update();this.updateNavArrows()},setEndDate:function(i){this.endDate=i||this.endDate;if(this.endDate.valueOf()!==8639968443048000){this.endDate=c.parseDate(this.endDate,this.format,this.language,this.formatType,this.timezone)}this.update();this.updateNavArrows()},setDatesDisabled:function(j){this.datesDisabled=j||[];if(!d.isArray(this.datesDisabled)){this.datesDisabled=this.datesDisabled.split(/,\s*/)}var i=this;this.datesDisabled=d.map(this.datesDisabled,function(k){return c.parseDate(k,i.format,i.language,i.formatType,i.timezone).toDateString()});this.update();this.updateNavArrows()},setTitle:function(i,j){return this.picker.find(i).find("th:eq(1)").text(this.title===false?j:this.title)},setDaysOfWeekDisabled:function(i){this.daysOfWeekDisabled=i||[];if(!d.isArray(this.daysOfWeekDisabled)){this.daysOfWeekDisabled=this.daysOfWeekDisabled.split(/,\s*/)}this.daysOfWeekDisabled=d.map(this.daysOfWeekDisabled,function(j){return parseInt(j,10)});this.update();this.updateNavArrows()},setMinutesDisabled:function(i){this.minutesDisabled=i||[];if(!d.isArray(this.minutesDisabled)){this.minutesDisabled=this.minutesDisabled.split(/,\s*/)}this.minutesDisabled=d.map(this.minutesDisabled,function(j){return parseInt(j,10)});this.update();this.updateNavArrows()},setHoursDisabled:function(i){this.hoursDisabled=i||[];if(!d.isArray(this.hoursDisabled)){this.hoursDisabled=this.hoursDisabled.split(/,\s*/)}this.hoursDisabled=d.map(this.hoursDisabled,function(j){return parseInt(j,10)});this.update();this.updateNavArrows()},place:function(){if(this.isInline){return}if(!this.zIndex){var j=0;d("div").each(function(){var o=parseInt(d(this).css("zIndex"),10);if(o>j){j=o}});this.zIndex=j+10}var n,m,l,k;if(this.container instanceof d){k=this.container.offset()}else{k=d(this.container).offset()}if(this.component){n=this.component.offset();l=n.left;if(this.pickerPosition==="bottom-left"||this.pickerPosition==="top-left"){l+=this.component.outerWidth()-this.picker.outerWidth()}}else{n=this.element.offset();l=n.left;if(this.pickerPosition==="bottom-left"||this.pickerPosition==="top-left"){l+=this.element.outerWidth()-this.picker.outerWidth()}}var i=document.body.clientWidth||window.innerWidth;if(l+220>i){l=i-220}if(this.pickerPosition==="top-left"||this.pickerPosition==="top-right"){m=n.top-this.picker.outerHeight()}else{m=n.top+this.height}m=m-k.top;l=l-k.left;this.picker.css({top:m,left:l,zIndex:this.zIndex})},hour_minute:"^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]",update:function(){var i,j=false;if(arguments&&arguments.length&&(typeof arguments[0]==="string"||arguments[0] instanceof Date)){i=arguments[0];j=true}else{i=(this.isInput?this.element.val():this.element.find("input").val())||this.element.data("date")||this.initialDate;if(typeof i==="string"){i=i.replace(/^\s+|\s+$/g,"")}}if(!i){i=new Date();j=false}if(typeof i==="string"){if(new RegExp(this.hour_minute).test(i)||new RegExp(this.hour_minute+":[0-5][0-9]").test(i)){i=this.getDate()}}this.date=c.parseDate(i,this.format,this.language,this.formatType,this.timezone);if(j){this.setValue()}if(this.datethis.endDate){this.viewDate=new Date(this.endDate)}else{this.viewDate=new Date(this.date)}}this.fill()},fillDow:function(){var i=this.weekStart,j="";while(i'+e[this.language].daysMin[(i++)%7]+""}j+="";this.picker.find(".datetimepicker-days thead").append(j)},fillMonths:function(){var l="";var m=new Date(this.viewDate);for(var k=0;k<12;k++){m.setUTCMonth(k);var j=this.onRenderMonth(m);l+=''+e[this.language].monthsShort[k]+""}this.picker.find(".datetimepicker-months td").html(l)},fill:function(){if(!this.date||!this.viewDate){return}var E=new Date(this.viewDate),t=E.getUTCFullYear(),G=E.getUTCMonth(),n=E.getUTCDate(),A=E.getUTCHours(),w=this.startDate.getUTCFullYear(),B=this.startDate.getUTCMonth(),p=this.endDate.getUTCFullYear(),x=this.endDate.getUTCMonth()+1,q=(new h(this.date.getUTCFullYear(),this.date.getUTCMonth(),this.date.getUTCDate())).valueOf(),D=new Date();this.setTitle(".datetimepicker-days",e[this.language].months[G]+" "+t);if(this.formatViewType==="time"){var k=this.getFormattedDate();this.setTitle(".datetimepicker-hours",k);this.setTitle(".datetimepicker-minutes",k)}else{this.setTitle(".datetimepicker-hours",n+" "+e[this.language].months[G]+" "+t);this.setTitle(".datetimepicker-minutes",n+" "+e[this.language].months[G]+" "+t)}this.picker.find("tfoot th.today").text(e[this.language].today||e.en.today).toggle(this.todayBtn!==false);this.picker.find("tfoot th.clear").text(e[this.language].clear||e.en.clear).toggle(this.clearBtn!==false);this.updateNavArrows();this.fillMonths();var I=h(t,G-1,28,0,0,0,0),z=c.getDaysInMonth(I.getUTCFullYear(),I.getUTCMonth());I.setUTCDate(z);I.setUTCDate(z-(I.getUTCDay()-this.weekStart+7)%7);var j=new Date(I);j.setUTCDate(j.getUTCDate()+42);j=j.valueOf();var r=[];var F;while(I.valueOf()")}F=this.onRenderDay(I);if(I.getUTCFullYear()t||(I.getUTCFullYear()===t&&I.getUTCMonth()>G)){F.push("new")}}if(this.todayHighlight&&I.getUTCFullYear()===D.getFullYear()&&I.getUTCMonth()===D.getMonth()&&I.getUTCDate()===D.getDate()){F.push("today")}if(I.valueOf()===q){F.push("active")}if((I.valueOf()+86400000)<=this.startDate||I.valueOf()>this.endDate||d.inArray(I.getUTCDay(),this.daysOfWeekDisabled)!==-1||d.inArray(I.toDateString(),this.datesDisabled)!==-1){F.push("disabled")}r.push(''+I.getUTCDate()+"");if(I.getUTCDay()===this.weekEnd){r.push("")}I.setUTCDate(I.getUTCDate()+1)}this.picker.find(".datetimepicker-days tbody").empty().append(r.join(""));r=[];var u="",C="",s="";var l=this.hoursDisabled||[];E=new Date(this.viewDate);for(var y=0;y<24;y++){E.setUTCHours(y);F=this.onRenderHour(E);if(l.indexOf(y)!==-1){F.push("disabled")}var v=h(t,G,n,y);if((v.valueOf()+3600000)<=this.startDate||v.valueOf()>this.endDate){F.push("disabled")}else{if(A===y){F.push("active")}}if(this.showMeridian&&e[this.language].meridiem.length===2){C=(y<12?e[this.language].meridiem[0]:e[this.language].meridiem[1]);if(C!==s){if(s!==""){r.push("")}r.push('
'+C.toUpperCase()+"")}s=C;u=(y%12?y%12:12);if(y<12){F.push("hour_am")}else{F.push("hour_pm")}r.push(''+u+"");if(y===23){r.push("
")}}else{u=y+":00";r.push(''+u+"")}}this.picker.find(".datetimepicker-hours td").html(r.join(""));r=[];u="";C="";s="";var m=this.minutesDisabled||[];E=new Date(this.viewDate);for(var y=0;y<60;y+=this.minuteStep){if(m.indexOf(y)!==-1){continue}E.setUTCMinutes(y);E.setUTCSeconds(0);F=this.onRenderMinute(E);if(this.showMeridian&&e[this.language].meridiem.length===2){C=(A<12?e[this.language].meridiem[0]:e[this.language].meridiem[1]);if(C!==s){if(s!==""){r.push("")}r.push('
'+C.toUpperCase()+"")}s=C;u=(A%12?A%12:12);r.push(''+u+":"+(y<10?"0"+y:y)+"");if(y===59){r.push("
")}}else{u=y+":00";r.push(''+A+":"+(y<10?"0"+y:y)+"")}}this.picker.find(".datetimepicker-minutes td").html(r.join(""));var J=this.date.getUTCFullYear();var o=this.setTitle(".datetimepicker-months",t).end().find(".month").removeClass("active");if(J===t){o.eq(this.date.getUTCMonth()).addClass("active")}if(tp){o.addClass("disabled")}if(t===w){o.slice(0,B).addClass("disabled")}if(t===p){o.slice(x).addClass("disabled")}r="";t=parseInt(t/10,10)*10;var H=this.setTitle(".datetimepicker-years",t+"-"+(t+9)).end().find("td");t-=1;E=new Date(this.viewDate);for(var y=-1;y<11;y++){E.setUTCFullYear(t);F=this.onRenderYear(E);if(y===-1||y===10){F.push(b)}r+=''+t+"";t+=1}H.html(r);this.place()},updateNavArrows:function(){var m=new Date(this.viewDate),k=m.getUTCFullYear(),l=m.getUTCMonth(),j=m.getUTCDate(),i=m.getUTCHours();switch(this.viewMode){case 0:if(k<=this.startDate.getUTCFullYear()&&l<=this.startDate.getUTCMonth()&&j<=this.startDate.getUTCDate()&&i<=this.startDate.getUTCHours()){this.picker.find(".prev").css({visibility:"hidden"})}else{this.picker.find(".prev").css({visibility:"visible"})}if(k>=this.endDate.getUTCFullYear()&&l>=this.endDate.getUTCMonth()&&j>=this.endDate.getUTCDate()&&i>=this.endDate.getUTCHours()){this.picker.find(".next").css({visibility:"hidden"})}else{this.picker.find(".next").css({visibility:"visible"})}break;case 1:if(k<=this.startDate.getUTCFullYear()&&l<=this.startDate.getUTCMonth()&&j<=this.startDate.getUTCDate()){this.picker.find(".prev").css({visibility:"hidden"})}else{this.picker.find(".prev").css({visibility:"visible"})}if(k>=this.endDate.getUTCFullYear()&&l>=this.endDate.getUTCMonth()&&j>=this.endDate.getUTCDate()){this.picker.find(".next").css({visibility:"hidden"})}else{this.picker.find(".next").css({visibility:"visible"})}break;case 2:if(k<=this.startDate.getUTCFullYear()&&l<=this.startDate.getUTCMonth()){this.picker.find(".prev").css({visibility:"hidden"})}else{this.picker.find(".prev").css({visibility:"visible"})}if(k>=this.endDate.getUTCFullYear()&&l>=this.endDate.getUTCMonth()){this.picker.find(".next").css({visibility:"hidden"})}else{this.picker.find(".next").css({visibility:"visible"})}break;case 3:case 4:if(k<=this.startDate.getUTCFullYear()){this.picker.find(".prev").css({visibility:"hidden"})}else{this.picker.find(".prev").css({visibility:"visible"})}if(k>=this.endDate.getUTCFullYear()){this.picker.find(".next").css({visibility:"hidden"})}else{this.picker.find(".next").css({visibility:"visible"})}break}},mousewheel:function(j){j.preventDefault();j.stopPropagation();if(this.wheelPause){return}this.wheelPause=true;var i=j.originalEvent;var l=i.wheelDelta;var k=l>0?1:(l===0)?0:-1;if(this.wheelViewModeNavigationInverseDirection){k=-k}this.showMode(k);setTimeout(d.proxy(function(){this.wheelPause=false},this),this.wheelViewModeNavigationDelay)},click:function(m){m.stopPropagation();m.preventDefault();var n=d(m.target).closest("span, td, th, legend");if(n.is("."+this.icontype)){n=d(n).parent().closest("span, td, th, legend")}if(n.length===1){if(n.is(".disabled")){this.element.trigger({type:"outOfRange",date:this.viewDate,startDate:this.startDate,endDate:this.endDate});return}switch(n[0].nodeName.toLowerCase()){case"th":switch(n[0].className){case"switch":this.showMode(1);break;case"prev":case"next":var i=c.modes[this.viewMode].navStep*(n[0].className==="prev"?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveHour(this.viewDate,i);break;case 1:this.viewDate=this.moveDate(this.viewDate,i);break;case 2:this.viewDate=this.moveMonth(this.viewDate,i);break;case 3:case 4:this.viewDate=this.moveYear(this.viewDate,i);break}this.fill();this.element.trigger({type:n[0].className+":"+this.convertViewModeText(this.viewMode),date:this.viewDate,startDate:this.startDate,endDate:this.endDate});break;case"clear":this.reset();if(this.autoclose){this.hide()}break;case"today":var j=new Date();j=h(j.getFullYear(),j.getMonth(),j.getDate(),j.getHours(),j.getMinutes(),j.getSeconds(),0);if(jthis.endDate){j=this.endDate}}this.viewMode=this.startViewMode;this.showMode(0);this._setDate(j);this.fill();if(this.autoclose){this.hide()}break}break;case"span":if(!n.is(".disabled")){var p=this.viewDate.getUTCFullYear(),o=this.viewDate.getUTCMonth(),q=this.viewDate.getUTCDate(),r=this.viewDate.getUTCHours(),k=this.viewDate.getUTCMinutes(),s=this.viewDate.getUTCSeconds();if(n.is(".month")){this.viewDate.setUTCDate(1);o=n.parent().find("span").index(n);q=this.viewDate.getUTCDate();this.viewDate.setUTCMonth(o);this.element.trigger({type:"changeMonth",date:this.viewDate});if(this.viewSelect>=3){this._setDate(h(p,o,q,r,k,s,0))}}else{if(n.is(".year")){this.viewDate.setUTCDate(1);p=parseInt(n.text(),10)||0;this.viewDate.setUTCFullYear(p);this.element.trigger({type:"changeYear",date:this.viewDate});if(this.viewSelect>=4){this._setDate(h(p,o,q,r,k,s,0))}}else{if(n.is(".hour")){r=parseInt(n.text(),10)||0;if(n.hasClass("hour_am")||n.hasClass("hour_pm")){if(r===12&&n.hasClass("hour_am")){r=0}else{if(r!==12&&n.hasClass("hour_pm")){r+=12}}}this.viewDate.setUTCHours(r);this.element.trigger({type:"changeHour",date:this.viewDate});if(this.viewSelect>=1){this._setDate(h(p,o,q,r,k,s,0))}}else{if(n.is(".minute")){k=parseInt(n.text().substr(n.text().indexOf(":")+1),10)||0;this.viewDate.setUTCMinutes(k);this.element.trigger({type:"changeMinute",date:this.viewDate});if(this.viewSelect>=0){this._setDate(h(p,o,q,r,k,s,0))}}}}}if(this.viewMode!==0){var l=this.viewMode;this.showMode(-1);this.fill();if(l===this.viewMode&&this.autoclose){this.hide()}}else{this.fill();if(this.autoclose){this.hide()}}}break;case"td":if(n.is(".day")&&!n.is(".disabled")){var q=parseInt(n.text(),10)||1;var p=this.viewDate.getUTCFullYear(),o=this.viewDate.getUTCMonth(),r=this.viewDate.getUTCHours(),k=this.viewDate.getUTCMinutes(),s=this.viewDate.getUTCSeconds();if(n.is(".old")){if(o===0){o=11;p-=1}else{o-=1}}else{if(n.is(".new")){if(o===11){o=0;p+=1}else{o+=1}}}this.viewDate.setUTCFullYear(p);this.viewDate.setUTCMonth(o,q);this.element.trigger({type:"changeDay",date:this.viewDate});if(this.viewSelect>=2){this._setDate(h(p,o,q,r,k,s,0))}}var l=this.viewMode;this.showMode(-1);this.fill();if(l===this.viewMode&&this.autoclose){this.hide()}break}}},_setDate:function(i,k){if(!k||k==="date"){this.date=i}if(!k||k==="view"){this.viewDate=i}this.fill();this.setValue();var j;if(this.isInput){j=this.element}else{if(this.component){j=this.element.find("input")}}if(j){j.change()}this.element.trigger({type:"changeDate",date:this.getDate()});if(i===null){this.date=this.viewDate}},moveMinute:function(j,i){if(!i){return j}var k=new Date(j.valueOf());k.setUTCMinutes(k.getUTCMinutes()+(i*this.minuteStep));return k},moveHour:function(j,i){if(!i){return j}var k=new Date(j.valueOf());k.setUTCHours(k.getUTCHours()+i);return k},moveDate:function(j,i){if(!i){return j}var k=new Date(j.valueOf());k.setUTCDate(k.getUTCDate()+i);return k},moveMonth:function(j,k){if(!k){return j}var n=new Date(j.valueOf()),r=n.getUTCDate(),o=n.getUTCMonth(),m=Math.abs(k),q,p;k=k>0?1:-1;if(m===1){p=k===-1?function(){return n.getUTCMonth()===o}:function(){return n.getUTCMonth()!==q};q=o+k;n.setUTCMonth(q);if(q<0||q>11){q=(q+12)%12}}else{for(var l=0;l=this.startDate&&i<=this.endDate},keydown:function(o){if(this.picker.is(":not(:visible)")){if(o.keyCode===27){this.show()}return}var k=false,j,i,n;switch(o.keyCode){case 27:this.hide();o.preventDefault();break;case 37:case 39:if(!this.keyboardNavigation){break}j=o.keyCode===37?-1:1;var m=this.viewMode;if(o.ctrlKey){m+=2}else{if(o.shiftKey){m+=1}}if(m===4){i=this.moveYear(this.date,j);n=this.moveYear(this.viewDate,j)}else{if(m===3){i=this.moveMonth(this.date,j);n=this.moveMonth(this.viewDate,j)}else{if(m===2){i=this.moveDate(this.date,j);n=this.moveDate(this.viewDate,j)}else{if(m===1){i=this.moveHour(this.date,j);n=this.moveHour(this.viewDate,j)}else{if(m===0){i=this.moveMinute(this.date,j);n=this.moveMinute(this.viewDate,j)}}}}}if(this.dateWithinRange(i)){this.date=i;this.viewDate=n;this.setValue();this.update();o.preventDefault();k=true}break;case 38:case 40:if(!this.keyboardNavigation){break}j=o.keyCode===38?-1:1;m=this.viewMode;if(o.ctrlKey){m+=2}else{if(o.shiftKey){m+=1}}if(m===4){i=this.moveYear(this.date,j);n=this.moveYear(this.viewDate,j)}else{if(m===3){i=this.moveMonth(this.date,j);n=this.moveMonth(this.viewDate,j)}else{if(m===2){i=this.moveDate(this.date,j*7);n=this.moveDate(this.viewDate,j*7)}else{if(m===1){if(this.showMeridian){i=this.moveHour(this.date,j*6);n=this.moveHour(this.viewDate,j*6)}else{i=this.moveHour(this.date,j*4);n=this.moveHour(this.viewDate,j*4)}}else{if(m===0){i=this.moveMinute(this.date,j*4);n=this.moveMinute(this.viewDate,j*4)}}}}}if(this.dateWithinRange(i)){this.date=i;this.viewDate=n;this.setValue();this.update();o.preventDefault();k=true}break;case 13:if(this.viewMode!==0){var p=this.viewMode;this.showMode(-1);this.fill();if(p===this.viewMode&&this.autoclose){this.hide()}}else{this.fill();if(this.autoclose){this.hide()}}o.preventDefault();break;case 9:this.hide();break}if(k){var l;if(this.isInput){l=this.element}else{if(this.component){l=this.element.find("input")}}if(l){l.change()}this.element.trigger({type:"changeDate",date:this.getDate()})}},showMode:function(i){if(i){var j=Math.max(0,Math.min(c.modes.length-1,this.viewMode+i));if(j>=this.minView&&j<=this.maxView){this.element.trigger({type:"changeMode",date:this.viewDate,oldViewMode:this.viewMode,newViewMode:j});this.viewMode=j}}this.picker.find(">div").hide().filter(".datetimepicker-"+c.modes[this.viewMode].clsName).css("display","block");this.updateNavArrows()},reset:function(){this._setDate(null,"date")},convertViewModeText:function(i){switch(i){case 4:return"decade";case 3:return"year";case 2:return"month";case 1:return"day";case 0:return"hour"}}};var b=d.fn.datetimepicker;d.fn.datetimepicker=function(k){var i=Array.apply(null,arguments);i.shift();var j;this.each(function(){var n=d(this),m=n.data("datetimepicker"),l=typeof k==="object"&&k;if(!m){n.data("datetimepicker",(m=new g(this,d.extend({},d.fn.datetimepicker.defaults,l))))}if(typeof k==="string"&&typeof m[k]==="function"){j=m[k].apply(m,i);if(j!==f){return false}}});if(j!==f){return j}else{return this}};d.fn.datetimepicker.defaults={};d.fn.datetimepicker.Constructor=g;var e=d.fn.datetimepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],meridiem:["am","pm"],suffix:["st","nd","rd","th"],today:"Today",clear:"Clear"}};var c={modes:[{clsName:"minutes",navFnc:"Hours",navStep:1},{clsName:"hours",navFnc:"Date",navStep:1},{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(i){return(((i%4===0)&&(i%100!==0))||(i%400===0))},getDaysInMonth:function(i,j){return[31,(c.isLeapYear(i)?29:28),31,30,31,30,31,31,30,31,30,31][j]},getDefaultFormat:function(i,j){if(i==="standard"){if(j==="input"){return"yyyy-mm-dd hh:ii"}else{return"yyyy-mm-dd hh:ii:ss"}}else{if(i==="php"){if(j==="input"){return"Y-m-d H:i"}else{return"Y-m-d H:i:s"}}else{throw new Error("Invalid format type.")}}},validParts:function(i){if(i==="standard"){return/t|hh?|HH?|p|P|z|Z|ii?|ss?|dd?|DD?|mm?|MM?|yy(?:yy)?/g}else{if(i==="php"){return/[dDjlNwzFmMnStyYaABgGhHis]/g}else{throw new Error("Invalid format type.")}}},nonpunctuation:/[^ -\/:-@\[-`{-~\t\n\rTZ]+/g,parseFormat:function(l,j){var i=l.replace(this.validParts(j),"\0").split("\0"),k=l.match(this.validParts(j));if(!i||!i.length||!k||k.length===0){throw new Error("Invalid date format.")}return{separators:i,parts:k}},parseDate:function(A,y,v,j,r){if(A instanceof Date){var u=new Date(A.valueOf()-A.getTimezoneOffset()*60000);u.setMilliseconds(0);return u}if(/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(A)){y=this.parseFormat("yyyy-mm-dd",j)}if(/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}$/.test(A)){y=this.parseFormat("yyyy-mm-dd hh:ii",j)}if(/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}\:\d{1,2}[Z]{0,1}$/.test(A)){y=this.parseFormat("yyyy-mm-dd hh:ii:ss",j)}if(/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(A)){var l=/([-+]\d+)([dmwy])/,q=A.match(/([-+]\d+)([dmwy])/g),t,p;A=new Date();for(var x=0;x',headTemplateV3:' ',contTemplate:'',footTemplate:''};c.template='
'+c.headTemplate+c.contTemplate+c.footTemplate+'
'+c.headTemplate+c.contTemplate+c.footTemplate+'
'+c.headTemplate+""+c.footTemplate+'
'+c.headTemplate+c.contTemplate+c.footTemplate+'
'+c.headTemplate+c.contTemplate+c.footTemplate+"
";c.templateV3='
'+c.headTemplateV3+c.contTemplate+c.footTemplate+'
'+c.headTemplateV3+c.contTemplate+c.footTemplate+'
'+c.headTemplateV3+""+c.footTemplate+'
'+c.headTemplateV3+c.contTemplate+c.footTemplate+'
'+c.headTemplateV3+c.contTemplate+c.footTemplate+"
";d.fn.datetimepicker.DPGlobal=c;d.fn.datetimepicker.noConflict=function(){d.fn.datetimepicker=b;return this};d(document).on("focus.datetimepicker.data-api click.datetimepicker.data-api",'[data-provide="datetimepicker"]',function(j){var i=d(this);if(i.data("datetimepicker")){return}j.preventDefault();i.datetimepicker("show")});d(function(){d('[data-provide="datetimepicker-inline"]').datetimepicker()})})); \ No newline at end of file diff --git a/src/main/webapp/static/js/util/bootstrap-datetimepicker.zh-CN.js b/src/main/webapp/static/js/util/bootstrap-datetimepicker.zh-CN.js new file mode 100644 index 0000000..418fb30 --- /dev/null +++ b/src/main/webapp/static/js/util/bootstrap-datetimepicker.zh-CN.js @@ -0,0 +1,16 @@ +/** + * Simplified Chinese translation for bootstrap-datetimepicker + * Yuan Cheung + */ +;(function($){ + $.fn.datetimepicker.dates['zh-CN'] = { + days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], + daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], + daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], + months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], + monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], + today: "今天", + suffix: [], + meridiem: ["上午", "下午"] + }; +}(jQuery)); diff --git a/src/main/webapp/static/js/util/jquery.validate.min.js b/src/main/webapp/static/js/util/jquery.validate.min.js new file mode 100644 index 0000000..20402da --- /dev/null +++ b/src/main/webapp/static/js/util/jquery.validate.min.js @@ -0,0 +1,4 @@ +/*! jQuery Validation Plugin - v1.17.0 - 7/29/2017 + * https://jqueryvalidation.org/ + * Copyright (c) 2017 Jörn Zaefferer; Licensed MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!c.settings.submitHandler||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(null!=j&&(!j.form&&j.hasAttribute("contenteditable")&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}}),a.extend(a.expr.pseudos||a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){var c=a(b).val();return null!==c&&!!a.trim(""+c)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){!this.form&&this.hasAttribute("contenteditable")&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name"));var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.hasAttribute("contenteditable")&&(this.form=a(this).closest("form")[0],this.name=d),!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type;return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=b.hasAttribute("contenteditable")?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);if("function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f){if(j=f.call(b,j),"string"!=typeof j)throw new TypeError("The normalizer should return a string value.");delete g.normalizer}for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+""),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return a.replace(/([\\!"#$%&'()*+,.\/:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(d,e){b[d]=a.isFunction(e)&&"normalizer"!==d?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[\/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e<=d},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var b,c={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a}); \ No newline at end of file diff --git a/version_diary b/version_diary new file mode 100644 index 0000000..076e2b3 --- /dev/null +++ b/version_diary @@ -0,0 +1,10 @@ +#版本1.0.0为gitee开源版本修改了数据库为本地数据库之后的版本 +功能:每日健康打开、历史打卡记录、健康周报、登陆注册、头像上传、修改资料和修改密码等。 + +#版本1.0.1修复了时间组件显示乱码问题 + +#版本1.0.2修复了头像未上传成功的问题 + +#版本2.0.2增加了默认头像,增加了邮箱认证功能,增加了近期推送功能 + +#版本2.0.3修复了近期推送邮箱未绑定没有提示的问题 \ No newline at end of file