Administrator 3 år sedan
incheckning
e9677b8b05
100 ändrade filer med 38783 tillägg och 0 borttagningar
  1. 43 0
      .gitignore
  2. 79 0
      README.md
  3. 12 0
      bin/clean.bat
  4. 12 0
      bin/package.bat
  5. 14 0
      bin/run.bat
  6. 120 0
      leiSP-admin/pom.xml
  7. 29 0
      leiSP-admin/src/main/java/com/LeispApplication.java
  8. 18 0
      leiSP-admin/src/main/java/com/LeispServletInitializer.java
  9. 112 0
      leiSP-admin/src/main/java/com/sooka/web/controller/common/CommonController.java
  10. 80 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoDialogController.java
  11. 326 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoFormController.java
  12. 35 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoIconController.java
  13. 326 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoOperateController.java
  14. 53 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoReportController.java
  15. 423 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoTableController.java
  16. 116 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/domain/CustomerModel.java
  17. 82 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/domain/GoodsModel.java
  18. 149 0
      leiSP-admin/src/main/java/com/sooka/web/controller/demo/domain/UserOperateModel.java
  19. 26 0
      leiSP-admin/src/main/java/com/sooka/web/controller/monitor/DruidController.java
  20. 31 0
      leiSP-admin/src/main/java/com/sooka/web/controller/monitor/ServerController.java
  21. 94 0
      leiSP-admin/src/main/java/com/sooka/web/controller/monitor/SysLogininforController.java
  22. 89 0
      leiSP-admin/src/main/java/com/sooka/web/controller/monitor/SysOperlogController.java
  23. 88 0
      leiSP-admin/src/main/java/com/sooka/web/controller/monitor/SysUserOnlineController.java
  24. 92 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysCaptchaController.java
  25. 157 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysConfigController.java
  26. 203 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysDeptController.java
  27. 120 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysDictDataController.java
  28. 188 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysDictTypeController.java
  29. 61 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysIndexController.java
  30. 65 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysLoginController.java
  31. 196 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysMenuController.java
  32. 112 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysNoticeController.java
  33. 163 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysPostController.java
  34. 173 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysProfileController.java
  35. 46 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysRegisterController.java
  36. 304 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysRoleController.java
  37. 291 0
      leiSP-admin/src/main/java/com/sooka/web/controller/system/SysUserController.java
  38. 26 0
      leiSP-admin/src/main/java/com/sooka/web/controller/tool/BuildController.java
  39. 24 0
      leiSP-admin/src/main/java/com/sooka/web/controller/tool/SwaggerController.java
  40. 175 0
      leiSP-admin/src/main/java/com/sooka/web/controller/tool/TestController.java
  41. 69 0
      leiSP-admin/src/main/java/com/sooka/web/core/config/SwaggerConfig.java
  42. 57 0
      leiSP-admin/src/main/resources/application-druid.yml
  43. 137 0
      leiSP-admin/src/main/resources/application.yml
  44. 24 0
      leiSP-admin/src/main/resources/banner.txt
  45. 81 0
      leiSP-admin/src/main/resources/ehcache/ehcache-shiro.xml
  46. 93 0
      leiSP-admin/src/main/resources/logback.xml
  47. 15 0
      leiSP-admin/src/main/resources/mybatis/mybatis-config.xml
  48. 617 0
      leiSP-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js
  49. 620 0
      leiSP-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js
  50. 550 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css
  51. 5697 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js
  52. 12 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css
  53. 10 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js
  54. BIN
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif
  55. BIN
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif
  56. 429 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css
  57. 3139 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js
  58. 6 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css
  59. 9 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js
  60. 1 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css
  61. 9 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js
  62. 1544 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js
  63. 10 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.min.js
  64. 663 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.css
  65. 7 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.min.js
  66. 2462 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.js
  67. 10 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.min.js
  68. BIN
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/clear.png
  69. BIN
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/loading.gif
  70. 119 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/bootstrap-table-export.js
  71. 2257 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/tableExport.js
  72. 1239 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js
  73. 117 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder/bootstrap-table-reorder.js
  74. 598 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder/jquery.tablednd.js
  75. 771 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.js
  76. 10 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.min.js
  77. 747 0
      leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-treetable/bootstrap-treetable.js
  78. 304 0
      leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.css
  79. 3616 0
      leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.js
  80. 9 0
      leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.css
  81. 10 0
      leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.js
  82. 406 0
      leiSP-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js
  83. 11 0
      leiSP-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js
  84. 418 0
      leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.css
  85. 1978 0
      leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.js
  86. 9 0
      leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.css
  87. 1 0
      leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.js
  88. 86 0
      leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.css
  89. 841 0
      leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.js
  90. 1 0
      leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.css
  91. 10 0
      leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.js
  92. 315 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/curvedLines.js
  93. 2599 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.js
  94. 750 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.pie.js
  95. 60 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.resize.js
  96. 212 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.spline.js
  97. 71 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.symbol.js
  98. 12 0
      leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.tooltip.min.js
  99. 182 0
      leiSP-admin/src/main/resources/static/ajax/libs/fullscreen/jquery.fullscreen.js
  100. 0 0
      leiSP-admin/src/main/resources/static/ajax/libs/iCheck/custom.css

+ 43 - 0
.gitignore

@@ -0,0 +1,43 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml

+ 79 - 0
README.md

@@ -0,0 +1,79 @@
+## 内置功能
+
+1.  用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2.  部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3.  岗位管理:配置系统用户所属担任职务。
+4.  菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5.  角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6.  字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7.  参数管理:对系统动态配置常用参数。
+8.  通知公告:系统通知公告信息发布维护。
+9.  操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 在线构建器:拖动表单元素生成相应的HTML代码。
+17. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 框架搭建
+1. 准备工作
+1.1 前置环境准备——安装Maven
+    基于Maven管理项目的构建,需要先安装好相应的版本。
+1.2 开发工具
+    建议使用IDEA
+1.3 JDK
+    jdk1.8+
+1.4 数据库
+    Mysql 5.6+
+
+2.运行系统
+2.1 修改数据库连接
+    编辑src/main/ resources目录下的application-druid.yml 文件,修改数据库地址账号信息。
+    执行sql/ Leisp_20180423.sql,quartz.sql 两个文件 日期随版本变化
+2.2 开发环境配置
+    编辑src/main/ resources目录下的application.yml 文件,
+    默认端口为80
+2.3 系统启动
+    运行 LeispApplication 
+    启动成功显示:
+   _____  ____   ____  _  __                 _____ _____  
+  / ____|/ __ \ / __ \| |/ /    /\    ___   / ____|  __ \ 
+ | (___ | |  | | |  | | ' /    /  \  ( _ ) | (___ | |__) |
+  \___ \| |  | | |  | |  <    / /\ \ / _ \/\\___ \|  ___/ 
+  ____) | |__| | |__| | . \  / ____ \ (_>  <____) | |     
+ |_____/ \____/ \____/|_|\_\/_/    \_\___/\/_____/|_|     
+   
+3. 文件结构
+com.sooka     
+├── common            // 工具类
+│       └── annotation                    // 自定义注解
+│       └── config                        // 全局配置
+│       └── constant                      // 通用常量
+│       └── core                          // 核心控制
+│       └── enums                         // 通用枚举
+│       └── exception                     // 通用异常
+│       └── json                          // JSON数据处理
+│       └── utils                         // 通用类处理
+│       └── xss                           // XSS过滤处理
+├── framework         // 框架核心
+│       └── aspectj                       // 注解实现
+│       └── config                        // 系统配置
+│       └── datasource                    // 数据权限
+│       └── interceptor                   // 拦截器
+│       └── manager                       // 异步处理
+│       └── shiro                         // 权限控制
+│       └── util                          // 通用工具
+│       └── web                           // 前端控制
+├── leiSP-generator   // 代码生成(可移除)
+├── leiSP-quartz      // 定时任务(可移除)
+├── leiSP-system      // 系统代码
+├── leiSP-admin       // 后台服务
+├── xxxxxx      // 其他模块
+
+4. 配置文件
+通用配置 application.yml
+数据源配置 application-druid.yml
+代码生成配置 generator.yml

+ 12 - 0
bin/clean.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ÇåÀíÉú³É·¾¶¡£
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean
+
+pause

+ 12 - 0
bin/package.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 打包Web工程,生成war/jar包文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean package -Dmaven.test.skip=true
+
+pause

+ 14 - 0
bin/run.bat

@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [ÐÅÏ¢] ÔËÐÐWeb¹¤³Ì¡£
+echo.
+
+cd %~dp0
+cd ../leiSP-admin/target
+
+set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -jar %JAVA_OPTS% leiSP-admin.jar
+
+cd bin
+pause

+ 120 - 0
leiSP-admin/pom.xml

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>leisp</artifactId>
+        <groupId>com.leisp</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>leisp-admin</artifactId>
+
+    <description>
+        web服务入口
+    </description>
+
+    <dependencies>
+
+        <!-- SpringBoot集成thymeleaf模板 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+
+        <!-- swagger2-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+
+        <!--防止进入swagger页面报类型转换错误,排除2.9.2中的引用,手动增加1.5.21版本-->
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.5.21</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+            <version>1.5.21</version>
+        </dependency>
+
+        <!-- swagger2-UI-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.leisp</groupId>
+            <artifactId>leisp-framework</artifactId>
+        </dependency>
+
+        <!-- 定时任务-->
+        <dependency>
+            <groupId>com.leisp</groupId>
+            <artifactId>leisp-quartz</artifactId>
+        </dependency>
+
+        <!-- 代码生成-->
+        <dependency>
+            <groupId>com.leisp</groupId>
+            <artifactId>leisp-generator</artifactId>
+        </dependency>
+
+        <!-- 业务依赖 -->
+        <dependency>
+            <groupId>com.leisp</groupId>
+            <artifactId>mybusiness</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>   
+                <groupId>org.apache.maven.plugins</groupId>   
+                <artifactId>maven-war-plugin</artifactId>   
+                <version>3.0.0</version>   
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <!--<warName>${project.artifactId}</warName>-->
+                </configuration>   
+           </plugin>   
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 29 - 0
leiSP-admin/src/main/java/com/LeispApplication.java

@@ -0,0 +1,29 @@
+package com;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+/**
+ * 启动程序
+ * 
+ * @author lei_wang
+ */
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+public class LeispApplication
+{
+    public static void main(String[] args)
+    {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication.run(LeispApplication.class, args);
+        System.out.println("**********SOOKA 管理系統 启动成功********* \n" +
+                "   _____  ____   ____  _  __                 _____ _____  \n" +
+                "  / ____|/ __ \\ / __ \\| |/ /    /\\    ___   / ____|  __ \\ \n" +
+                " | (___ | |  | | |  | | ' /    /  \\  ( _ ) | (___ | |__) |\n" +
+                "  \\___ \\| |  | | |  | |  <    / /\\ \\ / _ \\/\\\\___ \\|  ___/ \n" +
+                "  ____) | |__| | |__| | . \\  / ____ \\ (_>  <____) | |     \n" +
+                " |_____/ \\____/ \\____/|_|\\_\\/_/    \\_\\___/\\/_____/|_|     \n" +
+                "                                                          \n" +
+                "                                              ");
+    }
+}

+ 18 - 0
leiSP-admin/src/main/java/com/LeispServletInitializer.java

@@ -0,0 +1,18 @@
+package com;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ * 
+ * @author lei_wang
+ */
+public class LeispServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(LeispApplication.class);
+    }
+}

+ 112 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/common/CommonController.java

@@ -0,0 +1,112 @@
+package com.sooka.web.controller.common;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+import com.sooka.common.config.Global;
+import com.sooka.common.config.ServerConfig;
+import com.sooka.common.constant.Constants;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.utils.StringUtils;
+import com.sooka.common.utils.file.FileUploadUtils;
+import com.sooka.common.utils.file.FileUtils;
+
+/**
+ * 通用请求处理
+ * 
+ * @author lei_wang
+ */
+@Controller
+public class CommonController
+{
+    private static final Logger log = LoggerFactory.getLogger(CommonController.class);
+
+    @Autowired
+    private ServerConfig serverConfig;
+
+    /**
+     * 通用下载请求
+     * 
+     * @param fileName 文件名称
+     * @param delete 是否删除
+     */
+    @GetMapping("common/download")
+    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
+    {
+        try
+        {
+            if (!FileUtils.isValidFilename(fileName))
+            {
+                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
+            }
+            String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
+            String filePath = Global.getDownloadPath() + fileName;
+
+            response.setCharacterEncoding("utf-8");
+            response.setContentType("multipart/form-data");
+            response.setHeader("Content-Disposition",
+                    "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName));
+            FileUtils.writeBytes(filePath, response.getOutputStream());
+            if (delete)
+            {
+                FileUtils.deleteFile(filePath);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
+    }
+
+    /**
+     * 通用上传请求
+     */
+    @PostMapping("/common/upload")
+    @ResponseBody
+    public AjaxResult uploadFile(MultipartFile file) throws Exception
+    {
+        try
+        {
+            // 上传文件路径
+            String filePath = Global.getUploadPath();
+            // 上传并返回新文件名称
+            String fileName = FileUploadUtils.upload(filePath, file);
+            String url = serverConfig.getUrl() + fileName;
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("fileName", fileName);
+            ajax.put("url", url);
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 本地资源通用下载
+     */
+    @GetMapping("/common/download/resource")
+    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+            throws Exception
+    {
+        // 本地资源路径
+        String localPath = Global.getProfile();
+        // 数据库资源地址
+        String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
+        // 下载名称
+        String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
+        response.setCharacterEncoding("utf-8");
+        response.setContentType("multipart/form-data");
+        response.setHeader("Content-Disposition",
+                "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, downloadName));
+        FileUtils.writeBytes(downloadPath, response.getOutputStream());
+    }
+}

+ 80 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoDialogController.java

@@ -0,0 +1,80 @@
+package com.sooka.web.controller.demo.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * 模态窗口
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/demo/modal")
+public class DemoDialogController
+{
+    private String prefix = "demo/modal";
+
+    /**
+     * 模态窗口
+     */
+    @GetMapping("/dialog")
+    public String dialog()
+    {
+        return prefix + "/dialog";
+    }
+
+    /**
+     * 弹层组件
+     */
+    @GetMapping("/layer")
+    public String layer()
+    {
+        return prefix + "/layer";
+    }
+
+    /**
+     * 表单
+     */
+    @GetMapping("/form")
+    public String form()
+    {
+        return prefix + "/form";
+    }
+
+    /**
+     * 表格
+     */
+    @GetMapping("/table")
+    public String table()
+    {
+        return prefix + "/table";
+    }
+
+    /**
+     * 表格check
+     */
+    @GetMapping("/check")
+    public String check()
+    {
+        return prefix + "/table/check";
+    }
+
+    /**
+     * 表格radio
+     */
+    @GetMapping("/radio")
+    public String radio()
+    {
+        return prefix + "/table/radio";
+    }
+
+    /**
+     * 表格回传父窗体
+     */
+    @GetMapping("/parent")
+    public String parent()
+    {
+        return prefix + "/table/parent";
+    }
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 326 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoFormController.java


+ 35 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoIconController.java

@@ -0,0 +1,35 @@
+package com.sooka.web.controller.demo.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * 图标相关
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/demo/icon")
+public class DemoIconController
+{
+    private String prefix = "demo/icon";
+
+    /**
+     * FontAwesome图标
+     */
+    @GetMapping("/fontawesome")
+    public String fontAwesome()
+    {
+        return prefix + "/fontawesome";
+    }
+
+    /**
+     * Glyphicons图标
+     */
+    @GetMapping("/glyphicons")
+    public String glyphicons()
+    {
+        return prefix + "/glyphicons";
+    }
+}

+ 326 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoOperateController.java

@@ -0,0 +1,326 @@
+package com.sooka.web.controller.demo.controller;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.PageDomain;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.core.page.TableSupport;
+import com.sooka.common.core.text.Convert;
+import com.sooka.common.exception.BusinessException;
+import com.sooka.common.utils.StringUtils;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.web.controller.demo.domain.CustomerModel;
+import com.sooka.web.controller.demo.domain.UserOperateModel;
+
+/**
+ * 操作控制
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/demo/operate")
+public class DemoOperateController extends BaseController
+{
+    private String prefix = "demo/operate";
+
+    private final static Map<Integer, UserOperateModel> users = new LinkedHashMap<Integer, UserOperateModel>();
+    {
+        users.put(1, new UserOperateModel(1, "1000001", "测试1", "0", "15888888888", "ry@qq.com", 150.0, "0"));
+        users.put(2, new UserOperateModel(2, "1000002", "测试2", "1", "15666666666", "ry@qq.com", 180.0, "1"));
+        users.put(3, new UserOperateModel(3, "1000003", "测试3", "0", "15666666666", "ry@qq.com", 110.0, "1"));
+        users.put(4, new UserOperateModel(4, "1000004", "测试4", "1", "15666666666", "ry@qq.com", 220.0, "1"));
+        users.put(5, new UserOperateModel(5, "1000005", "测试5", "0", "15666666666", "ry@qq.com", 140.0, "1"));
+        users.put(6, new UserOperateModel(6, "1000006", "测试6", "1", "15666666666", "ry@qq.com", 330.0, "1"));
+        users.put(7, new UserOperateModel(7, "1000007", "测试7", "0", "15666666666", "ry@qq.com", 160.0, "1"));
+        users.put(8, new UserOperateModel(8, "1000008", "测试8", "1", "15666666666", "ry@qq.com", 170.0, "1"));
+        users.put(9, new UserOperateModel(9, "1000009", "测试9", "0", "15666666666", "ry@qq.com", 180.0, "1"));
+        users.put(10, new UserOperateModel(10, "1000010", "测试10", "0", "15666666666", "ry@qq.com", 210.0, "1"));
+        users.put(11, new UserOperateModel(11, "1000011", "测试11", "1", "15666666666", "ry@qq.com", 110.0, "1"));
+        users.put(12, new UserOperateModel(12, "1000012", "测试12", "0", "15666666666", "ry@qq.com", 120.0, "1"));
+        users.put(13, new UserOperateModel(13, "1000013", "测试13", "1", "15666666666", "ry@qq.com", 380.0, "1"));
+        users.put(14, new UserOperateModel(14, "1000014", "测试14", "0", "15666666666", "ry@qq.com", 280.0, "1"));
+        users.put(15, new UserOperateModel(15, "1000015", "测试15", "0", "15666666666", "ry@qq.com", 570.0, "1"));
+        users.put(16, new UserOperateModel(16, "1000016", "测试16", "1", "15666666666", "ry@qq.com", 260.0, "1"));
+        users.put(17, new UserOperateModel(17, "1000017", "测试17", "1", "15666666666", "ry@qq.com", 210.0, "1"));
+        users.put(18, new UserOperateModel(18, "1000018", "测试18", "1", "15666666666", "ry@qq.com", 340.0, "1"));
+        users.put(19, new UserOperateModel(19, "1000019", "测试19", "1", "15666666666", "ry@qq.com", 160.0, "1"));
+        users.put(20, new UserOperateModel(20, "1000020", "测试20", "1", "15666666666", "ry@qq.com", 220.0, "1"));
+        users.put(21, new UserOperateModel(21, "1000021", "测试21", "1", "15666666666", "ry@qq.com", 120.0, "1"));
+        users.put(22, new UserOperateModel(22, "1000022", "测试22", "1", "15666666666", "ry@qq.com", 130.0, "1"));
+        users.put(23, new UserOperateModel(23, "1000023", "测试23", "1", "15666666666", "ry@qq.com", 490.0, "1"));
+        users.put(24, new UserOperateModel(24, "1000024", "测试24", "1", "15666666666", "ry@qq.com", 570.0, "1"));
+        users.put(25, new UserOperateModel(25, "1000025", "测试25", "1", "15666666666", "ry@qq.com", 250.0, "1"));
+        users.put(26, new UserOperateModel(26, "1000026", "测试26", "1", "15666666666", "ry@qq.com", 250.0, "1"));
+    }
+
+    /**
+     * 表格
+     */
+    @GetMapping("/table")
+    public String table()
+    {
+        return prefix + "/table";
+    }
+
+    /**
+     * 其他
+     */
+    @GetMapping("/other")
+    public String other()
+    {
+        return prefix + "/other";
+    }
+
+    /**
+     * 查询数据
+     */
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(UserOperateModel userModel)
+    {
+        TableDataInfo rspData = new TableDataInfo();
+        List<UserOperateModel> userList = new ArrayList<UserOperateModel>(users.values());
+        // 查询条件过滤
+        if (StringUtils.isNotEmpty(userModel.getSearchValue()))
+        {
+            userList.clear();
+            for (Map.Entry<Integer, UserOperateModel> entry : users.entrySet())
+            {
+                if (entry.getValue().getUserName().equals(userModel.getSearchValue()))
+                {
+                    userList.add(entry.getValue());
+                }
+            }
+        }
+        else if (StringUtils.isNotEmpty(userModel.getUserName()))
+        {
+            userList.clear();
+            for (Map.Entry<Integer, UserOperateModel> entry : users.entrySet())
+            {
+                if (entry.getValue().getUserName().equals(userModel.getUserName()))
+                {
+                    userList.add(entry.getValue());
+                }
+            }
+        }
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize())
+        {
+            rspData.setRows(userList);
+            rspData.setTotal(userList.size());
+            return rspData;
+        }
+        Integer pageNum = (pageDomain.getPageNum() - 1) * 10;
+        Integer pageSize = pageDomain.getPageNum() * 10;
+        if (pageSize > userList.size())
+        {
+            pageSize = userList.size();
+        }
+        rspData.setRows(userList.subList(pageNum, pageSize));
+        rspData.setTotal(userList.size());
+        return rspData;
+    }
+
+    /**
+     * 新增用户
+     */
+    @GetMapping("/add")
+    public String add(ModelMap mmap)
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存用户
+     */
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(UserOperateModel user)
+    {
+        Integer userId = users.size() + 1;
+        user.setUserId(userId);
+        return AjaxResult.success(users.put(userId, user));
+    }
+
+    /**
+     * 新增保存主子表信息
+     */
+    @PostMapping("/customer/add")
+    @ResponseBody
+    public AjaxResult addSave(CustomerModel customerModel)
+    {
+        System.out.println(customerModel.toString());
+        return AjaxResult.success();
+    }
+
+    /**
+     * 修改用户
+     */
+    @GetMapping("/edit/{userId}")
+    public String edit(@PathVariable("userId") Integer userId, ModelMap mmap)
+    {
+        mmap.put("user", users.get(userId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存用户
+     */
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(UserOperateModel user)
+    {
+        return AjaxResult.success(users.put(user.getUserId(), user));
+    }
+
+    /**
+     * 导出
+     */
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(UserOperateModel user)
+    {
+        List<UserOperateModel> list = new ArrayList<UserOperateModel>(users.values());
+        ExcelUtil<UserOperateModel> util = new ExcelUtil<UserOperateModel>(UserOperateModel.class);
+        return util.exportExcel(list, "用户数据");
+    }
+
+    /**
+     * 下载模板
+     */
+    @GetMapping("/importTemplate")
+    @ResponseBody
+    public AjaxResult importTemplate()
+    {
+        ExcelUtil<UserOperateModel> util = new ExcelUtil<UserOperateModel>(UserOperateModel.class);
+        return util.importTemplateExcel("用户数据");
+    }
+
+    /**
+     * 导入数据
+     */
+    @PostMapping("/importData")
+    @ResponseBody
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<UserOperateModel> util = new ExcelUtil<UserOperateModel>(UserOperateModel.class);
+        List<UserOperateModel> userList = util.importExcel(file.getInputStream());
+        String message = importUser(userList, updateSupport);
+        return AjaxResult.success(message);
+    }
+
+    /**
+     * 删除用户
+     */
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        Integer[] userIds = Convert.toIntArray(ids);
+        for (Integer userId : userIds)
+        {
+            users.remove(userId);
+        }
+        return AjaxResult.success();
+    }
+
+    /**
+     * 查看详细
+     */
+    @GetMapping("/detail/{userId}")
+    public String detail(@PathVariable("userId") Integer userId, ModelMap mmap)
+    {
+        mmap.put("user", users.get(userId));
+        return prefix + "/detail";
+    }
+
+    @PostMapping("/clean")
+    @ResponseBody
+    public AjaxResult clean()
+    {
+        users.clear();
+        return success();
+    }
+
+    /**
+     * 导入用户数据
+     * 
+     * @param userList 用户数据列表
+     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
+     * @return 结果
+     */
+    public String importUser(List<UserOperateModel> userList, Boolean isUpdateSupport)
+    {
+        if (StringUtils.isNull(userList) || userList.size() == 0)
+        {
+            throw new BusinessException("导入用户数据不能为空!");
+        }
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        for (UserOperateModel user : userList)
+        {
+            try
+            {
+                // 验证是否存在这个用户
+                boolean userFlag = false;
+                for (Map.Entry<Integer, UserOperateModel> entry : users.entrySet())
+                {
+                    if (entry.getValue().getUserName().equals(user.getUserName()))
+                    {
+                        userFlag = true;
+                        break;
+                    }
+                }
+                if (!userFlag)
+                {
+                    Integer userId = users.size() + 1;
+                    user.setUserId(userId);
+                    users.put(userId, user);
+                    successNum++;
+                    successMsg.append("<br/>" + successNum + "、用户 " + user.getUserName() + " 导入成功");
+                }
+                else if (isUpdateSupport)
+                {
+                    users.put(user.getUserId(), user);
+                    successNum++;
+                    successMsg.append("<br/>" + successNum + "、用户 " + user.getUserName() + " 更新成功");
+                }
+                else
+                {
+                    failureNum++;
+                    failureMsg.append("<br/>" + failureNum + "、用户 " + user.getUserName() + " 已存在");
+                }
+            }
+            catch (Exception e)
+            {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
+                failureMsg.append(msg + e.getMessage());
+            }
+        }
+        if (failureNum > 0)
+        {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            throw new BusinessException(failureMsg.toString());
+        }
+        else
+        {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        return successMsg.toString();
+    }
+}

+ 53 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoReportController.java

@@ -0,0 +1,53 @@
+package com.sooka.web.controller.demo.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * 报表
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/demo/report")
+public class DemoReportController
+{
+    private String prefix = "demo/report";
+
+    /**
+     * 百度ECharts
+     */
+    @GetMapping("/echarts")
+    public String echarts()
+    {
+        return prefix + "/echarts";
+    }
+
+    /**
+     * 图表插件
+     */
+    @GetMapping("/peity")
+    public String peity()
+    {
+        return prefix + "/peity";
+    }
+
+    /**
+     * 线状图插件
+     */
+    @GetMapping("/sparkline")
+    public String sparkline()
+    {
+        return prefix + "/sparkline";
+    }
+
+    /**
+     * 图表组合
+     */
+    @GetMapping("/metrics")
+    public String metrics()
+    {
+        return prefix + "/metrics";
+    }
+}

+ 423 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/controller/DemoTableController.java

@@ -0,0 +1,423 @@
+package com.sooka.web.controller.demo.controller;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.page.PageDomain;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.core.page.TableSupport;
+import com.sooka.common.utils.DateUtils;
+import com.sooka.common.utils.StringUtils;
+
+/**
+ * 表格相关
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/demo/table")
+public class DemoTableController extends BaseController
+{
+    private String prefix = "demo/table";
+
+    private final static List<UserTableModel> users = new ArrayList<UserTableModel>();
+    {
+        users.add(new UserTableModel(1, "1000001", "测试1", "0", "15888888888", "ry@qq.com", 150.0, "0"));
+        users.add(new UserTableModel(2, "1000002", "测试2", "1", "15666666666", "ry@qq.com", 180.0, "1"));
+        users.add(new UserTableModel(3, "1000003", "测试3", "0", "15666666666", "ry@qq.com", 110.0, "1"));
+        users.add(new UserTableModel(4, "1000004", "测试4", "1", "15666666666", "ry@qq.com", 220.0, "1"));
+        users.add(new UserTableModel(5, "1000005", "测试5", "0", "15666666666", "ry@qq.com", 140.0, "1"));
+        users.add(new UserTableModel(6, "1000006", "测试6", "1", "15666666666", "ry@qq.com", 330.0, "1"));
+        users.add(new UserTableModel(7, "1000007", "测试7", "0", "15666666666", "ry@qq.com", 160.0, "1"));
+        users.add(new UserTableModel(8, "1000008", "测试8", "1", "15666666666", "ry@qq.com", 170.0, "1"));
+        users.add(new UserTableModel(9, "1000009", "测试9", "0", "15666666666", "ry@qq.com", 180.0, "1"));
+        users.add(new UserTableModel(10, "1000010", "测试10", "0", "15666666666", "ry@qq.com", 210.0, "1"));
+        users.add(new UserTableModel(11, "1000011", "测试11", "1", "15666666666", "ry@qq.com", 110.0, "1"));
+        users.add(new UserTableModel(12, "1000012", "测试12", "0", "15666666666", "ry@qq.com", 120.0, "1"));
+        users.add(new UserTableModel(13, "1000013", "测试13", "1", "15666666666", "ry@qq.com", 380.0, "1"));
+        users.add(new UserTableModel(14, "1000014", "测试14", "0", "15666666666", "ry@qq.com", 280.0, "1"));
+        users.add(new UserTableModel(15, "1000015", "测试15", "0", "15666666666", "ry@qq.com", 570.0, "1"));
+        users.add(new UserTableModel(16, "1000016", "测试16", "1", "15666666666", "ry@qq.com", 260.0, "1"));
+        users.add(new UserTableModel(17, "1000017", "测试17", "1", "15666666666", "ry@qq.com", 210.0, "1"));
+        users.add(new UserTableModel(18, "1000018", "测试18", "1", "15666666666", "ry@qq.com", 340.0, "1"));
+        users.add(new UserTableModel(19, "1000019", "测试19", "1", "15666666666", "ry@qq.com", 160.0, "1"));
+        users.add(new UserTableModel(20, "1000020", "测试20", "1", "15666666666", "ry@qq.com", 220.0, "1"));
+        users.add(new UserTableModel(21, "1000021", "测试21", "1", "15666666666", "ry@qq.com", 120.0, "1"));
+        users.add(new UserTableModel(22, "1000022", "测试22", "1", "15666666666", "ry@qq.com", 130.0, "1"));
+        users.add(new UserTableModel(23, "1000023", "测试23", "1", "15666666666", "ry@qq.com", 490.0, "1"));
+        users.add(new UserTableModel(24, "1000024", "测试24", "1", "15666666666", "ry@qq.com", 570.0, "1"));
+        users.add(new UserTableModel(25, "1000025", "测试25", "1", "15666666666", "ry@qq.com", 250.0, "1"));
+        users.add(new UserTableModel(26, "1000026", "测试26", "1", "15666666666", "ry@qq.com", 250.0, "1"));
+    }
+
+    /**
+     * 搜索相关
+     */
+    @GetMapping("/search")
+    public String search()
+    {
+        return prefix + "/search";
+    }
+
+    /**
+     * 数据汇总
+     */
+    @GetMapping("/footer")
+    public String footer()
+    {
+        return prefix + "/footer";
+    }
+
+    /**
+     * 组合表头
+     */
+    @GetMapping("/groupHeader")
+    public String groupHeader()
+    {
+        return prefix + "/groupHeader";
+    }
+
+    /**
+     * 表格导出
+     */
+    @GetMapping("/export")
+    public String export()
+    {
+        return prefix + "/export";
+    }
+
+    /**
+     * 翻页记住选择
+     */
+    @GetMapping("/remember")
+    public String remember()
+    {
+        return prefix + "/remember";
+    }
+
+    /**
+     * 跳转至指定页
+     */
+    @GetMapping("/pageGo")
+    public String pageGo()
+    {
+        return prefix + "/pageGo";
+    }
+
+    /**
+     * 自定义查询参数
+     */
+    @GetMapping("/params")
+    public String params()
+    {
+        return prefix + "/params";
+    }
+
+    /**
+     * 多表格
+     */
+    @GetMapping("/multi")
+    public String multi()
+    {
+        return prefix + "/multi";
+    }
+
+    /**
+     * 点击按钮加载表格
+     */
+    @GetMapping("/button")
+    public String button()
+    {
+        return prefix + "/button";
+    }
+
+    /**
+     * 直接加载表格数据
+     */
+    @GetMapping("/data")
+    public String data(ModelMap mmap)
+    {
+        mmap.put("users", users);
+        return prefix + "/data";
+    }
+
+    /**
+     * 表格冻结列
+     */
+    @GetMapping("/fixedColumns")
+    public String fixedColumns()
+    {
+        return prefix + "/fixedColumns";
+    }
+
+    /**
+     * 自定义触发事件
+     */
+    @GetMapping("/event")
+    public String event()
+    {
+        return prefix + "/event";
+    }
+
+    /**
+     * 表格细节视图
+     */
+    @GetMapping("/detail")
+    public String detail()
+    {
+        return prefix + "/detail";
+    }
+
+    /**
+     * 表格父子视图
+     */
+    @GetMapping("/child")
+    public String child()
+    {
+        return prefix + "/child";
+    }
+
+    /**
+     * 表格图片预览
+     */
+    @GetMapping("/image")
+    public String image()
+    {
+        return prefix + "/image";
+    }
+
+    /**
+     * 动态增删改查
+     */
+    @GetMapping("/curd")
+    public String curd()
+    {
+        return prefix + "/curd";
+    }
+
+    /**
+     * 表格拖拽操作
+     */
+    @GetMapping("/reorder")
+    public String reorder()
+    {
+        return prefix + "/reorder";
+    }
+
+    /**
+     * 表格行内编辑操作
+     */
+    @GetMapping("/editable")
+    public String editable()
+    {
+        return prefix + "/editable";
+    }
+
+    /**
+     * 主子表提交
+     */
+    @GetMapping("/subdata")
+    public String subdata()
+    {
+        return prefix + "/subdata";
+    }
+
+    /**
+     * 表格其他操作
+     */
+    @GetMapping("/other")
+    public String other()
+    {
+        return prefix + "/other";
+    }
+
+    /**
+     * 查询数据
+     */
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(UserTableModel userModel)
+    {
+        TableDataInfo rspData = new TableDataInfo();
+        List<UserTableModel> userList = new ArrayList<UserTableModel>(Arrays.asList(new UserTableModel[users.size()]));
+        Collections.copy(userList, users);
+        // 查询条件过滤
+        if (StringUtils.isNotEmpty(userModel.getUserName()))
+        {
+            userList.clear();
+            for (UserTableModel user : users)
+            {
+                if (user.getUserName().equals(userModel.getUserName()))
+                {
+                    userList.add(user);
+                }
+            }
+        }
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize())
+        {
+            rspData.setRows(userList);
+            rspData.setTotal(userList.size());
+            return rspData;
+        }
+        Integer pageNum = (pageDomain.getPageNum() - 1) * 10;
+        Integer pageSize = pageDomain.getPageNum() * 10;
+        if (pageSize > userList.size())
+        {
+            pageSize = userList.size();
+        }
+        rspData.setRows(userList.subList(pageNum, pageSize));
+        rspData.setTotal(userList.size());
+        return rspData;
+    }
+}
+
+class UserTableModel
+{
+    /** 用户ID */
+    private int userId;
+
+    /** 用户编号 */
+    private String userCode;
+
+    /** 用户姓名 */
+    private String userName;
+
+    /** 用户性别 */
+    private String userSex;
+
+    /** 用户手机 */
+    private String userPhone;
+
+    /** 用户邮箱 */
+    private String userEmail;
+
+    /** 用户余额 */
+    private double userBalance;
+
+    /** 用户状态(0正常 1停用) */
+    private String status;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    public UserTableModel()
+    {
+
+    }
+
+    public UserTableModel(int userId, String userCode, String userName, String userSex, String userPhone,
+            String userEmail, double userBalance, String status)
+    {
+        this.userId = userId;
+        this.userCode = userCode;
+        this.userName = userName;
+        this.userSex = userSex;
+        this.userPhone = userPhone;
+        this.userEmail = userEmail;
+        this.userBalance = userBalance;
+        this.status = status;
+        this.createTime = DateUtils.getNowDate();
+    }
+
+    public int getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(int userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getUserCode()
+    {
+        return userCode;
+    }
+
+    public void setUserCode(String userCode)
+    {
+        this.userCode = userCode;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public void setUserName(String userName)
+    {
+        this.userName = userName;
+    }
+
+    public String getUserSex()
+    {
+        return userSex;
+    }
+
+    public void setUserSex(String userSex)
+    {
+        this.userSex = userSex;
+    }
+
+    public String getUserPhone()
+    {
+        return userPhone;
+    }
+
+    public void setUserPhone(String userPhone)
+    {
+        this.userPhone = userPhone;
+    }
+
+    public String getUserEmail()
+    {
+        return userEmail;
+    }
+
+    public void setUserEmail(String userEmail)
+    {
+        this.userEmail = userEmail;
+    }
+
+    public double getUserBalance()
+    {
+        return userBalance;
+    }
+
+    public void setUserBalance(double userBalance)
+    {
+        this.userBalance = userBalance;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public Date getCreateTime()
+    {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime)
+    {
+        this.createTime = createTime;
+    }
+}

+ 116 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/domain/CustomerModel.java

@@ -0,0 +1,116 @@
+package com.sooka.web.controller.demo.domain;
+
+import java.util.List;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 客户测试信息
+ * 
+ * @author lei_wang
+ */
+public class CustomerModel
+{
+    /**
+     * 客户姓名
+     */
+    private String name;
+
+    /**
+     * 客户手机
+     */
+    private String phonenumber;
+
+    /**
+     * 客户性别
+     */
+    private String sex;
+
+    /**
+     * 客户生日
+     */
+    private String birthday;
+
+    /**
+     * 客户描述
+     */
+    private String remark;
+
+    /**
+     * 商品信息
+     */
+    private List<GoodsModel> goods;
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public String getPhonenumber()
+    {
+        return phonenumber;
+    }
+
+    public void setPhonenumber(String phonenumber)
+    {
+        this.phonenumber = phonenumber;
+    }
+
+
+    public String getSex()
+    {
+        return sex;
+    }
+
+    public void setSex(String sex)
+    {
+        this.sex = sex;
+    }
+
+    public String getBirthday()
+    {
+        return birthday;
+    }
+
+    public void setBirthday(String birthday)
+    {
+        this.birthday = birthday;
+    }
+
+    public String getRemark()
+    {
+        return remark;
+    }
+
+    public void setRemark(String remark)
+    {
+        this.remark = remark;
+    }
+
+    public List<GoodsModel> getGoods()
+    {
+        return goods;
+    }
+
+    public void setGoods(List<GoodsModel> goods)
+    {
+        this.goods = goods;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("name", getName())
+            .append("phonenumber", getPhonenumber())
+            .append("sex", getSex())
+            .append("birthday", getBirthday())
+            .append("goods", getGoods())
+            .append("remark", getRemark())
+            .toString();
+    }
+}

+ 82 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/domain/GoodsModel.java

@@ -0,0 +1,82 @@
+package com.sooka.web.controller.demo.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 商品测试信息
+ * 
+ * @author lei_wang
+ */
+public class GoodsModel
+{
+    /**
+     * 商品名称
+     */
+    private String name;
+
+    /**
+     * 商品重量
+     */
+    private Integer weight;
+
+    /**
+     * 商品价格
+     */
+    private Double price;
+
+    /**
+     * 商品种类
+     */
+    private String type;
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public Integer getWeight()
+    {
+        return weight;
+    }
+
+    public void setWeight(Integer weight)
+    {
+        this.weight = weight;
+    }
+
+    public Double getPrice()
+    {
+        return price;
+    }
+
+    public void setPrice(Double price)
+    {
+        this.price = price;
+    }
+
+    public String getType()
+    {
+        return type;
+    }
+
+    public void setType(String type)
+    {
+        this.type = type;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("name", getName())
+            .append("weight", getWeight())
+            .append("price", getPrice())
+            .append("type", getType())
+            .toString();
+    }
+}

+ 149 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/demo/domain/UserOperateModel.java

@@ -0,0 +1,149 @@
+package com.sooka.web.controller.demo.domain;
+
+import java.util.Date;
+import com.sooka.common.annotation.Excel;
+import com.sooka.common.annotation.Excel.Type;
+import com.sooka.common.core.domain.BaseEntity;
+import com.sooka.common.utils.DateUtils;
+
+public class UserOperateModel extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    private int userId;
+
+    @Excel(name = "用户编号")
+    private String userCode;
+
+    @Excel(name = "用户姓名")
+    private String userName;
+
+    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
+    private String userSex;
+
+    @Excel(name = "用户手机")
+    private String userPhone;
+
+    @Excel(name = "用户邮箱")
+    private String userEmail;
+
+    @Excel(name = "用户余额")
+    private double userBalance;
+
+    @Excel(name = "用户状态", readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
+    private Date createTime;
+
+    public UserOperateModel()
+    {
+
+    }
+
+    public UserOperateModel(int userId, String userCode, String userName, String userSex, String userPhone,
+            String userEmail, double userBalance, String status)
+    {
+        this.userId = userId;
+        this.userCode = userCode;
+        this.userName = userName;
+        this.userSex = userSex;
+        this.userPhone = userPhone;
+        this.userEmail = userEmail;
+        this.userBalance = userBalance;
+        this.status = status;
+        this.createTime = DateUtils.getNowDate();
+    }
+
+    public int getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(int userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getUserCode()
+    {
+        return userCode;
+    }
+
+    public void setUserCode(String userCode)
+    {
+        this.userCode = userCode;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public void setUserName(String userName)
+    {
+        this.userName = userName;
+    }
+
+    public String getUserSex()
+    {
+        return userSex;
+    }
+
+    public void setUserSex(String userSex)
+    {
+        this.userSex = userSex;
+    }
+
+    public String getUserPhone()
+    {
+        return userPhone;
+    }
+
+    public void setUserPhone(String userPhone)
+    {
+        this.userPhone = userPhone;
+    }
+
+    public String getUserEmail()
+    {
+        return userEmail;
+    }
+
+    public void setUserEmail(String userEmail)
+    {
+        this.userEmail = userEmail;
+    }
+
+    public double getUserBalance()
+    {
+        return userBalance;
+    }
+
+    public void setUserBalance(double userBalance)
+    {
+        this.userBalance = userBalance;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    @Override
+    public Date getCreateTime()
+    {
+        return createTime;
+    }
+
+    @Override
+    public void setCreateTime(Date createTime)
+    {
+        this.createTime = createTime;
+    }
+}

+ 26 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/monitor/DruidController.java

@@ -0,0 +1,26 @@
+package com.sooka.web.controller.monitor;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import com.sooka.common.core.controller.BaseController;
+
+/**
+ * druid 监控
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/monitor/data")
+public class DruidController extends BaseController
+{
+    private String prefix = "/druid";
+
+    @RequiresPermissions("monitor:data:view")
+    @GetMapping()
+    public String index()
+    {
+        return redirect(prefix + "/index");
+    }
+}

+ 31 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/monitor/ServerController.java

@@ -0,0 +1,31 @@
+package com.sooka.web.controller.monitor;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.framework.web.domain.Server;
+
+/**
+ * 服务器监控
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/monitor/server")
+public class ServerController extends BaseController
+{
+    private String prefix = "monitor/server";
+
+    @RequiresPermissions("monitor:server:view")
+    @GetMapping()
+    public String server(ModelMap mmap) throws Exception
+    {
+        Server server = new Server();
+        server.copyTo();
+        mmap.put("server", server);
+        return prefix + "/server";
+    }
+}

+ 94 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/monitor/SysLogininforController.java

@@ -0,0 +1,94 @@
+package com.sooka.web.controller.monitor;
+
+import java.util.List;
+import com.sooka.framework.shiro.service.SysPasswordService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.system.domain.SysLogininfor;
+import com.sooka.system.service.ISysLogininforService;
+
+/**
+ * 系统访问记录
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/monitor/logininfor")
+public class SysLogininforController extends BaseController
+{
+    private String prefix = "monitor/logininfor";
+
+    @Autowired
+    private ISysLogininforService logininforService;
+
+    @Autowired
+    private SysPasswordService passwordService;
+
+    @RequiresPermissions("monitor:logininfor:view")
+    @GetMapping()
+    public String logininfor()
+    {
+        return prefix + "/logininfor";
+    }
+
+    @RequiresPermissions("monitor:logininfor:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysLogininfor logininfor)
+    {
+        startPage();
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        return getDataTable(list);
+    }
+
+    @Log(title = "登陆日志", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("monitor:logininfor:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysLogininfor logininfor)
+    {
+        List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
+        ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class);
+        return util.exportExcel(list, "登陆日志");
+    }
+
+    @RequiresPermissions("monitor:logininfor:remove")
+    @Log(title = "登陆日志", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(logininforService.deleteLogininforByIds(ids));
+    }
+    
+    @RequiresPermissions("monitor:logininfor:remove")
+    @Log(title = "登陆日志", businessType = BusinessType.CLEAN)
+    @PostMapping("/clean")
+    @ResponseBody
+    public AjaxResult clean()
+    {
+        logininforService.cleanLogininfor();
+        return success();
+    }
+
+    @RequiresPermissions("monitor:logininfor:unlock")
+    @Log(title = "账户解锁", businessType = BusinessType.OTHER)
+    @PostMapping("/unlock")
+    @ResponseBody
+    public AjaxResult unlock(String loginName)
+    {
+        passwordService.unlock(loginName);
+        return success();
+    }
+}

+ 89 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/monitor/SysOperlogController.java

@@ -0,0 +1,89 @@
+package com.sooka.web.controller.monitor;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.system.domain.SysOperLog;
+import com.sooka.system.service.ISysOperLogService;
+
+/**
+ * 操作日志记录
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/monitor/operlog")
+public class SysOperlogController extends BaseController
+{
+    private String prefix = "monitor/operlog";
+
+    @Autowired
+    private ISysOperLogService operLogService;
+
+    @RequiresPermissions("monitor:operlog:view")
+    @GetMapping()
+    public String operlog()
+    {
+        return prefix + "/operlog";
+    }
+
+    @RequiresPermissions("monitor:operlog:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysOperLog operLog)
+    {
+        startPage();
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        return getDataTable(list);
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("monitor:operlog:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysOperLog operLog)
+    {
+        List<SysOperLog> list = operLogService.selectOperLogList(operLog);
+        ExcelUtil<SysOperLog> util = new ExcelUtil<SysOperLog>(SysOperLog.class);
+        return util.exportExcel(list, "操作日志");
+    }
+
+    @RequiresPermissions("monitor:operlog:remove")
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(operLogService.deleteOperLogByIds(ids));
+    }
+
+    @RequiresPermissions("monitor:operlog:detail")
+    @GetMapping("/detail/{operId}")
+    public String detail(@PathVariable("operId") Long operId, ModelMap mmap)
+    {
+        mmap.put("operLog", operLogService.selectOperLogById(operId));
+        return prefix + "/detail";
+    }
+    
+    @Log(title = "操作日志", businessType = BusinessType.CLEAN)
+    @RequiresPermissions("monitor:operlog:remove")
+    @PostMapping("/clean")
+    @ResponseBody
+    public AjaxResult clean()
+    {
+        operLogService.cleanOperLog();
+        return success();
+    }
+}

+ 88 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/monitor/SysUserOnlineController.java

@@ -0,0 +1,88 @@
+package com.sooka.web.controller.monitor;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.Logical;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.core.text.Convert;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.enums.OnlineStatus;
+import com.sooka.framework.shiro.session.OnlineSession;
+import com.sooka.framework.shiro.session.OnlineSessionDAO;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysUserOnline;
+import com.sooka.system.service.ISysUserOnlineService;
+
+/**
+ * 在线用户监控
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/monitor/online")
+public class SysUserOnlineController extends BaseController
+{
+    private String prefix = "monitor/online";
+
+    @Autowired
+    private ISysUserOnlineService userOnlineService;
+
+    @Autowired
+    private OnlineSessionDAO onlineSessionDAO;
+
+    @RequiresPermissions("monitor:online:view")
+    @GetMapping()
+    public String online()
+    {
+        return prefix + "/online";
+    }
+
+    @RequiresPermissions("monitor:online:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysUserOnline userOnline)
+    {
+        startPage();
+        List<SysUserOnline> list = userOnlineService.selectUserOnlineList(userOnline);
+        return getDataTable(list);
+    }
+
+    @RequiresPermissions(value = { "monitor:online:batchForceLogout", "monitor:online:forceLogout" }, logical = Logical.OR)
+    @Log(title = "在线用户", businessType = BusinessType.FORCE)
+    @PostMapping("/batchForceLogout")
+    @ResponseBody
+    public AjaxResult batchForceLogout(String ids)
+    {
+        for (String sessionId : Convert.toStrArray(ids))
+        {
+            SysUserOnline online = userOnlineService.selectOnlineById(sessionId);
+            if (online == null)
+            {
+                return error("用户已下线");
+            }
+            OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId());
+            if (onlineSession == null)
+            {
+                return error("用户已下线");
+            }
+            if (sessionId.equals(ShiroUtils.getSessionId()))
+            {
+                return error("当前登陆用户无法强退");
+            }
+            onlineSession.setStatus(OnlineStatus.off_line);
+            onlineSessionDAO.update(onlineSession);
+            online.setStatus(OnlineStatus.off_line);
+            userOnlineService.saveOnline(online);
+        }
+        return success();
+    }
+}

+ 92 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysCaptchaController.java

@@ -0,0 +1,92 @@
+package com.sooka.web.controller.system;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+import com.google.code.kaptcha.Constants;
+import com.google.code.kaptcha.Producer;
+import com.sooka.common.core.controller.BaseController;
+
+/**
+ * 图片验证码(支持算术形式)
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/captcha")
+public class SysCaptchaController extends BaseController
+{
+    @Resource(name = "captchaProducer")
+    private Producer captchaProducer;
+
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+
+    /**
+     * 验证码生成
+     */
+    @GetMapping(value = "/captchaImage")
+    public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response)
+    {
+        ServletOutputStream out = null;
+        try
+        {
+            HttpSession session = request.getSession();
+            response.setDateHeader("Expires", 0);
+            response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
+            response.addHeader("Cache-Control", "post-check=0, pre-check=0");
+            response.setHeader("Pragma", "no-cache");
+            response.setContentType("image/jpeg");
+
+            String type = request.getParameter("type");
+            String capStr = null;
+            String code = null;
+            BufferedImage bi = null;
+            if ("math".equals(type))
+            {
+                String capText = captchaProducerMath.createText();
+                capStr = capText.substring(0, capText.lastIndexOf("@"));
+                code = capText.substring(capText.lastIndexOf("@") + 1);
+                bi = captchaProducerMath.createImage(capStr);
+            }
+            else if ("char".equals(type))
+            {
+                capStr = code = captchaProducer.createText();
+                bi = captchaProducer.createImage(capStr);
+            }
+            session.setAttribute(Constants.KAPTCHA_SESSION_KEY, code);
+            out = response.getOutputStream();
+            ImageIO.write(bi, "jpg", out);
+            out.flush();
+
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+}

+ 157 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysConfigController.java

@@ -0,0 +1,157 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysConfig;
+import com.sooka.system.service.ISysConfigService;
+
+/**
+ * 参数配置 信息操作处理
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/config")
+public class SysConfigController extends BaseController
+{
+    private String prefix = "system/config";
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @RequiresPermissions("system:config:view")
+    @GetMapping()
+    public String config()
+    {
+        return prefix + "/config";
+    }
+
+    /**
+     * 查询参数配置列表
+     */
+    @RequiresPermissions("system:config:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysConfig config)
+    {
+        startPage();
+        List<SysConfig> list = configService.selectConfigList(config);
+        return getDataTable(list);
+    }
+
+    @Log(title = "参数管理", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("system:config:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysConfig config)
+    {
+        List<SysConfig> list = configService.selectConfigList(config);
+        ExcelUtil<SysConfig> util = new ExcelUtil<SysConfig>(SysConfig.class);
+        return util.exportExcel(list, "参数数据");
+    }
+
+    /**
+     * 新增参数配置
+     */
+    @GetMapping("/add")
+    public String add()
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存参数配置
+     */
+    @RequiresPermissions("system:config:add")
+    @Log(title = "参数管理", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysConfig config)
+    {
+        if (UserConstants.CONFIG_KEY_NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config)))
+        {
+            return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        config.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(configService.insertConfig(config));
+    }
+
+    /**
+     * 修改参数配置
+     */
+    @GetMapping("/edit/{configId}")
+    public String edit(@PathVariable("configId") Long configId, ModelMap mmap)
+    {
+        mmap.put("config", configService.selectConfigById(configId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存参数配置
+     */
+    @RequiresPermissions("system:config:edit")
+    @Log(title = "参数管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysConfig config)
+    {
+        if (UserConstants.CONFIG_KEY_NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config)))
+        {
+            return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在");
+        }
+        config.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(configService.updateConfig(config));
+    }
+
+    /**
+     * 删除参数配置
+     */
+    @RequiresPermissions("system:config:remove")
+    @Log(title = "参数管理", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(configService.deleteConfigByIds(ids));
+    }
+
+    /**
+     * 清空缓存
+     */
+    @RequiresPermissions("system:config:remove")
+    @Log(title = "参数管理", businessType = BusinessType.CLEAN)
+    @GetMapping("/clearCache")
+    @ResponseBody
+    public AjaxResult clearCache()
+    {
+        configService.clearCache();
+        return success();
+    }
+
+    /**
+     * 校验参数键名
+     */
+    @PostMapping("/checkConfigKeyUnique")
+    @ResponseBody
+    public String checkConfigKeyUnique(SysConfig config)
+    {
+        return configService.checkConfigKeyUnique(config);
+    }
+}

+ 203 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysDeptController.java

@@ -0,0 +1,203 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.domain.Ztree;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.StringUtils;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysDept;
+import com.sooka.system.domain.SysRole;
+import com.sooka.system.service.ISysDeptService;
+
+/**
+ * 部门信息
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/dept")
+public class SysDeptController extends BaseController
+{
+    private String prefix = "system/dept";
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @RequiresPermissions("system:dept:view")
+    @GetMapping()
+    public String dept()
+    {
+        return prefix + "/dept";
+    }
+
+    @RequiresPermissions("system:dept:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public List<SysDept> list(SysDept dept)
+    {
+        List<SysDept> deptList = deptService.selectDeptList(dept);
+        return deptList;
+    }
+
+    /**
+     * 新增部门
+     */
+    @GetMapping("/add/{parentId}")
+    public String add(@PathVariable("parentId") Long parentId, ModelMap mmap)
+    {
+        mmap.put("dept", deptService.selectDeptById(parentId));
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存部门
+     */
+    @Log(title = "部门管理", businessType = BusinessType.INSERT)
+    @RequiresPermissions("system:dept:add")
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysDept dept)
+    {
+        if (UserConstants.DEPT_NAME_NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept)))
+        {
+            return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        dept.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(deptService.insertDept(dept));
+    }
+
+    /**
+     * 修改
+     */
+    @GetMapping("/edit/{deptId}")
+    public String edit(@PathVariable("deptId") Long deptId, ModelMap mmap)
+    {
+        SysDept dept = deptService.selectDeptById(deptId);
+        if (StringUtils.isNotNull(dept) && 100L == deptId)
+        {
+            dept.setParentName("无");
+        }
+        mmap.put("dept", dept);
+        return prefix + "/edit";
+    }
+
+    /**
+     * 保存
+     */
+    @Log(title = "部门管理", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("system:dept:edit")
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysDept dept)
+    {
+        if (UserConstants.DEPT_NAME_NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept)))
+        {
+            return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
+        }
+        else if (dept.getParentId().equals(dept.getDeptId()))
+        {
+            return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
+        }
+        else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())
+                && deptService.selectNormalChildrenDeptById(dept.getDeptId()) > 0)
+        {
+            return AjaxResult.error("该部门包含未停用的子部门!");
+        }
+        dept.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(deptService.updateDept(dept));
+    }
+
+    /**
+     * 删除
+     */
+    @Log(title = "部门管理", businessType = BusinessType.DELETE)
+    @RequiresPermissions("system:dept:remove")
+    @GetMapping("/remove/{deptId}")
+    @ResponseBody
+    public AjaxResult remove(@PathVariable("deptId") Long deptId)
+    {
+        if (deptService.selectDeptCount(deptId) > 0)
+        {
+            return AjaxResult.warn("存在下级部门,不允许删除");
+        }
+        if (deptService.checkDeptExistUser(deptId))
+        {
+            return AjaxResult.warn("部门存在用户,不允许删除");
+        }
+        return toAjax(deptService.deleteDeptById(deptId));
+    }
+
+    /**
+     * 校验部门名称
+     */
+    @PostMapping("/checkDeptNameUnique")
+    @ResponseBody
+    public String checkDeptNameUnique(SysDept dept)
+    {
+        return deptService.checkDeptNameUnique(dept);
+    }
+
+    /**
+     * 选择部门树
+     * 
+     * @param deptId 部门ID
+     * @param excludeId 排除ID
+     */
+    @GetMapping(value = { "/selectDeptTree/{deptId}", "/selectDeptTree/{deptId}/{excludeId}" })
+    public String selectDeptTree(@PathVariable("deptId") Long deptId,
+            @PathVariable(value = "excludeId", required = false) String excludeId, ModelMap mmap)
+    {
+        mmap.put("dept", deptService.selectDeptById(deptId));
+        mmap.put("excludeId", excludeId);
+        return prefix + "/tree";
+    }
+
+    /**
+     * 加载部门列表树
+     */
+    @GetMapping("/treeData")
+    @ResponseBody
+    public List<Ztree> treeData()
+    {
+        List<Ztree> ztrees = deptService.selectDeptTree(new SysDept());
+        return ztrees;
+    }
+
+    /**
+     * 加载部门列表树(排除下级)
+     */
+    @GetMapping("/treeData/{excludeId}")
+    @ResponseBody
+    public List<Ztree> treeDataExcludeChild(@PathVariable(value = "excludeId", required = false) Long excludeId)
+    {
+        SysDept dept = new SysDept();
+        dept.setDeptId(excludeId);
+        List<Ztree> ztrees = deptService.selectDeptTreeExcludeChild(dept);
+        return ztrees;
+    }
+
+    /**
+     * 加载角色部门(数据权限)列表树
+     */
+    @GetMapping("/roleDeptTreeData")
+    @ResponseBody
+    public List<Ztree> deptTreeData(SysRole role)
+    {
+        List<Ztree> ztrees = deptService.roleDeptTreeData(role);
+        return ztrees;
+    }
+}

+ 120 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysDictDataController.java

@@ -0,0 +1,120 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysDictData;
+import com.sooka.system.service.ISysDictDataService;
+
+/**
+ * 数据字典信息
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/dict/data")
+public class SysDictDataController extends BaseController
+{
+    private String prefix = "system/dict/data";
+
+    @Autowired
+    private ISysDictDataService dictDataService;
+
+    @RequiresPermissions("system:dict:view")
+    @GetMapping()
+    public String dictData()
+    {
+        return prefix + "/data";
+    }
+
+    @PostMapping("/list")
+    @RequiresPermissions("system:dict:list")
+    @ResponseBody
+    public TableDataInfo list(SysDictData dictData)
+    {
+        startPage();
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        return getDataTable(list);
+    }
+
+    @Log(title = "字典数据", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("system:dict:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysDictData dictData)
+    {
+        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
+        ExcelUtil<SysDictData> util = new ExcelUtil<SysDictData>(SysDictData.class);
+        return util.exportExcel(list, "字典数据");
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @GetMapping("/add/{dictType}")
+    public String add(@PathVariable("dictType") String dictType, ModelMap mmap)
+    {
+        mmap.put("dictType", dictType);
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存字典类型
+     */
+    @Log(title = "字典数据", businessType = BusinessType.INSERT)
+    @RequiresPermissions("system:dict:add")
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysDictData dict)
+    {
+        dict.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(dictDataService.insertDictData(dict));
+    }
+
+    /**
+     * 修改字典类型
+     */
+    @GetMapping("/edit/{dictCode}")
+    public String edit(@PathVariable("dictCode") Long dictCode, ModelMap mmap)
+    {
+        mmap.put("dict", dictDataService.selectDictDataById(dictCode));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存字典类型
+     */
+    @Log(title = "字典数据", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("system:dict:edit")
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysDictData dict)
+    {
+        dict.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(dictDataService.updateDictData(dict));
+    }
+
+    @Log(title = "字典数据", businessType = BusinessType.DELETE)
+    @RequiresPermissions("system:dict:remove")
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(dictDataService.deleteDictDataByIds(ids));
+    }
+}

+ 188 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysDictTypeController.java

@@ -0,0 +1,188 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.domain.Ztree;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysDictType;
+import com.sooka.system.service.ISysDictTypeService;
+
+/**
+ * 数据字典信息
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/dict")
+public class SysDictTypeController extends BaseController
+{
+    private String prefix = "system/dict/type";
+
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
+    @RequiresPermissions("system:dict:view")
+    @GetMapping()
+    public String dictType()
+    {
+        return prefix + "/type";
+    }
+
+    @PostMapping("/list")
+    @RequiresPermissions("system:dict:list")
+    @ResponseBody
+    public TableDataInfo list(SysDictType dictType)
+    {
+        startPage();
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        return getDataTable(list);
+    }
+
+    @Log(title = "字典类型", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("system:dict:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysDictType dictType)
+    {
+
+        List<SysDictType> list = dictTypeService.selectDictTypeList(dictType);
+        ExcelUtil<SysDictType> util = new ExcelUtil<SysDictType>(SysDictType.class);
+        return util.exportExcel(list, "字典类型");
+    }
+
+    /**
+     * 新增字典类型
+     */
+    @GetMapping("/add")
+    public String add()
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存字典类型
+     */
+    @Log(title = "字典类型", businessType = BusinessType.INSERT)
+    @RequiresPermissions("system:dict:add")
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysDictType dict)
+    {
+        if (UserConstants.DICT_TYPE_NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict)))
+        {
+            return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dict.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(dictTypeService.insertDictType(dict));
+    }
+
+    /**
+     * 修改字典类型
+     */
+    @GetMapping("/edit/{dictId}")
+    public String edit(@PathVariable("dictId") Long dictId, ModelMap mmap)
+    {
+        mmap.put("dict", dictTypeService.selectDictTypeById(dictId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存字典类型
+     */
+    @Log(title = "字典类型", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("system:dict:edit")
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysDictType dict)
+    {
+        if (UserConstants.DICT_TYPE_NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict)))
+        {
+            return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在");
+        }
+        dict.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(dictTypeService.updateDictType(dict));
+    }
+
+    @Log(title = "字典类型", businessType = BusinessType.DELETE)
+    @RequiresPermissions("system:dict:remove")
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(dictTypeService.deleteDictTypeByIds(ids));
+    }
+
+    /**
+     * 清空缓存
+     */
+    @RequiresPermissions("system:dict:remove")
+    @Log(title = "字典类型", businessType = BusinessType.CLEAN)
+    @GetMapping("/clearCache")
+    @ResponseBody
+    public AjaxResult clearCache()
+    {
+        dictTypeService.clearCache();
+        return success();
+    }
+
+    /**
+     * 查询字典详细
+     */
+    @RequiresPermissions("system:dict:list")
+    @GetMapping("/detail/{dictId}")
+    public String detail(@PathVariable("dictId") Long dictId, ModelMap mmap)
+    {
+        mmap.put("dict", dictTypeService.selectDictTypeById(dictId));
+        mmap.put("dictList", dictTypeService.selectDictTypeAll());
+        return "system/dict/data/data";
+    }
+
+    /**
+     * 校验字典类型
+     */
+    @PostMapping("/checkDictTypeUnique")
+    @ResponseBody
+    public String checkDictTypeUnique(SysDictType dictType)
+    {
+        return dictTypeService.checkDictTypeUnique(dictType);
+    }
+
+    /**
+     * 选择字典树
+     */
+    @GetMapping("/selectDictTree/{columnId}/{dictType}")
+    public String selectDeptTree(@PathVariable("columnId") Long columnId, @PathVariable("dictType") String dictType,
+            ModelMap mmap)
+    {
+        mmap.put("columnId", columnId);
+        mmap.put("dict", dictTypeService.selectDictTypeByType(dictType));
+        return prefix + "/tree";
+    }
+
+    /**
+     * 加载字典列表树
+     */
+    @GetMapping("/treeData")
+    @ResponseBody
+    public List<Ztree> treeData()
+    {
+        List<Ztree> ztrees = dictTypeService.selectDictTree(new SysDictType());
+        return ztrees;
+    }
+}

+ 61 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysIndexController.java

@@ -0,0 +1,61 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import com.sooka.common.config.Global;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysMenu;
+import com.sooka.system.domain.SysUser;
+import com.sooka.system.service.ISysConfigService;
+import com.sooka.system.service.ISysMenuService;
+
+/**
+ * 首页 业务处理
+ * 
+ * @author lei_wang
+ */
+@Controller
+public class SysIndexController extends BaseController
+{
+    @Autowired
+    private ISysMenuService menuService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    // 系统首页
+    @GetMapping("/index")
+    public String index(ModelMap mmap)
+    {
+        // 取身份信息
+        SysUser user = ShiroUtils.getSysUser();
+        // 根据用户id取出菜单
+        List<SysMenu> menus = menuService.selectMenusByUser(user);
+        mmap.put("menus", menus);
+        mmap.put("user", user);
+        mmap.put("sideTheme", configService.selectConfigByKey("sys.index.sideTheme"));
+        mmap.put("skinName", configService.selectConfigByKey("sys.index.skinName"));
+        mmap.put("copyrightYear", Global.getCopyrightYear());
+        mmap.put("demoEnabled", Global.isDemoEnabled());
+        return "index";
+    }
+
+    // 切换主题
+    @GetMapping("/system/switchSkin")
+    public String switchSkin(ModelMap mmap)
+    {
+        return "skin";
+    }
+
+    // 系统介绍
+    @GetMapping("/system/main")
+    public String main(ModelMap mmap)
+    {
+        mmap.put("version", Global.getVersion());
+        return "main";
+    }
+}

+ 65 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysLoginController.java

@@ -0,0 +1,65 @@
+package com.sooka.web.controller.system;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.subject.Subject;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.utils.ServletUtils;
+import com.sooka.common.utils.StringUtils;
+
+/**
+ * 登录验证
+ * 
+ * @author lei_wang
+ */
+@Controller
+public class SysLoginController extends BaseController
+{
+    @GetMapping("/login")
+    public String login(HttpServletRequest request, HttpServletResponse response)
+    {
+        // 如果是Ajax请求,返回Json字符串。
+        if (ServletUtils.isAjaxRequest(request))
+        {
+            return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}");
+        }
+
+        return "login";
+    }
+
+    @PostMapping("/login")
+    @ResponseBody
+    public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
+    {
+        UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
+        Subject subject = SecurityUtils.getSubject();
+        try
+        {
+            subject.login(token);
+            return success();
+        }
+        catch (AuthenticationException e)
+        {
+            String msg = "用户或密码错误";
+            if (StringUtils.isNotEmpty(e.getMessage()))
+            {
+                msg = e.getMessage();
+            }
+            return error(msg);
+        }
+    }
+
+    @GetMapping("/unauth")
+    public String unauth()
+    {
+        return "error/unauth";
+    }
+}

+ 196 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysMenuController.java

@@ -0,0 +1,196 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.domain.Ztree;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysMenu;
+import com.sooka.system.domain.SysRole;
+import com.sooka.system.service.ISysMenuService;
+
+/**
+ * 菜单信息
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/menu")
+public class SysMenuController extends BaseController
+{
+    private String prefix = "system/menu";
+
+    @Autowired
+    private ISysMenuService menuService;
+
+    @RequiresPermissions("system:menu:view")
+    @GetMapping()
+    public String menu()
+    {
+        return prefix + "/menu";
+    }
+
+    @RequiresPermissions("system:menu:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public List<SysMenu> list(SysMenu menu)
+    {
+        Long userId = ShiroUtils.getUserId();
+        List<SysMenu> menuList = menuService.selectMenuList(menu, userId);
+        return menuList;
+    }
+
+    /**
+     * 删除菜单
+     */
+    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
+    @RequiresPermissions("system:menu:remove")
+    @GetMapping("/remove/{menuId}")
+    @ResponseBody
+    public AjaxResult remove(@PathVariable("menuId") Long menuId)
+    {
+        if (menuService.selectCountMenuByParentId(menuId) > 0)
+        {
+            return AjaxResult.warn("存在子菜单,不允许删除");
+        }
+        if (menuService.selectCountRoleMenuByMenuId(menuId) > 0)
+        {
+            return AjaxResult.warn("菜单已分配,不允许删除");
+        }
+        ShiroUtils.clearCachedAuthorizationInfo();
+        return toAjax(menuService.deleteMenuById(menuId));
+    }
+
+    /**
+     * 新增
+     */
+    @GetMapping("/add/{parentId}")
+    public String add(@PathVariable("parentId") Long parentId, ModelMap mmap)
+    {
+        SysMenu menu = null;
+        if (0L != parentId)
+        {
+            menu = menuService.selectMenuById(parentId);
+        }
+        else
+        {
+            menu = new SysMenu();
+            menu.setMenuId(0L);
+            menu.setMenuName("主目录");
+        }
+        mmap.put("menu", menu);
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存菜单
+     */
+    @Log(title = "菜单管理", businessType = BusinessType.INSERT)
+    @RequiresPermissions("system:menu:add")
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysMenu menu)
+    {
+        if (UserConstants.MENU_NAME_NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu)))
+        {
+            return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        }
+        menu.setCreateBy(ShiroUtils.getLoginName());
+        ShiroUtils.clearCachedAuthorizationInfo();
+        return toAjax(menuService.insertMenu(menu));
+    }
+
+    /**
+     * 修改菜单
+     */
+    @GetMapping("/edit/{menuId}")
+    public String edit(@PathVariable("menuId") Long menuId, ModelMap mmap)
+    {
+        mmap.put("menu", menuService.selectMenuById(menuId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存菜单
+     */
+    @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("system:menu:edit")
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysMenu menu)
+    {
+        if (UserConstants.MENU_NAME_NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu)))
+        {
+            return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
+        }
+        menu.setUpdateBy(ShiroUtils.getLoginName());
+        ShiroUtils.clearCachedAuthorizationInfo();
+        return toAjax(menuService.updateMenu(menu));
+    }
+
+    /**
+     * 选择菜单图标
+     */
+    @GetMapping("/icon")
+    public String icon()
+    {
+        return prefix + "/icon";
+    }
+
+    /**
+     * 校验菜单名称
+     */
+    @PostMapping("/checkMenuNameUnique")
+    @ResponseBody
+    public String checkMenuNameUnique(SysMenu menu)
+    {
+        return menuService.checkMenuNameUnique(menu);
+    }
+
+    /**
+     * 加载角色菜单列表树
+     */
+    @GetMapping("/roleMenuTreeData")
+    @ResponseBody
+    public List<Ztree> roleMenuTreeData(SysRole role)
+    {
+        Long userId = ShiroUtils.getUserId();
+        List<Ztree> ztrees = menuService.roleMenuTreeData(role, userId);
+        return ztrees;
+    }
+
+    /**
+     * 加载所有菜单列表树
+     */
+    @GetMapping("/menuTreeData")
+    @ResponseBody
+    public List<Ztree> menuTreeData()
+    {
+        Long userId = ShiroUtils.getUserId();
+        List<Ztree> ztrees = menuService.menuTreeData(userId);
+        return ztrees;
+    }
+
+    /**
+     * 选择菜单树
+     */
+    @GetMapping("/selectMenuTree/{menuId}")
+    public String selectMenuTree(@PathVariable("menuId") Long menuId, ModelMap mmap)
+    {
+        mmap.put("menu", menuService.selectMenuById(menuId));
+        return prefix + "/tree";
+    }
+}

+ 112 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysNoticeController.java

@@ -0,0 +1,112 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysNotice;
+import com.sooka.system.service.ISysNoticeService;
+
+/**
+ * 公告 信息操作处理
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/notice")
+public class SysNoticeController extends BaseController
+{
+    private String prefix = "system/notice";
+
+    @Autowired
+    private ISysNoticeService noticeService;
+
+    @RequiresPermissions("system:notice:view")
+    @GetMapping()
+    public String notice()
+    {
+        return prefix + "/notice";
+    }
+
+    /**
+     * 查询公告列表
+     */
+    @RequiresPermissions("system:notice:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysNotice notice)
+    {
+        startPage();
+        List<SysNotice> list = noticeService.selectNoticeList(notice);
+        return getDataTable(list);
+    }
+
+    /**
+     * 新增公告
+     */
+    @GetMapping("/add")
+    public String add()
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存公告
+     */
+    @RequiresPermissions("system:notice:add")
+    @Log(title = "通知公告", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(SysNotice notice)
+    {
+        notice.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(noticeService.insertNotice(notice));
+    }
+
+    /**
+     * 修改公告
+     */
+    @GetMapping("/edit/{noticeId}")
+    public String edit(@PathVariable("noticeId") Long noticeId, ModelMap mmap)
+    {
+        mmap.put("notice", noticeService.selectNoticeById(noticeId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存公告
+     */
+    @RequiresPermissions("system:notice:edit")
+    @Log(title = "通知公告", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(SysNotice notice)
+    {
+        notice.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(noticeService.updateNotice(notice));
+    }
+
+    /**
+     * 删除公告
+     */
+    @RequiresPermissions("system:notice:remove")
+    @Log(title = "通知公告", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        return toAjax(noticeService.deleteNoticeByIds(ids));
+    }
+}

+ 163 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysPostController.java

@@ -0,0 +1,163 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysPost;
+import com.sooka.system.service.ISysPostService;
+
+/**
+ * 岗位信息操作处理
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/post")
+public class SysPostController extends BaseController
+{
+    private String prefix = "system/post";
+
+    @Autowired
+    private ISysPostService postService;
+
+    @RequiresPermissions("system:post:view")
+    @GetMapping()
+    public String operlog()
+    {
+        return prefix + "/post";
+    }
+
+    @RequiresPermissions("system:post:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysPost post)
+    {
+        startPage();
+        List<SysPost> list = postService.selectPostList(post);
+        return getDataTable(list);
+    }
+
+    @Log(title = "岗位管理", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("system:post:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysPost post)
+    {
+        List<SysPost> list = postService.selectPostList(post);
+        ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class);
+        return util.exportExcel(list, "岗位数据");
+    }
+
+    @RequiresPermissions("system:post:remove")
+    @Log(title = "岗位管理", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        try
+        {
+            return toAjax(postService.deletePostByIds(ids));
+        }
+        catch (Exception e)
+        {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 新增岗位
+     */
+    @GetMapping("/add")
+    public String add()
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存岗位
+     */
+    @RequiresPermissions("system:post:add")
+    @Log(title = "岗位管理", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysPost post)
+    {
+        if (UserConstants.POST_NAME_NOT_UNIQUE.equals(postService.checkPostNameUnique(post)))
+        {
+            return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        }
+        else if (UserConstants.POST_CODE_NOT_UNIQUE.equals(postService.checkPostCodeUnique(post)))
+        {
+            return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        post.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(postService.insertPost(post));
+    }
+
+    /**
+     * 修改岗位
+     */
+    @GetMapping("/edit/{postId}")
+    public String edit(@PathVariable("postId") Long postId, ModelMap mmap)
+    {
+        mmap.put("post", postService.selectPostById(postId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存岗位
+     */
+    @RequiresPermissions("system:post:edit")
+    @Log(title = "岗位管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysPost post)
+    {
+        if (UserConstants.POST_NAME_NOT_UNIQUE.equals(postService.checkPostNameUnique(post)))
+        {
+            return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
+        }
+        else if (UserConstants.POST_CODE_NOT_UNIQUE.equals(postService.checkPostCodeUnique(post)))
+        {
+            return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
+        }
+        post.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(postService.updatePost(post));
+    }
+
+    /**
+     * 校验岗位名称
+     */
+    @PostMapping("/checkPostNameUnique")
+    @ResponseBody
+    public String checkPostNameUnique(SysPost post)
+    {
+        return postService.checkPostNameUnique(post);
+    }
+
+    /**
+     * 校验岗位编码
+     */
+    @PostMapping("/checkPostCodeUnique")
+    @ResponseBody
+    public String checkPostCodeUnique(SysPost post)
+    {
+        return postService.checkPostCodeUnique(post);
+    }
+}

+ 173 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysProfileController.java

@@ -0,0 +1,173 @@
+package com.sooka.web.controller.system;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.config.Global;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.StringUtils;
+import com.sooka.common.utils.file.FileUploadUtils;
+import com.sooka.framework.shiro.service.SysPasswordService;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysUser;
+import com.sooka.system.service.ISysUserService;
+
+/**
+ * 个人信息 业务处理
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController
+{
+    private static final Logger log = LoggerFactory.getLogger(SysProfileController.class);
+
+    private String prefix = "system/user/profile";
+
+    @Autowired
+    private ISysUserService userService;
+    
+    @Autowired
+    private SysPasswordService passwordService;
+
+    /**
+     * 个人信息
+     */
+    @GetMapping()
+    public String profile(ModelMap mmap)
+    {
+        SysUser user = ShiroUtils.getSysUser();
+        mmap.put("user", user);
+        mmap.put("roleGroup", userService.selectUserRoleGroup(user.getUserId()));
+        mmap.put("postGroup", userService.selectUserPostGroup(user.getUserId()));
+        return prefix + "/profile";
+    }
+
+    @GetMapping("/checkPassword")
+    @ResponseBody
+    public boolean checkPassword(String password)
+    {
+        SysUser user = ShiroUtils.getSysUser();
+        if (passwordService.matches(user, password))
+        {
+            return true;
+        }
+        return false;
+    }
+
+    @GetMapping("/resetPwd")
+    public String resetPwd(ModelMap mmap)
+    {
+        SysUser user = ShiroUtils.getSysUser();
+        mmap.put("user", userService.selectUserById(user.getUserId()));
+        return prefix + "/resetPwd";
+    }
+
+    @Log(title = "重置密码", businessType = BusinessType.UPDATE)
+    @PostMapping("/resetPwd")
+    @ResponseBody
+    public AjaxResult resetPwd(String oldPassword, String newPassword)
+    {
+        SysUser user = ShiroUtils.getSysUser();
+        if (StringUtils.isNotEmpty(newPassword) && passwordService.matches(user, oldPassword))
+        {
+            user.setSalt(ShiroUtils.randomSalt());
+            user.setPassword(passwordService.encryptPassword(user.getLoginName(), newPassword, user.getSalt()));
+            if (userService.resetUserPwd(user) > 0)
+            {
+                ShiroUtils.setSysUser(userService.selectUserById(user.getUserId()));
+                return success();
+            }
+            return error();
+        }
+        else
+        {
+            return error("修改密码失败,旧密码错误");
+        }
+    }
+
+    /**
+     * 修改用户
+     */
+    @GetMapping("/edit")
+    public String edit(ModelMap mmap)
+    {
+        SysUser user = ShiroUtils.getSysUser();
+        mmap.put("user", userService.selectUserById(user.getUserId()));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改头像
+     */
+    @GetMapping("/avatar")
+    public String avatar(ModelMap mmap)
+    {
+        SysUser user = ShiroUtils.getSysUser();
+        mmap.put("user", userService.selectUserById(user.getUserId()));
+        return prefix + "/avatar";
+    }
+
+    /**
+     * 修改用户
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PostMapping("/update")
+    @ResponseBody
+    public AjaxResult update(SysUser user)
+    {
+        SysUser currentUser = ShiroUtils.getSysUser();
+        currentUser.setUserName(user.getUserName());
+        currentUser.setEmail(user.getEmail());
+        currentUser.setPhonenumber(user.getPhonenumber());
+        currentUser.setSex(user.getSex());
+        if (userService.updateUserInfo(currentUser) > 0)
+        {
+            ShiroUtils.setSysUser(userService.selectUserById(currentUser.getUserId()));
+            return success();
+        }
+        return error();
+    }
+
+    /**
+     * 保存头像
+     */
+    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateAvatar")
+    @ResponseBody
+    public AjaxResult updateAvatar(@RequestParam("avatarfile") MultipartFile file)
+    {
+        SysUser currentUser = ShiroUtils.getSysUser();
+        try
+        {
+            if (!file.isEmpty())
+            {
+                String avatar = FileUploadUtils.upload(Global.getAvatarPath(), file);
+                currentUser.setAvatar(avatar);
+                if (userService.updateUserInfo(currentUser) > 0)
+                {
+                    ShiroUtils.setSysUser(userService.selectUserById(currentUser.getUserId()));
+                    return success();
+                }
+            }
+            return error();
+        }
+        catch (Exception e)
+        {
+            log.error("修改头像失败!", e);
+            return error(e.getMessage());
+        }
+    }
+}

+ 46 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysRegisterController.java

@@ -0,0 +1,46 @@
+package com.sooka.web.controller.system;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.framework.shiro.service.SysRegisterService;
+import com.sooka.system.domain.SysUser;
+import com.sooka.system.service.ISysConfigService;
+
+/**
+ * 注册验证
+ * 
+ * @author lei_wang
+ */
+@Controller
+public class SysRegisterController extends BaseController
+{
+    @Autowired
+    private SysRegisterService registerService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @GetMapping("/register")
+    public String register()
+    {
+        return "register";
+    }
+
+    @PostMapping("/register")
+    @ResponseBody
+    public AjaxResult ajaxRegister(SysUser user)
+    {
+        if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
+        {
+            return error("当前系统没有开启注册功能!");
+        }
+        String msg = registerService.register(user);
+        return StringUtils.isEmpty(msg) ? success() : error(msg);
+    }
+}

+ 304 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysRoleController.java

@@ -0,0 +1,304 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysRole;
+import com.sooka.system.domain.SysUser;
+import com.sooka.system.domain.SysUserRole;
+import com.sooka.system.service.ISysRoleService;
+import com.sooka.system.service.ISysUserService;
+
+/**
+ * 角色信息
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/role")
+public class SysRoleController extends BaseController
+{
+    private String prefix = "system/role";
+
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @RequiresPermissions("system:role:view")
+    @GetMapping()
+    public String role()
+    {
+        return prefix + "/role";
+    }
+
+    @RequiresPermissions("system:role:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysRole role)
+    {
+        startPage();
+        List<SysRole> list = roleService.selectRoleList(role);
+        return getDataTable(list);
+    }
+
+    @Log(title = "角色管理", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("system:role:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysRole role)
+    {
+        List<SysRole> list = roleService.selectRoleList(role);
+        ExcelUtil<SysRole> util = new ExcelUtil<SysRole>(SysRole.class);
+        return util.exportExcel(list, "角色数据");
+    }
+
+    /**
+     * 新增角色
+     */
+    @GetMapping("/add")
+    public String add()
+    {
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存角色
+     */
+    @RequiresPermissions("system:role:add")
+    @Log(title = "角色管理", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysRole role)
+    {
+        if (UserConstants.ROLE_NAME_NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role)))
+        {
+            return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        }
+        else if (UserConstants.ROLE_KEY_NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role)))
+        {
+            return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+        role.setCreateBy(ShiroUtils.getLoginName());
+        ShiroUtils.clearCachedAuthorizationInfo();
+        return toAjax(roleService.insertRole(role));
+
+    }
+
+    /**
+     * 修改角色
+     */
+    @GetMapping("/edit/{roleId}")
+    public String edit(@PathVariable("roleId") Long roleId, ModelMap mmap)
+    {
+        mmap.put("role", roleService.selectRoleById(roleId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存角色
+     */
+    @RequiresPermissions("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        if (UserConstants.ROLE_NAME_NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role)))
+        {
+            return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
+        }
+        else if (UserConstants.ROLE_KEY_NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role)))
+        {
+            return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
+        }
+        role.setUpdateBy(ShiroUtils.getLoginName());
+        ShiroUtils.clearCachedAuthorizationInfo();
+        return toAjax(roleService.updateRole(role));
+    }
+
+    /**
+     * 角色分配数据权限
+     */
+    @GetMapping("/authDataScope/{roleId}")
+    public String authDataScope(@PathVariable("roleId") Long roleId, ModelMap mmap)
+    {
+        mmap.put("role", roleService.selectRoleById(roleId));
+        return prefix + "/dataScope";
+    }
+
+    /**
+     * 保存角色分配数据权限
+     */
+    @RequiresPermissions("system:role:edit")
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/authDataScope")
+    @ResponseBody
+    public AjaxResult authDataScopeSave(SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        role.setUpdateBy(ShiroUtils.getLoginName());
+        if (roleService.authDataScope(role) > 0)
+        {
+            ShiroUtils.setSysUser(userService.selectUserById(ShiroUtils.getSysUser().getUserId()));
+            return success();
+        }
+        return error();
+    }
+
+    @RequiresPermissions("system:role:remove")
+    @Log(title = "角色管理", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        try
+        {
+            return toAjax(roleService.deleteRoleByIds(ids));
+        }
+        catch (Exception e)
+        {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 校验角色名称
+     */
+    @PostMapping("/checkRoleNameUnique")
+    @ResponseBody
+    public String checkRoleNameUnique(SysRole role)
+    {
+        return roleService.checkRoleNameUnique(role);
+    }
+
+    /**
+     * 校验角色权限
+     */
+    @PostMapping("/checkRoleKeyUnique")
+    @ResponseBody
+    public String checkRoleKeyUnique(SysRole role)
+    {
+        return roleService.checkRoleKeyUnique(role);
+    }
+
+    /**
+     * 选择菜单树
+     */
+    @GetMapping("/selectMenuTree")
+    public String selectMenuTree()
+    {
+        return prefix + "/tree";
+    }
+
+    /**
+     * 角色状态修改
+     */
+    @Log(title = "角色管理", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("system:role:edit")
+    @PostMapping("/changeStatus")
+    @ResponseBody
+    public AjaxResult changeStatus(SysRole role)
+    {
+        roleService.checkRoleAllowed(role);
+        return toAjax(roleService.changeStatus(role));
+    }
+
+    /**
+     * 分配用户
+     */
+    @RequiresPermissions("system:role:edit")
+    @GetMapping("/authUser/{roleId}")
+    public String authUser(@PathVariable("roleId") Long roleId, ModelMap mmap)
+    {
+        mmap.put("role", roleService.selectRoleById(roleId));
+        return prefix + "/authUser";
+    }
+
+    /**
+     * 查询已分配用户角色列表
+     */
+    @RequiresPermissions("system:role:list")
+    @PostMapping("/authUser/allocatedList")
+    @ResponseBody
+    public TableDataInfo allocatedList(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectAllocatedList(user);
+        return getDataTable(list);
+    }
+
+    /**
+     * 取消授权
+     */
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PostMapping("/authUser/cancel")
+    @ResponseBody
+    public AjaxResult cancelAuthUser(SysUserRole userRole)
+    {
+        return toAjax(roleService.deleteAuthUser(userRole));
+    }
+
+    /**
+     * 批量取消授权
+     */
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PostMapping("/authUser/cancelAll")
+    @ResponseBody
+    public AjaxResult cancelAuthUserAll(Long roleId, String userIds)
+    {
+        return toAjax(roleService.deleteAuthUsers(roleId, userIds));
+    }
+
+    /**
+     * 选择用户
+     */
+    @GetMapping("/authUser/selectUser/{roleId}")
+    public String selectUser(@PathVariable("roleId") Long roleId, ModelMap mmap)
+    {
+        mmap.put("role", roleService.selectRoleById(roleId));
+        return prefix + "/selectUser";
+    }
+
+    /**
+     * 查询未分配用户角色列表
+     */
+    @RequiresPermissions("system:role:list")
+    @PostMapping("/authUser/unallocatedList")
+    @ResponseBody
+    public TableDataInfo unallocatedList(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectUnallocatedList(user);
+        return getDataTable(list);
+    }
+
+    /**
+     * 批量选择用户授权
+     */
+    @Log(title = "角色管理", businessType = BusinessType.GRANT)
+    @PostMapping("/authUser/selectAll")
+    @ResponseBody
+    public AjaxResult selectAuthUserAll(Long roleId, String userIds)
+    {
+        return toAjax(roleService.insertAuthUsers(roleId, userIds));
+    }
+}

+ 291 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/system/SysUserController.java

@@ -0,0 +1,291 @@
+package com.sooka.web.controller.system;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+import com.sooka.common.annotation.Log;
+import com.sooka.common.constant.UserConstants;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.core.page.TableDataInfo;
+import com.sooka.common.enums.BusinessType;
+import com.sooka.common.utils.poi.ExcelUtil;
+import com.sooka.framework.shiro.service.SysPasswordService;
+import com.sooka.framework.util.ShiroUtils;
+import com.sooka.system.domain.SysRole;
+import com.sooka.system.domain.SysUser;
+import com.sooka.system.service.ISysPostService;
+import com.sooka.system.service.ISysRoleService;
+import com.sooka.system.service.ISysUserService;
+
+/**
+ * 用户信息
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController
+{
+    private String prefix = "system/user";
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysPostService postService;
+
+    @Autowired
+    private SysPasswordService passwordService;
+
+    @RequiresPermissions("system:user:view")
+    @GetMapping()
+    public String user()
+    {
+        return prefix + "/user";
+    }
+
+    @RequiresPermissions("system:user:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list(SysUser user)
+    {
+        startPage();
+        List<SysUser> list = userService.selectUserList(user);
+        return getDataTable(list);
+    }
+
+    @Log(title = "用户管理", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("system:user:export")
+    @PostMapping("/export")
+    @ResponseBody
+    public AjaxResult export(SysUser user)
+    {
+        List<SysUser> list = userService.selectUserList(user);
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        return util.exportExcel(list, "用户数据");
+    }
+
+    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
+    @RequiresPermissions("system:user:import")
+    @PostMapping("/importData")
+    @ResponseBody
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        List<SysUser> userList = util.importExcel(file.getInputStream());
+        String operName = ShiroUtils.getSysUser().getLoginName();
+        String message = userService.importUser(userList, updateSupport, operName);
+        return AjaxResult.success(message);
+    }
+
+    @RequiresPermissions("system:user:view")
+    @GetMapping("/importTemplate")
+    @ResponseBody
+    public AjaxResult importTemplate()
+    {
+        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
+        return util.importTemplateExcel("用户数据");
+    }
+
+    /**
+     * 新增用户
+     */
+    @GetMapping("/add")
+    public String add(ModelMap mmap)
+    {
+        mmap.put("roles", roleService.selectRoleAll().stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        mmap.put("posts", postService.selectPostAll());
+        return prefix + "/add";
+    }
+
+    /**
+     * 新增保存用户
+     */
+    @RequiresPermissions("system:user:add")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave(@Validated SysUser user)
+    {
+        if (UserConstants.USER_NAME_NOT_UNIQUE.equals(userService.checkLoginNameUnique(user.getLoginName())))
+        {
+            return error("新增用户'" + user.getLoginName() + "'失败,登录账号已存在");
+        }
+        else if (UserConstants.USER_PHONE_NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
+        {
+            return error("新增用户'" + user.getLoginName() + "'失败,手机号码已存在");
+        }
+        else if (UserConstants.USER_EMAIL_NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
+        {
+            return error("新增用户'" + user.getLoginName() + "'失败,邮箱账号已存在");
+        }
+        user.setSalt(ShiroUtils.randomSalt());
+        user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt()));
+        user.setCreateBy(ShiroUtils.getLoginName());
+        return toAjax(userService.insertUser(user));
+    }
+
+    /**
+     * 修改用户
+     */
+    @GetMapping("/edit/{userId}")
+    public String edit(@PathVariable("userId") Long userId, ModelMap mmap)
+    {
+        List<SysRole> roles = roleService.selectRolesByUserId(userId);
+        mmap.put("user", userService.selectUserById(userId));
+        mmap.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        mmap.put("posts", postService.selectPostsByUserId(userId));
+        return prefix + "/edit";
+    }
+
+    /**
+     * 修改保存用户
+     */
+    @RequiresPermissions("system:user:edit")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave(@Validated SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        if (UserConstants.USER_PHONE_NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
+        {
+            return error("修改用户'" + user.getLoginName() + "'失败,手机号码已存在");
+        }
+        else if (UserConstants.USER_EMAIL_NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
+        {
+            return error("修改用户'" + user.getLoginName() + "'失败,邮箱账号已存在");
+        }
+        user.setUpdateBy(ShiroUtils.getLoginName());
+        return toAjax(userService.updateUser(user));
+    }
+
+    @RequiresPermissions("system:user:resetPwd")
+    @Log(title = "重置密码", businessType = BusinessType.UPDATE)
+    @GetMapping("/resetPwd/{userId}")
+    public String resetPwd(@PathVariable("userId") Long userId, ModelMap mmap)
+    {
+        mmap.put("user", userService.selectUserById(userId));
+        return prefix + "/resetPwd";
+    }
+
+    @RequiresPermissions("system:user:resetPwd")
+    @Log(title = "重置密码", businessType = BusinessType.UPDATE)
+    @PostMapping("/resetPwd")
+    @ResponseBody
+    public AjaxResult resetPwdSave(SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        user.setSalt(ShiroUtils.randomSalt());
+        user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt()));
+        if (userService.resetUserPwd(user) > 0)
+        {
+            if (ShiroUtils.getUserId().longValue() == user.getUserId().longValue())
+            {
+                ShiroUtils.setSysUser(userService.selectUserById(user.getUserId()));
+            }
+            return success();
+        }
+        return error();
+    }
+
+    /**
+     * 进入授权角色页
+     */
+    @GetMapping("/authRole/{userId}")
+    public String authRole(@PathVariable("userId") Long userId, ModelMap mmap)
+    {
+        SysUser user = userService.selectUserById(userId);
+        // 获取用户所属的角色列表
+        List<SysRole> roles = roleService.selectRolesByUserId(userId);
+        mmap.put("user", user);
+        mmap.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+        return prefix + "/authRole";
+    }
+
+    /**
+     * 用户授权角色
+     */
+    @RequiresPermissions("system:user:add")
+    @Log(title = "用户管理", businessType = BusinessType.GRANT)
+    @PostMapping("/authRole/insertAuthRole")
+    @ResponseBody
+    public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
+    {
+        userService.insertUserAuth(userId, roleIds);
+        return success();
+    }
+
+    @RequiresPermissions("system:user:remove")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids)
+    {
+        try
+        {
+            return toAjax(userService.deleteUserByIds(ids));
+        }
+        catch (Exception e)
+        {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 校验用户名
+     */
+    @PostMapping("/checkLoginNameUnique")
+    @ResponseBody
+    public String checkLoginNameUnique(SysUser user)
+    {
+        return userService.checkLoginNameUnique(user.getLoginName());
+    }
+
+    /**
+     * 校验手机号码
+     */
+    @PostMapping("/checkPhoneUnique")
+    @ResponseBody
+    public String checkPhoneUnique(SysUser user)
+    {
+        return userService.checkPhoneUnique(user);
+    }
+
+    /**
+     * 校验email邮箱
+     */
+    @PostMapping("/checkEmailUnique")
+    @ResponseBody
+    public String checkEmailUnique(SysUser user)
+    {
+        return userService.checkEmailUnique(user);
+    }
+
+    /**
+     * 用户状态修改
+     */
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("system:user:edit")
+    @PostMapping("/changeStatus")
+    @ResponseBody
+    public AjaxResult changeStatus(SysUser user)
+    {
+        userService.checkUserAllowed(user);
+        return toAjax(userService.changeStatus(user));
+    }
+}

+ 26 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/tool/BuildController.java

@@ -0,0 +1,26 @@
+package com.sooka.web.controller.tool;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import com.sooka.common.core.controller.BaseController;
+
+/**
+ * build 表单构建
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/tool/build")
+public class BuildController extends BaseController
+{
+    private String prefix = "tool/build";
+
+    @RequiresPermissions("tool:build:view")
+    @GetMapping()
+    public String build()
+    {
+        return prefix + "/build";
+    }
+}

+ 24 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/tool/SwaggerController.java

@@ -0,0 +1,24 @@
+package com.sooka.web.controller.tool;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import com.sooka.common.core.controller.BaseController;
+
+/**
+ * swagger 接口
+ * 
+ * @author lei_wang
+ */
+@Controller
+@RequestMapping("/tool/swagger")
+public class SwaggerController extends BaseController
+{
+    @RequiresPermissions("tool:swagger:view")
+    @GetMapping()
+    public String index()
+    {
+        return redirect("/swagger-ui.html");
+    }
+}

+ 175 - 0
leiSP-admin/src/main/java/com/sooka/web/controller/tool/TestController.java

@@ -0,0 +1,175 @@
+package com.sooka.web.controller.tool;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.sooka.common.core.controller.BaseController;
+import com.sooka.common.core.domain.AjaxResult;
+import com.sooka.common.utils.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * swagger 用户测试方法
+ * 
+ * @author lei_wang
+ */
+@Api("用户信息管理")
+@RestController
+@RequestMapping("/test/user")
+public class TestController extends BaseController
+{
+    private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
+    {
+        users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
+        users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
+    }
+
+    @ApiOperation("获取用户列表")
+    @GetMapping("/list")
+    public AjaxResult userList()
+    {
+        List<UserEntity> userList = new ArrayList<UserEntity>(users.values());
+        return AjaxResult.success(userList);
+    }
+
+    @ApiOperation("获取用户详细")
+    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path")
+    @GetMapping("/{userId}")
+    public AjaxResult getUser(@PathVariable Integer userId)
+    {
+        if (!users.isEmpty() && users.containsKey(userId))
+        {
+            return AjaxResult.success(users.get(userId));
+        }
+        else
+        {
+            return error("用户不存在");
+        }
+    }
+
+    @ApiOperation("新增用户")
+    @ApiImplicitParam(name = "userEntity", value = "新增用户信息", dataType = "UserEntity")
+    @PostMapping("/save")
+    public AjaxResult save(UserEntity user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
+        {
+            return error("用户ID不能为空");
+        }
+        return AjaxResult.success(users.put(user.getUserId(), user));
+    }
+
+    @ApiOperation("更新用户")
+    @ApiImplicitParam(name = "userEntity", value = "新增用户信息", dataType = "UserEntity")
+    @PutMapping("/update")
+    public AjaxResult update(UserEntity user)
+    {
+        if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId()))
+        {
+            return error("用户ID不能为空");
+        }
+        if (users.isEmpty() || !users.containsKey(user.getUserId()))
+        {
+            return error("用户不存在");
+        }
+        users.remove(user.getUserId());
+        return AjaxResult.success(users.put(user.getUserId(), user));
+    }
+
+    @ApiOperation("删除用户信息")
+    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path")
+    @DeleteMapping("/{userId}")
+    public AjaxResult delete(@PathVariable Integer userId)
+    {
+        if (!users.isEmpty() && users.containsKey(userId))
+        {
+            users.remove(userId);
+            return success();
+        }
+        else
+        {
+            return error("用户不存在");
+        }
+    }
+}
+
+@ApiModel("用户实体")
+class UserEntity
+{
+    @ApiModelProperty("用户ID")
+    private Integer userId;
+
+    @ApiModelProperty("用户名称")
+    private String username;
+
+    @ApiModelProperty("用户密码")
+    private String password;
+
+    @ApiModelProperty("用户手机")
+    private String mobile;
+
+    public UserEntity()
+    {
+
+    }
+
+    public UserEntity(Integer userId, String username, String password, String mobile)
+    {
+        this.userId = userId;
+        this.username = username;
+        this.password = password;
+        this.mobile = mobile;
+    }
+
+    public Integer getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Integer userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getUsername()
+    {
+        return username;
+    }
+
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+
+    public String getPassword()
+    {
+        return password;
+    }
+
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+
+    public String getMobile()
+    {
+        return mobile;
+    }
+
+    public void setMobile(String mobile)
+    {
+        this.mobile = mobile;
+    }
+}

+ 69 - 0
leiSP-admin/src/main/java/com/sooka/web/core/config/SwaggerConfig.java

@@ -0,0 +1,69 @@
+package com.sooka.web.core.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.sooka.common.config.Global;
+import io.swagger.annotations.ApiOperation;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * Swagger2的接口配置
+ * 
+ * @author lei_wang
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig
+{
+    /** 是否开启swagger */
+    @Value("${swagger.enabled}")
+    private boolean enabled;
+    
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi()
+    {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                //.apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
+                // 扫描所有 .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo()
+    {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:Leisp管理系统_接口文档")
+                // 描述
+                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
+                // 作者信息
+                .contact(new Contact(Global.getName(), null, null))
+                // 版本
+                .version("版本号:" + Global.getVersion())
+                .build();
+    }
+}

+ 57 - 0
leiSP-admin/src/main/resources/application-druid.yml

@@ -0,0 +1,57 @@
+# 数据源配置
+spring:
+    datasource:
+        type: com.alibaba.druid.pool.DruidDataSource
+        driverClassName: com.mysql.cj.jdbc.Driver
+        druid:
+            # 主库数据源
+            master:
+                url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                username: root
+                password: 123456
+            # 从库数据源
+            slave:
+                # 从数据源开关/默认关闭
+                enabled: false
+                url: 
+                username: 
+                password: 
+            # 初始连接数
+            initialSize: 5
+            # 最小连接池数量
+            minIdle: 10
+            # 最大连接池数量
+            maxActive: 20
+            # 配置获取连接等待超时的时间
+            maxWait: 60000
+            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+            timeBetweenEvictionRunsMillis: 60000
+            # 配置一个连接在池中最小生存的时间,单位是毫秒
+            minEvictableIdleTimeMillis: 300000
+            # 配置一个连接在池中最大生存的时间,单位是毫秒
+            maxEvictableIdleTimeMillis: 900000
+            # 配置检测连接是否有效
+            validationQuery: SELECT 1 FROM DUAL
+            testWhileIdle: true
+            testOnBorrow: false
+            testOnReturn: false
+            webStatFilter: 
+                enabled: true
+            statViewServlet:
+                enabled: true
+                # 设置白名单,不填则允许所有访问
+                allow:
+                url-pattern: /druid/*
+                # 控制台管理用户名和密码
+                login-username: 
+                login-password: 
+            filter:
+                stat:
+                    enabled: true
+                    # 慢SQL记录
+                    log-slow-sql: true
+                    slow-sql-millis: 1000
+                    merge-sql: true
+                wall:
+                    config:
+                        multi-statement-allow: true

+ 137 - 0
leiSP-admin/src/main/resources/application.yml

@@ -0,0 +1,137 @@
+# 项目相关配置
+leisp:
+  # 名称
+  name: LEI
+  # 版本
+  version: 1.0.0
+  # 版权年份
+  copyrightYear: 2019
+  # 实例演示开关
+  demoEnabled: true
+  # 文件路径 示例( Windows配置D:/leisp/uploadPath,Linux配置 /home/leisp/uploadPath)
+  profile: D:/workingSpace/IEDA_Pro/lEISP/leiSP-admin/src/main/resources/static/img/upload
+  # 获取ip地址开关
+  addressEnabled: false
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为80
+  port: 80
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # tomcat最大线程数,默认为200
+    max-threads: 800
+    # Tomcat启动初始化的线程数,默认值25
+    min-spare-threads: 30
+ 
+# 日志配置
+logging:
+  level:
+    com.ruoyi: debug
+    org.springframework: warn
+
+# 用户配置
+user:
+  password:
+    # 密码错误{maxRetryCount}次锁定10分钟
+    maxRetryCount: 5
+
+# Spring配置
+spring:
+  # 模板引擎
+  thymeleaf:
+    mode: HTML
+    encoding: utf-8
+    # 禁用缓存
+    cache: false
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: static/i18n/messages
+  jackson:
+    time-zone: GMT+8
+    date-format: yyyy-MM-dd HH:mm:ss
+  profiles: 
+    active: druid
+  # 文件上传
+  servlet:
+     multipart:
+       # 单个文件大小
+       max-file-size:  10MB
+       # 设置总上传的文件大小
+       max-request-size:  20MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+
+# MyBatis
+mybatis:
+    # 搜索指定包别名
+    typeAliasesPackage: com.**.domain
+    # 配置mapper的扫描,找到所有的mapper.xml映射文件
+    mapperLocations: classpath*:mapper/**/*Mapper.xml
+    # 加载全局的配置文件
+    configLocation: classpath:mybatis/mybatis-config.xml
+
+# PageHelper分页插件
+pagehelper: 
+  helperDialect: mysql
+  reasonable: true
+  supportMethodsArguments: true
+  params: count=countSql 
+
+# Shiro
+shiro:
+  user:
+    # 登录地址
+    loginUrl: /login
+    # 权限认证失败地址
+    unauthorizedUrl: /unauth
+    # 首页地址
+    indexUrl: /index
+    # 验证码开关
+    captchaEnabled: true
+    # 验证码类型 math 数组计算 char 字符
+    captchaType: math
+  cookie:
+    # 设置Cookie的域名 默认空,即当前访问的域名
+    domain: 
+    # 设置cookie的有效访问路径
+    path: /
+    # 设置HttpOnly属性
+    httpOnly: true
+    # 设置Cookie的过期时间,天为单位
+    maxAge: 30
+    # 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecretKey deskey = keygen.generateKey(); System.out.println(Base64.encodeToString(deskey.getEncoded()));
+    cipherKey: zSyK5Kp6PZAAjlT+eeNMlg==
+  session:
+    # Session超时时间,-1代表永不过期(默认30分钟)
+    expireTime: 30
+    # 同步session到数据库的周期(默认1分钟)
+    dbSyncPeriod: 1
+    # 相隔多久检查一次session的有效性,默认就是10分钟
+    validationInterval: 10
+    # 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制)
+    maxSession: -1
+    # 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
+    kickoutAfter: false
+
+# 防止XSS攻击
+xss: 
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice/*
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# Swagger配置
+swagger:
+  # 是否开启swagger
+  enabled: true

+ 24 - 0
leiSP-admin/src/main/resources/banner.txt

@@ -0,0 +1,24 @@
+Application Version: ${leisp.version}
+Spring Boot Version: ${spring-boot.version}
+////////////////////////////////////////////////////////////////////
+//                          _ooOoo_                               //
+//                         o8888888o                              //
+//                         88" . "88                              //
+//                         (| ^_^ |)                              //
+//                         O\  =  /O                              //
+//                      ____/`---'\____                           //
+//                    .'  \\|     |//  `.                         //
+//                   /  \\|||  :  |||//  \                        //
+//                  /  _||||| -:- |||||-  \                       //
+//                  |   | \\\  -  /// |   |                       //
+//                  | \_|  ''\---/''  |   |                       //
+//                  \  .-\__  `-`  ___/-. /                       //
+//                ___`. .'  /--.--\  `. . ___                     //
+//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
+//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
+//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
+//      ========`-.____`-.___\_____/___.-`____.-'========         //
+//                           `=---='                              //
+//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
+//             佛祖保佑       永不宕机      永无BUG               //
+////////////////////////////////////////////////////////////////////

+ 81 - 0
leiSP-admin/src/main/resources/ehcache/ehcache-shiro.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ehcache name="ruoyi" updateCheck="false">
+
+    <!-- 磁盘缓存位置 -->
+    <diskStore path="java.io.tmpdir"/>
+    
+    <!-- maxEntriesLocalHeap:堆内存中最大缓存对象数,0没有限制 -->
+    <!-- maxElementsInMemory: 在内存中缓存的element的最大数目。-->
+    <!-- eternal:elements是否永久有效,如果为true,timeouts将被忽略,element将永不过期 -->
+    <!-- timeToIdleSeconds:失效前的空闲秒数,当eternal为false时,这个属性才有效,0为不限制 -->
+    <!-- timeToLiveSeconds:失效前的存活秒数,创建时间到失效时间的间隔为存活时间,当eternal为false时,这个属性才有效,0为不限制 -->
+    <!-- overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上 -->
+    <!-- statistics:是否收集统计信息。如果需要监控缓存使用情况,应该打开这个选项。默认为关闭(统计会影响性能)。设置statistics="true"开启统计 -->
+    
+    <!-- 默认缓存 -->
+    <defaultCache
+            maxEntriesLocalHeap="1000"
+            eternal="false"
+            timeToIdleSeconds="3600"
+            timeToLiveSeconds="3600"
+            overflowToDisk="false">
+    </defaultCache>
+
+    <!-- 登录记录缓存 锁定10分钟 -->
+    <cache name="loginRecordCache"
+           maxEntriesLocalHeap="2000"
+           eternal="false"
+           timeToIdleSeconds="600"
+           timeToLiveSeconds="0"
+           overflowToDisk="false"
+           statistics="true">
+    </cache>
+
+    <!-- 系统活跃用户缓存 -->
+    <cache name="sys-userCache"
+           maxEntriesLocalHeap="10000"
+           overflowToDisk="false"
+           eternal="false"
+           diskPersistent="false"
+           timeToLiveSeconds="0"
+           timeToIdleSeconds="0"
+           statistics="true">
+    </cache>
+    
+    <!-- 系统缓存 -->
+    <cache name="sys-cache"
+           maxEntriesLocalHeap="1000"
+           eternal="true"
+           overflowToDisk="true"
+           statistics="true">
+    </cache>
+    
+    <!-- 系统参数缓存 -->
+    <cache name="sys-config"
+           maxEntriesLocalHeap="1000"
+           eternal="true"
+           overflowToDisk="true"
+           statistics="true">
+    </cache>
+    
+    <!-- 系统字典缓存 -->
+    <cache name="sys-dict"
+           maxEntriesLocalHeap="1000"
+           eternal="true"
+           overflowToDisk="true"
+           statistics="true">
+    </cache>
+    
+    <!-- 系统会话缓存 -->
+    <cache name="shiro-activeSessionCache"
+           maxElementsInMemory="10000"
+           overflowToDisk="true"
+           eternal="true"
+           timeToLiveSeconds="0"
+           timeToIdleSeconds="0"
+           diskPersistent="true"
+           diskExpiryThreadIntervalSeconds="600">
+    </cache>
+    
+</ehcache>
+	

+ 93 - 0
leiSP-admin/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/leisp/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+	
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+	
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+	
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+	
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.sooka" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+	
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+	
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration> 

+ 15 - 0
leiSP-admin/src/main/resources/mybatis/mybatis-config.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+	
+	<settings>
+		<setting name="cacheEnabled"             value="true"  />  <!-- 全局映射器启用缓存 -->
+		<setting name="useGeneratedKeys"         value="true"  />  <!-- 允许 JDBC 支持自动生成主键 -->
+		<setting name="defaultExecutorType"      value="REUSE" />  <!-- 配置默认的执行器 -->
+		<setting name="logImpl"                  value="SLF4J" />  <!-- 指定 MyBatis 所用日志的具体实现 -->
+		<!-- <setting name="mapUnderscoreToCamelCase" value="true"/>  驼峰式命名 -->
+	</settings>
+	
+</configuration>

+ 617 - 0
leiSP-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js

@@ -0,0 +1,617 @@
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
+/*
+
+  The MIT License (MIT)
+
+  Copyright (c) 2007-2013 Einar Lielmanis and contributors.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+
+
+ Style HTML
+---------------
+
+  Written by Nochum Sossonko, (nsossonko@hotmail.com)
+
+  Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv>
+    http://jsbeautifier.org/
+
+  Usage:
+    style_html(html_source);
+
+    style_html(html_source, options);
+
+  The options are:
+    indent_size (default 4)          — indentation size,
+    indent_char (default space)      — character to indent with,
+    max_char (default 250)            -  maximum amount of characters per line (0 = disable)
+    brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
+            put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line.
+    unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
+    indent_scripts (default normal)  - "keep"|"separate"|"normal"
+
+    e.g.
+
+    style_html(html_source, {
+      'indent_size': 2,
+      'indent_char': ' ',
+      'max_char': 78,
+      'brace_style': 'expand',
+      'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u']
+    });
+*/
+
+(function() {
+
+    function style_html(html_source, options, js_beautify, css_beautify) {
+    //Wrapper function to invoke all the necessary constructors and deal with the output.
+
+      var multi_parser,
+          indent_size,
+          indent_character,
+          max_char,
+          brace_style,
+          unformatted;
+
+      options = options || {};
+      indent_size = options.indent_size || 4;
+      indent_character = options.indent_char || ' ';
+      brace_style = options.brace_style || 'collapse';
+      max_char = options.max_char === 0 ? Infinity : options.max_char || 250;
+      unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+
+      function Parser() {
+
+        this.pos = 0; //Parser position
+        this.token = '';
+        this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
+        this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
+          parent: 'parent1',
+          parentcount: 1,
+          parent1: ''
+        };
+        this.tag_type = '';
+        this.token_text = this.last_token = this.last_text = this.token_type = '';
+
+        this.Utils = { //Uilities made available to the various functions
+          whitespace: "\n\r\t ".split(''),
+          single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
+          extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
+          in_array: function (what, arr) {
+            for (var i=0; i<arr.length; i++) {
+              if (what === arr[i]) {
+                return true;
+              }
+            }
+            return false;
+          }
+        };
+
+        this.get_content = function () { //function to capture regular content between tags
+
+          var input_char = '',
+              content = [],
+              space = false; //if a space is needed
+
+          while (this.input.charAt(this.pos) !== '<') {
+            if (this.pos >= this.input.length) {
+              return content.length?content.join(''):['', 'TK_EOF'];
+            }
+
+            input_char = this.input.charAt(this.pos);
+            this.pos++;
+            this.line_char_count++;
+
+            if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+              if (content.length) {
+                space = true;
+              }
+              this.line_char_count--;
+              continue; //don't want to insert unnecessary space
+            }
+            else if (space) {
+              if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached
+                content.push('\n');
+                for (var i=0; i<this.indent_level; i++) {
+                  content.push(this.indent_string);
+                }
+                this.line_char_count = 0;
+              }
+              else{
+                content.push(' ');
+                this.line_char_count++;
+              }
+              space = false;
+            }
+            content.push(input_char); //letter at-a-time (or string) inserted to an array
+          }
+          return content.length?content.join(''):'';
+        };
+
+        this.get_contents_to = function (name) { //get the full content of a script or style to pass to js_beautify
+          if (this.pos === this.input.length) {
+            return ['', 'TK_EOF'];
+          }
+          var input_char = '';
+          var content = '';
+          var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
+          reg_match.lastIndex = this.pos;
+          var reg_array = reg_match.exec(this.input);
+          var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script
+          if(this.pos < end_script) { //get everything in between the script tags
+            content = this.input.substring(this.pos, end_script);
+            this.pos = end_script;
+          }
+          return content;
+        };
+
+        this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object
+          if (this.tags[tag + 'count']) { //check for the existence of this tag type
+            this.tags[tag + 'count']++;
+            this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+          }
+          else { //otherwise initialize this tag type
+            this.tags[tag + 'count'] = 1;
+            this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+          }
+          this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
+          this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
+        };
+
+        this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer
+          if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
+            var temp_parent = this.tags.parent; //check to see if it's a closable tag.
+            while (temp_parent) { //till we reach '' (the initial value);
+              if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
+                break;
+              }
+              temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
+            }
+            if (temp_parent) { //if we caught something
+              this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
+              this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
+            }
+            delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
+            delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
+            if (this.tags[tag + 'count'] === 1) {
+              delete this.tags[tag + 'count'];
+            }
+            else {
+              this.tags[tag + 'count']--;
+            }
+          }
+        };
+
+        this.get_tag = function (peek) { //function to get a full tag and parse its type
+          var input_char = '',
+              content = [],
+              comment = '',
+              space = false,
+              tag_start, tag_end,
+              orig_pos = this.pos,
+              orig_line_char_count = this.line_char_count;
+
+          peek = peek !== undefined ? peek : false;
+
+          do {
+            if (this.pos >= this.input.length) {
+              if (peek) {
+                this.pos = orig_pos;
+                this.line_char_count = orig_line_char_count;
+              }
+              return content.length?content.join(''):['', 'TK_EOF'];
+            }
+
+            input_char = this.input.charAt(this.pos);
+            this.pos++;
+            this.line_char_count++;
+
+            if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
+              space = true;
+              this.line_char_count--;
+              continue;
+            }
+
+            if (input_char === "'" || input_char === '"') {
+              if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially
+                input_char += this.get_unformatted(input_char);
+                space = true;
+              }
+            }
+
+            if (input_char === '=') { //no space before =
+              space = false;
+            }
+
+            if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) {
+                //no space after = or before >
+              if (this.line_char_count >= this.max_char) {
+                this.print_newline(false, content);
+                this.line_char_count = 0;
+              }
+              else {
+                content.push(' ');
+                this.line_char_count++;
+              }
+              space = false;
+            }
+            if (input_char === '<') {
+              tag_start = this.pos - 1;
+            }
+            content.push(input_char); //inserts character at-a-time (or string)
+          } while (input_char !== '>');
+
+          var tag_complete = content.join('');
+          var tag_index;
+          if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
+            tag_index = tag_complete.indexOf(' ');
+          }
+          else { //otherwise go with the tag ending
+            tag_index = tag_complete.indexOf('>');
+          }
+          var tag_check = tag_complete.substring(1, tag_index).toLowerCase();
+          if (tag_complete.charAt(tag_complete.length-2) === '/' ||
+            this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
+            if ( ! peek) {
+              this.tag_type = 'SINGLE';
+            }
+          }
+          else if (tag_check === 'script') { //for later script handling
+            if ( ! peek) {
+              this.record_tag(tag_check);
+              this.tag_type = 'SCRIPT';
+            }
+          }
+          else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content)
+            if ( ! peek) {
+              this.record_tag(tag_check);
+              this.tag_type = 'STYLE';
+            }
+          }
+          else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
+            comment = this.get_unformatted('</'+tag_check+'>', tag_complete); //...delegate to get_unformatted function
+            content.push(comment);
+            // Preserve collapsed whitespace either before or after this tag.
+            if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){
+                content.splice(0, 0, this.input.charAt(tag_start - 1));
+            }
+            tag_end = this.pos - 1;
+            if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){
+                content.push(this.input.charAt(tag_end + 1));
+            }
+            this.tag_type = 'SINGLE';
+          }
+          else if (tag_check.charAt(0) === '!') { //peek for <!-- comment
+            if (tag_check.indexOf('[if') !== -1) { //peek for <!--[if conditional comment
+              if (tag_complete.indexOf('!IE') !== -1) { //this type needs a closing --> so...
+                comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted
+                content.push(comment);
+              }
+              if ( ! peek) {
+                this.tag_type = 'START';
+              }
+            }
+            else if (tag_check.indexOf('[endif') !== -1) {//peek for <!--[endif end conditional comment
+              this.tag_type = 'END';
+              this.unindent();
+            }
+            else if (tag_check.indexOf('[cdata[') !== -1) { //if it's a <[cdata[ comment...
+              comment = this.get_unformatted(']]>', tag_complete); //...delegate to get_unformatted function
+              content.push(comment);
+              if ( ! peek) {
+                this.tag_type = 'SINGLE'; //<![CDATA[ comments are treated like single tags
+              }
+            }
+            else {
+              comment = this.get_unformatted('-->', tag_complete);
+              content.push(comment);
+              this.tag_type = 'SINGLE';
+            }
+          }
+          else if ( ! peek) {
+            if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
+              this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
+              this.tag_type = 'END';
+            }
+            else { //otherwise it's a start-tag
+              this.record_tag(tag_check); //push it on the tag stack
+              this.tag_type = 'START';
+            }
+            if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
+              this.print_newline(true, this.output);
+            }
+          }
+
+          if (peek) {
+            this.pos = orig_pos;
+            this.line_char_count = orig_line_char_count;
+          }
+
+          return content.join(''); //returns fully formatted tag
+        };
+
+        this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety
+
+          if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
+            return '';
+          }
+          var input_char = '';
+          var content = '';
+          var space = true;
+          do {
+
+            if (this.pos >= this.input.length) {
+              return content;
+            }
+
+            input_char = this.input.charAt(this.pos);
+            this.pos++;
+
+            if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+              if (!space) {
+                this.line_char_count--;
+                continue;
+              }
+              if (input_char === '\n' || input_char === '\r') {
+                content += '\n';
+                /*  Don't change tab indention for unformatted blocks.  If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
+                for (var i=0; i<this.indent_level; i++) {
+                  content += this.indent_string;
+                }
+                space = false; //...and make sure other indentation is erased
+                */
+                this.line_char_count = 0;
+                continue;
+              }
+            }
+            content += input_char;
+            this.line_char_count++;
+            space = true;
+
+
+          } while (content.toLowerCase().indexOf(delimiter) === -1);
+          return content;
+        };
+
+        this.get_token = function () { //initial handler for token-retrieval
+          var token;
+
+          if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
+           var type = this.last_token.substr(7);
+           token = this.get_contents_to(type);
+            if (typeof token !== 'string') {
+              return token;
+            }
+            return [token, 'TK_' + type];
+          }
+          if (this.current_mode === 'CONTENT') {
+            token = this.get_content();
+            if (typeof token !== 'string') {
+              return token;
+            }
+            else {
+              return [token, 'TK_CONTENT'];
+            }
+          }
+
+          if (this.current_mode === 'TAG') {
+            token = this.get_tag();
+            if (typeof token !== 'string') {
+              return token;
+            }
+            else {
+              var tag_name_type = 'TK_TAG_' + this.tag_type;
+              return [token, tag_name_type];
+            }
+          }
+        };
+
+        this.get_full_indent = function (level) {
+          level = this.indent_level + level || 0;
+          if (level < 1) {
+            return '';
+          }
+
+          return Array(level + 1).join(this.indent_string);
+        };
+
+        this.is_unformatted = function(tag_check, unformatted) {
+            //is this an HTML5 block-level link?
+            if (!this.Utils.in_array(tag_check, unformatted)){
+                return false;
+            }
+
+            if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)){
+                return true;
+            }
+
+            //at this point we have an  tag; is its first child something we want to remain
+            //unformatted?
+            var next_tag = this.get_tag(true /* peek. */);
+
+            // tets next_tag to see if it is just html tag (no external content)
+            var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
+
+            // if next_tag comes back but is not an isolated tag, then
+            // let's treat the 'a' tag as having content
+            // and respect the unformatted option
+            if (!tag || this.Utils.in_array(tag, unformatted)){
+                return true;
+            } else {
+                return false;
+            }
+        };
+
+        this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions
+
+          this.input = js_source || ''; //gets the input for the Parser
+          this.output = [];
+          this.indent_character = indent_character;
+          this.indent_string = '';
+          this.indent_size = indent_size;
+          this.brace_style = brace_style;
+          this.indent_level = 0;
+          this.max_char = max_char;
+          this.line_char_count = 0; //count to see if max_char was exceeded
+
+          for (var i=0; i<this.indent_size; i++) {
+            this.indent_string += this.indent_character;
+          }
+
+          this.print_newline = function (ignore, arr) {
+            this.line_char_count = 0;
+            if (!arr || !arr.length) {
+              return;
+            }
+            if (!ignore) { //we might want the extra line
+              while (this.Utils.in_array(arr[arr.length-1], this.Utils.whitespace)) {
+                arr.pop();
+              }
+            }
+            arr.push('\n');
+            for (var i=0; i<this.indent_level; i++) {
+              arr.push(this.indent_string);
+            }
+          };
+
+          this.print_token = function (text) {
+            this.output.push(text);
+          };
+
+          this.indent = function () {
+            this.indent_level++;
+          };
+
+          this.unindent = function () {
+            if (this.indent_level > 0) {
+              this.indent_level--;
+            }
+          };
+        };
+        return this;
+      }
+
+      /*_____________________--------------------_____________________*/
+
+      multi_parser = new Parser(); //wrapping functions Parser
+      multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values
+
+      while (true) {
+          var t = multi_parser.get_token();
+          multi_parser.token_text = t[0];
+          multi_parser.token_type = t[1];
+
+        if (multi_parser.token_type === 'TK_EOF') {
+          break;
+        }
+
+        switch (multi_parser.token_type) {
+          case 'TK_TAG_START':
+            multi_parser.print_newline(false, multi_parser.output);
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.indent();
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_STYLE':
+          case 'TK_TAG_SCRIPT':
+            multi_parser.print_newline(false, multi_parser.output);
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_END':
+            //Print new line only if the tag has no content and has child
+            if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+                var tag_name = multi_parser.token_text.match(/\w+/)[0];
+                var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
+                if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) {
+                    multi_parser.print_newline(true, multi_parser.output);
+                }
+            }
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_SINGLE':
+            // Don't add a newline before elements that should remain unformatted.
+            var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
+            if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
+                multi_parser.print_newline(false, multi_parser.output);
+            }
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_CONTENT':
+            if (multi_parser.token_text !== '') {
+              multi_parser.print_token(multi_parser.token_text);
+            }
+            multi_parser.current_mode = 'TAG';
+            break;
+          case 'TK_STYLE':
+          case 'TK_SCRIPT':
+            if (multi_parser.token_text !== '') {
+              multi_parser.output.push('\n');
+              var text = multi_parser.token_text,
+                  _beautifier,
+                  script_indent_level = 1;
+              if (multi_parser.token_type === 'TK_SCRIPT') {
+                _beautifier = typeof js_beautify === 'function' && js_beautify;
+              } else if (multi_parser.token_type === 'TK_STYLE') {
+                _beautifier = typeof css_beautify === 'function' && css_beautify;
+              }
+
+              if (options.indent_scripts === "keep") {
+                script_indent_level = 0;
+              } else if (options.indent_scripts === "separate") {
+                script_indent_level = -multi_parser.indent_level;
+              }
+
+              var indentation = multi_parser.get_full_indent(script_indent_level);
+              if (_beautifier) {
+                // call the Beautifier if avaliable
+                text = _beautifier(text.replace(/^\s*/, indentation), options);
+              } else {
+                // simply indent the string otherwise
+                var white = text.match(/^\s*/)[0];
+                var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+                var reindent = multi_parser.get_full_indent(script_indent_level -_level);
+                text = text.replace(/^\s*/, indentation)
+                       .replace(/\r\n|\r|\n/g, '\n' + reindent)
+                       .replace(/\s*$/, '');
+              }
+              if (text) {
+                multi_parser.print_token(text);
+                multi_parser.print_newline(true, multi_parser.output);
+              }
+            }
+            multi_parser.current_mode = 'TAG';
+            break;
+        }
+        multi_parser.last_token = multi_parser.token_type;
+        multi_parser.last_text = multi_parser.token_text;
+      }
+      return multi_parser.output.join('');
+    }
+
+    // If we're running a web page and don't have either of the above, add our one global
+    window.html_beautify = function(html_source, options) {
+        return style_html(html_source, options, window.js_beautify, window.css_beautify);
+    };
+
+}());

+ 620 - 0
leiSP-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js

@@ -0,0 +1,620 @@
+/*!
+ * jQuery blockUI plugin
+ * Version 2.70.0-2014.11.23
+ * Requires jQuery v1.7 or later
+ *
+ * Examples at: http://malsup.com/jquery/block/
+ * Copyright (c) 2007-2013 M. Alsup
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
+ */
+
+;(function() {
+/*jshint eqeqeq:false curly:false latedef:false */
+"use strict";
+
+	function setup($) {
+		$.fn._fadeIn = $.fn.fadeIn;
+
+		var noOp = $.noop || function() {};
+
+		// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
+		// confusing userAgent strings on Vista)
+		var msie = /MSIE/.test(navigator.userAgent);
+		var ie6  = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
+		var mode = document.documentMode || 0;
+		var setExpr = $.isFunction( document.createElement('div').style.setExpression );
+
+		// global $ methods for blocking/unblocking the entire page
+		$.blockUI   = function(opts) { install(window, opts); };
+		$.unblockUI = function(opts) { remove(window, opts); };
+
+		// convenience method for quick growl-like notifications  (http://www.google.com/search?q=growl)
+		$.growlUI = function(title, message, timeout, onClose) {
+			var $m = $('<div class="growlUI"></div>');
+			if (title) $m.append('<h1>'+title+'</h1>');
+			if (message) $m.append('<h2>'+message+'</h2>');
+			if (timeout === undefined) timeout = 3000;
+
+			// Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications
+			var callBlock = function(opts) {
+				opts = opts || {};
+
+				$.blockUI({
+					message: $m,
+					fadeIn : typeof opts.fadeIn  !== 'undefined' ? opts.fadeIn  : 700,
+					fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000,
+					timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout,
+					centerY: false,
+					showOverlay: false,
+					onUnblock: onClose,
+					css: $.blockUI.defaults.growlCSS
+				});
+			};
+
+			callBlock();
+			var nonmousedOpacity = $m.css('opacity');
+			$m.mouseover(function() {
+				callBlock({
+					fadeIn: 0,
+					timeout: 30000
+				});
+
+				var displayBlock = $('.blockMsg');
+				displayBlock.stop(); // cancel fadeout if it has started
+				displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
+			}).mouseout(function() {
+				$('.blockMsg').fadeOut(1000);
+			});
+			// End konapun additions
+		};
+
+		// plugin method for blocking element content
+		$.fn.block = function(opts) {
+			if ( this[0] === window ) {
+				$.blockUI( opts );
+				return this;
+			}
+			var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
+			this.each(function() {
+				var $el = $(this);
+				if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
+					return;
+				$el.unblock({ fadeOut: 0 });
+			});
+
+			return this.each(function() {
+				if ($.css(this,'position') == 'static') {
+					this.style.position = 'relative';
+					$(this).data('blockUI.static', true);
+				}
+				this.style.zoom = 1; // force 'hasLayout' in ie
+				install(this, opts);
+			});
+		};
+
+		// plugin method for unblocking element content
+		$.fn.unblock = function(opts) {
+			if ( this[0] === window ) {
+				$.unblockUI( opts );
+				return this;
+			}
+			return this.each(function() {
+				remove(this, opts);
+			});
+		};
+
+		$.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
+
+		// override these in your code to change the default behavior and style
+		$.blockUI.defaults = {
+			// message displayed when blocking (use null for no message)
+			message:  '<div class="loaderbox"><div class="loading-activity"></div> 加载中......</div>',
+
+			title: null,		// title string; only used when theme == true
+			draggable: true,	// only used when theme == true (requires jquery-ui.js to be loaded)
+
+			theme: false, // set to true to use with jQuery UI themes
+
+			// styles for the message when blocking; if you wish to disable
+			// these and use an external stylesheet then do this in your code:
+			// $.blockUI.defaults.css = {};
+			css: {
+				padding:	0,
+				margin:		0,
+				width:		'30%',
+				top:		'40%',
+				left:		'35%',
+				textAlign:	'center',
+				color:		'#000',
+				border:		'0px',
+				backgroundColor:'transparent',
+				cursor:		'wait'
+			},
+
+			// minimal style set used when themes are used
+			themedCSS: {
+				width:	'30%',
+				top:	'40%',
+				left:	'35%'
+			},
+
+			// styles for the overlay
+			overlayCSS:  {
+				backgroundColor:	'#000',
+				opacity:			0.6,
+				cursor:				'wait'
+			},
+
+			// style to replace wait cursor before unblocking to correct issue
+			// of lingering wait cursor
+			cursorReset: 'default',
+
+			// styles applied when using $.growlUI
+			growlCSS: {
+				width:		'350px',
+				top:		'10px',
+				left:		'',
+				right:		'10px',
+				border:		'none',
+				padding:	'5px',
+				opacity:	0.6,
+				cursor:		'default',
+				color:		'#fff',
+				backgroundColor: '#000',
+				'-webkit-border-radius':'10px',
+				'-moz-border-radius':	'10px',
+				'border-radius':		'10px'
+			},
+
+			// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
+			// (hat tip to Jorge H. N. de Vasconcelos)
+			/*jshint scripturl:true */
+			iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
+
+			// force usage of iframe in non-IE browsers (handy for blocking applets)
+			forceIframe: false,
+
+			// z-index for the blocking overlay
+			baseZ: 1000,
+
+			// set these to true to have the message automatically centered
+			centerX: true, // <-- only effects element blocking (page block controlled via css above)
+			centerY: true,
+
+			// allow body element to be stetched in ie6; this makes blocking look better
+			// on "short" pages.  disable if you wish to prevent changes to the body height
+			allowBodyStretch: true,
+
+			// enable if you want key and mouse events to be disabled for content that is blocked
+			bindEvents: true,
+
+			// be default blockUI will supress tab navigation from leaving blocking content
+			// (if bindEvents is true)
+			constrainTabKey: true,
+
+			// fadeIn time in millis; set to 0 to disable fadeIn on block
+			fadeIn:  200,
+
+			// fadeOut time in millis; set to 0 to disable fadeOut on unblock
+			fadeOut:  400,
+
+			// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
+			timeout: 0,
+
+			// disable if you don't want to show the overlay
+			showOverlay: true,
+
+			// if true, focus will be placed in the first available input field when
+			// page blocking
+			focusInput: true,
+
+            // elements that can receive focus
+            focusableElements: ':input:enabled:visible',
+
+			// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
+			// no longer needed in 2012
+			// applyPlatformOpacityRules: true,
+
+			// callback method invoked when fadeIn has completed and blocking message is visible
+			onBlock: null,
+
+			// callback method invoked when unblocking has completed; the callback is
+			// passed the element that has been unblocked (which is the window object for page
+			// blocks) and the options that were passed to the unblock call:
+			//	onUnblock(element, options)
+			onUnblock: null,
+
+			// callback method invoked when the overlay area is clicked.
+			// setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
+			onOverlayClick: null,
+
+			// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
+			quirksmodeOffsetHack: 4,
+
+			// class name of the message block
+			blockMsgClass: 'blockMsg',
+
+			// if it is already blocked, then ignore it (don't unblock and reblock)
+			ignoreIfBlocked: false
+		};
+
+		// private data and functions follow...
+
+		var pageBlock = null;
+		var pageBlockEls = [];
+
+		function install(el, opts) {
+			var css, themedCSS;
+			var full = (el == window);
+			var msg = (opts && opts.message !== undefined ? opts.message : undefined);
+			opts = $.extend({}, $.blockUI.defaults, opts || {});
+
+			if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
+				return;
+
+			opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
+			css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
+			if (opts.onOverlayClick)
+				opts.overlayCSS.cursor = 'pointer';
+
+			themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
+			msg = msg === undefined ? opts.message : msg;
+
+			// remove the current block (if there is one)
+			if (full && pageBlock)
+				remove(window, {fadeOut:0});
+
+			// if an existing element is being used as the blocking content then we capture
+			// its current place in the DOM (and current display style) so we can restore
+			// it when we unblock
+			if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
+				var node = msg.jquery ? msg[0] : msg;
+				var data = {};
+				$(el).data('blockUI.history', data);
+				data.el = node;
+				data.parent = node.parentNode;
+				data.display = node.style.display;
+				data.position = node.style.position;
+				if (data.parent)
+					data.parent.removeChild(node);
+			}
+
+			$(el).data('blockUI.onUnblock', opts.onUnblock);
+			var z = opts.baseZ;
+
+			// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
+			// layer1 is the iframe layer which is used to supress bleed through of underlying content
+			// layer2 is the overlay layer which has opacity and a wait cursor (by default)
+			// layer3 is the message content that is displayed while blocking
+			var lyr1, lyr2, lyr3, s;
+			if (msie || opts.forceIframe)
+				lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
+			else
+				lyr1 = $('<div class="blockUI" style="display:none"></div>');
+
+			if (opts.theme)
+				lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
+			else
+				lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
+
+			if (opts.theme && full) {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
+				if ( opts.title ) {
+					s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+				}
+				s += '<div class="ui-widget-content ui-dialog-content"></div>';
+				s += '</div>';
+			}
+			else if (opts.theme) {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
+				if ( opts.title ) {
+					s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
+				}
+				s += '<div class="ui-widget-content ui-dialog-content"></div>';
+				s += '</div>';
+			}
+			else if (full) {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
+			}
+			else {
+				s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
+			}
+			lyr3 = $(s);
+
+			// if we have a message, style it
+			if (msg) {
+				if (opts.theme) {
+					lyr3.css(themedCSS);
+					lyr3.addClass('ui-widget-content');
+				}
+				else
+					lyr3.css(css);
+			}
+
+			// style the overlay
+			if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
+				lyr2.css(opts.overlayCSS);
+			lyr2.css('position', full ? 'fixed' : 'absolute');
+
+			// make iframe layer transparent in IE
+			if (msie || opts.forceIframe)
+				lyr1.css('opacity',0.0);
+
+			//$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
+			var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
+			$.each(layers, function() {
+				this.appendTo($par);
+			});
+
+			if (opts.theme && opts.draggable && $.fn.draggable) {
+				lyr3.draggable({
+					handle: '.ui-dialog-titlebar',
+					cancel: 'li'
+				});
+			}
+
+			// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
+			var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
+			if (ie6 || expr) {
+				// give body 100% height
+				if (full && opts.allowBodyStretch && $.support.boxModel)
+					$('html,body').css('height','100%');
+
+				// fix ie6 issue when blocked element has a border width
+				if ((ie6 || !$.support.boxModel) && !full) {
+					var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
+					var fixT = t ? '(0 - '+t+')' : 0;
+					var fixL = l ? '(0 - '+l+')' : 0;
+				}
+
+				// simulate fixed position
+				$.each(layers, function(i,o) {
+					var s = o[0].style;
+					s.position = 'absolute';
+					if (i < 2) {
+						if (full)
+							s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
+						else
+							s.setExpression('height','this.parentNode.offsetHeight + "px"');
+						if (full)
+							s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
+						else
+							s.setExpression('width','this.parentNode.offsetWidth + "px"');
+						if (fixL) s.setExpression('left', fixL);
+						if (fixT) s.setExpression('top', fixT);
+					}
+					else if (opts.centerY) {
+						if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
+						s.marginTop = 0;
+					}
+					else if (!opts.centerY && full) {
+						var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
+						var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
+						s.setExpression('top',expression);
+					}
+				});
+			}
+
+			// show the message
+			if (msg) {
+				if (opts.theme)
+					lyr3.find('.ui-widget-content').append(msg);
+				else
+					lyr3.append(msg);
+				if (msg.jquery || msg.nodeType)
+					$(msg).show();
+			}
+
+			if ((msie || opts.forceIframe) && opts.showOverlay)
+				lyr1.show(); // opacity is zero
+			if (opts.fadeIn) {
+				var cb = opts.onBlock ? opts.onBlock : noOp;
+				var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
+				var cb2 = msg ? cb : noOp;
+				if (opts.showOverlay)
+					lyr2._fadeIn(opts.fadeIn, cb1);
+				if (msg)
+					lyr3._fadeIn(opts.fadeIn, cb2);
+			}
+			else {
+				if (opts.showOverlay)
+					lyr2.show();
+				if (msg)
+					lyr3.show();
+				if (opts.onBlock)
+					opts.onBlock.bind(lyr3)();
+			}
+
+			// bind key and mouse events
+			bind(1, el, opts);
+
+			if (full) {
+				pageBlock = lyr3[0];
+				pageBlockEls = $(opts.focusableElements,pageBlock);
+				if (opts.focusInput)
+					setTimeout(focus, 20);
+			}
+			else
+				center(lyr3[0], opts.centerX, opts.centerY);
+
+			if (opts.timeout) {
+				// auto-unblock
+				var to = setTimeout(function() {
+					if (full)
+						$.unblockUI(opts);
+					else
+						$(el).unblock(opts);
+				}, opts.timeout);
+				$(el).data('blockUI.timeout', to);
+			}
+		}
+
+		// remove the block
+		function remove(el, opts) {
+			var count;
+			var full = (el == window);
+			var $el = $(el);
+			var data = $el.data('blockUI.history');
+			var to = $el.data('blockUI.timeout');
+			if (to) {
+				clearTimeout(to);
+				$el.removeData('blockUI.timeout');
+			}
+			opts = $.extend({}, $.blockUI.defaults, opts || {});
+			bind(0, el, opts); // unbind events
+
+			if (opts.onUnblock === null) {
+				opts.onUnblock = $el.data('blockUI.onUnblock');
+				$el.removeData('blockUI.onUnblock');
+			}
+
+			var els;
+			if (full) // crazy selector to handle odd field errors in ie6/7
+				els = $('body').children().filter('.blockUI').add('body > .blockUI');
+			else
+				els = $el.find('>.blockUI');
+
+			// fix cursor issue
+			if ( opts.cursorReset ) {
+				if ( els.length > 1 )
+					els[1].style.cursor = opts.cursorReset;
+				if ( els.length > 2 )
+					els[2].style.cursor = opts.cursorReset;
+			}
+
+			if (full)
+				pageBlock = pageBlockEls = null;
+
+			if (opts.fadeOut) {
+				count = els.length;
+				els.stop().fadeOut(opts.fadeOut, function() {
+					if ( --count === 0)
+						reset(els,data,opts,el);
+				});
+			}
+			else
+				reset(els, data, opts, el);
+		}
+
+		// move blocking element back into the DOM where it started
+		function reset(els,data,opts,el) {
+			var $el = $(el);
+			if ( $el.data('blockUI.isBlocked') )
+				return;
+
+			els.each(function(i,o) {
+				// remove via DOM calls so we don't lose event handlers
+				if (this.parentNode)
+					this.parentNode.removeChild(this);
+			});
+
+			if (data && data.el) {
+				data.el.style.display = data.display;
+				data.el.style.position = data.position;
+				data.el.style.cursor = 'default'; // #59
+				if (data.parent)
+					data.parent.appendChild(data.el);
+				$el.removeData('blockUI.history');
+			}
+
+			if ($el.data('blockUI.static')) {
+				$el.css('position', 'static'); // #22
+			}
+
+			if (typeof opts.onUnblock == 'function')
+				opts.onUnblock(el,opts);
+
+			// fix issue in Safari 6 where block artifacts remain until reflow
+			var body = $(document.body), w = body.width(), cssW = body[0].style.width;
+			body.width(w-1).width(w);
+			body[0].style.width = cssW;
+		}
+
+		// bind/unbind the handler
+		function bind(b, el, opts) {
+			var full = el == window, $el = $(el);
+
+			// don't bother unbinding if there is nothing to unbind
+			if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
+				return;
+
+			$el.data('blockUI.isBlocked', b);
+
+			// don't bind events when overlay is not in use or if bindEvents is false
+			if (!full || !opts.bindEvents || (b && !opts.showOverlay))
+				return;
+
+			// bind anchors and inputs for mouse and key events
+			var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
+			if (b)
+				$(document).bind(events, opts, handler);
+			else
+				$(document).unbind(events, handler);
+
+		// former impl...
+		//		var $e = $('a,:input');
+		//		b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
+		}
+
+		// event handler to suppress keyboard/mouse events when blocking
+		function handler(e) {
+			// allow tab navigation (conditionally)
+			if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) {
+				if (pageBlock && e.data.constrainTabKey) {
+					var els = pageBlockEls;
+					var fwd = !e.shiftKey && e.target === els[els.length-1];
+					var back = e.shiftKey && e.target === els[0];
+					if (fwd || back) {
+						setTimeout(function(){focus(back);},10);
+						return false;
+					}
+				}
+			}
+			var opts = e.data;
+			var target = $(e.target);
+			if (target.hasClass('blockOverlay') && opts.onOverlayClick)
+				opts.onOverlayClick(e);
+
+			// allow events within the message content
+			if (target.parents('div.' + opts.blockMsgClass).length > 0)
+				return true;
+
+			// allow events for content that is not being blocked
+			return target.parents().children().filter('div.blockUI').length === 0;
+		}
+
+		function focus(back) {
+			if (!pageBlockEls)
+				return;
+			var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
+			if (e)
+				e.focus();
+		}
+
+		function center(el, x, y) {
+			var p = el.parentNode, s = el.style;
+			var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
+			var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
+			if (x) s.left = l > 0 ? (l+'px') : '0';
+			if (y) s.top  = t > 0 ? (t+'px') : '0';
+		}
+
+		function sz(el, p) {
+			return parseInt($.css(el,p),10)||0;
+		}
+
+	}
+
+
+	/*global define:true */
+	if (typeof define === 'function' && define.amd && define.amd.jQuery) {
+		define(['jquery'], setup);
+	} else {
+		setup(jQuery);
+	}
+
+})();

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 550 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5697 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 12 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 10 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.min.js


BIN
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading-sm.gif


BIN
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/loading.gif


+ 429 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css

@@ -0,0 +1,429 @@
+/*!
+ * Bootstrap-select v1.13.10 (https://developer.snapappointments.com/bootstrap-select)
+ *
+ * Copyright 2012-2019 SnapAppointments, LLC
+ * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
+ */
+
+select.bs-select-hidden,
+.bootstrap-select > select.bs-select-hidden,
+select.selectpicker {
+  display: none !important;
+}
+.bootstrap-select {
+  width: 220px \0;
+  /*IE9 and below*/
+  vertical-align: middle;
+}
+.bootstrap-select > .dropdown-toggle {
+  position: relative;
+  width: 100%;
+  text-align: right;
+  white-space: nowrap;
+  display: -webkit-inline-box;
+  display: -webkit-inline-flex;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  -webkit-box-align: center;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: justify;
+  -webkit-justify-content: space-between;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+}
+.bootstrap-select > .dropdown-toggle:after {
+  margin-top: -1px;
+}
+.bootstrap-select > .dropdown-toggle.bs-placeholder,
+.bootstrap-select > .dropdown-toggle.bs-placeholder:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder:active {
+  color: #999;
+}
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active,
+.bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active {
+  color: rgba(255, 255, 255, 0.5);
+}
+.bootstrap-select > select {
+  position: absolute !important;
+  bottom: 0;
+  left: 50%;
+  display: block !important;
+  width: 0.5px !important;
+  height: 100% !important;
+  padding: 0 !important;
+  opacity: 0 !important;
+  border: none;
+  z-index: 0 !important;
+}
+.bootstrap-select > select.mobile-device {
+  top: 0;
+  left: 0;
+  display: block !important;
+  width: 100% !important;
+  z-index: 2 !important;
+}
+.has-error .bootstrap-select .dropdown-toggle,
+.error .bootstrap-select .dropdown-toggle,
+.bootstrap-select.is-invalid .dropdown-toggle,
+.was-validated .bootstrap-select .selectpicker:invalid + .dropdown-toggle {
+  border-color: #b94a48;
+}
+.bootstrap-select.is-valid .dropdown-toggle,
+.was-validated .bootstrap-select .selectpicker:valid + .dropdown-toggle {
+  border-color: #28a745;
+}
+.bootstrap-select.fit-width {
+  width: auto !important;
+}
+.bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) {
+  width: 220px;
+}
+.bootstrap-select > select.mobile-device:focus + .dropdown-toggle,
+.bootstrap-select .dropdown-toggle:focus {
+  outline: thin dotted #333333 !important;
+  outline: 5px auto -webkit-focus-ring-color !important;
+  outline-offset: -2px;
+}
+.bootstrap-select.form-control {
+  margin-bottom: 0;
+  padding: 0;
+  border: none;
+  height: auto;
+}
+:not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) {
+  width: 100%;
+}
+.bootstrap-select.form-control.input-group-btn {
+  float: none;
+  z-index: auto;
+}
+.form-inline .bootstrap-select,
+.form-inline .bootstrap-select.form-control:not([class*="col-"]) {
+  width: auto;
+}
+.bootstrap-select:not(.input-group-btn),
+.bootstrap-select[class*="col-"] {
+  float: none;
+  display: inline-block;
+  margin-left: 0;
+}
+.bootstrap-select.dropdown-menu-right,
+.bootstrap-select[class*="col-"].dropdown-menu-right,
+.row .bootstrap-select[class*="col-"].dropdown-menu-right {
+  float: right;
+}
+.form-inline .bootstrap-select,
+.form-horizontal .bootstrap-select,
+.form-group .bootstrap-select {
+  margin-bottom: 0;
+}
+.form-group-lg .bootstrap-select.form-control,
+.form-group-sm .bootstrap-select.form-control {
+  padding: 0;
+}
+.form-group-lg .bootstrap-select.form-control .dropdown-toggle,
+.form-group-sm .bootstrap-select.form-control .dropdown-toggle {
+  height: 100%;
+  font-size: inherit;
+  line-height: inherit;
+  border-radius: inherit;
+}
+.bootstrap-select.form-control-sm .dropdown-toggle,
+.bootstrap-select.form-control-lg .dropdown-toggle {
+  font-size: inherit;
+  line-height: inherit;
+  border-radius: inherit;
+}
+.bootstrap-select.form-control-sm .dropdown-toggle {
+  padding: 0.25rem 0.5rem;
+}
+.bootstrap-select.form-control-lg .dropdown-toggle {
+  padding: 0.5rem 1rem;
+}
+.form-inline .bootstrap-select .form-control {
+  width: 100%;
+}
+.bootstrap-select.disabled,
+.bootstrap-select > .disabled {
+  cursor: not-allowed;
+}
+.bootstrap-select.disabled:focus,
+.bootstrap-select > .disabled:focus {
+  outline: none !important;
+}
+.bootstrap-select.bs-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 0 !important;
+  padding: 0 !important;
+}
+.bootstrap-select.bs-container .dropdown-menu {
+  z-index: 1060;
+}
+.bootstrap-select .dropdown-toggle .filter-option {
+  position: static;
+  top: 0;
+  left: 0;
+  float: left;
+  height: 100%;
+  width: 100%;
+  text-align: left;
+  overflow: hidden;
+  -webkit-box-flex: 0;
+  -webkit-flex: 0 1 auto;
+      -ms-flex: 0 1 auto;
+          flex: 0 1 auto;
+}
+.bs3.bootstrap-select .dropdown-toggle .filter-option {
+  padding-right: inherit;
+}
+.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option {
+  position: absolute;
+  padding-top: inherit;
+  padding-bottom: inherit;
+  padding-left: inherit;
+  float: none;
+}
+.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner {
+  padding-right: inherit;
+}
+.bootstrap-select .dropdown-toggle .filter-option-inner-inner {
+  overflow: hidden;
+}
+.bootstrap-select .dropdown-toggle .filter-expand {
+  width: 0 !important;
+  float: left;
+  opacity: 0 !important;
+  overflow: hidden;
+}
+.bootstrap-select .dropdown-toggle .caret {
+  position: absolute;
+  top: 50%;
+  right: 12px;
+  margin-top: -2px;
+  vertical-align: middle;
+}
+.input-group .bootstrap-select.form-control .dropdown-toggle {
+  border-radius: inherit;
+}
+.bootstrap-select[class*="col-"] .dropdown-toggle {
+  width: 100%;
+}
+.bootstrap-select .dropdown-menu {
+  min-width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bootstrap-select .dropdown-menu > .inner:focus {
+  outline: none !important;
+}
+.bootstrap-select .dropdown-menu.inner {
+  position: static;
+  float: none;
+  border: 0;
+  padding: 0;
+  margin: 0;
+  border-radius: 0;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.bootstrap-select .dropdown-menu li {
+  position: relative;
+}
+.bootstrap-select .dropdown-menu li.active small {
+  color: rgba(255, 255, 255, 0.5) !important;
+}
+.bootstrap-select .dropdown-menu li.disabled a {
+  cursor: not-allowed;
+}
+.bootstrap-select .dropdown-menu li a {
+  cursor: pointer;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+}
+.bootstrap-select .dropdown-menu li a.opt {
+  position: relative;
+  padding-left: 2.25em;
+}
+.bootstrap-select .dropdown-menu li a span.check-mark {
+  display: none;
+}
+.bootstrap-select .dropdown-menu li a span.text {
+  display: inline-block;
+}
+.bootstrap-select .dropdown-menu li small {
+  padding-left: 0.5em;
+}
+.bootstrap-select .dropdown-menu .notify {
+  position: absolute;
+  bottom: 5px;
+  width: 96%;
+  margin: 0 2%;
+  min-height: 26px;
+  padding: 3px 5px;
+  background: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  pointer-events: none;
+  opacity: 0.9;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bootstrap-select .no-results {
+  padding: 3px;
+  background: #f5f5f5;
+  margin: 0 5px;
+  white-space: nowrap;
+}
+.bootstrap-select.fit-width .dropdown-toggle .filter-option {
+  position: static;
+  display: inline;
+  padding: 0;
+}
+.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,
+.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner {
+  display: inline;
+}
+.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before {
+  content: '\00a0';
+}
+.bootstrap-select.fit-width .dropdown-toggle .caret {
+  position: static;
+  top: auto;
+  margin-top: -1px;
+}
+.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark {
+  position: absolute;
+  display: inline-block;
+  right: 15px;
+  top: 5px;
+}
+.bootstrap-select.show-tick .dropdown-menu li a span.text {
+  margin-right: 34px;
+}
+.bootstrap-select .bs-ok-default:after {
+  content: '';
+  display: block;
+  width: 0.5em;
+  height: 1em;
+  border-style: solid;
+  border-width: 0 0.26em 0.26em 0;
+  -webkit-transform: rotate(45deg);
+      -ms-transform: rotate(45deg);
+       -o-transform: rotate(45deg);
+          transform: rotate(45deg);
+}
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle,
+.bootstrap-select.show-menu-arrow.show > .dropdown-toggle {
+  z-index: 1061;
+}
+.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before {
+  content: '';
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid rgba(204, 204, 204, 0.2);
+  position: absolute;
+  bottom: -4px;
+  left: 9px;
+  display: none;
+}
+.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after {
+  content: '';
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid white;
+  position: absolute;
+  bottom: -4px;
+  left: 10px;
+  display: none;
+}
+.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before {
+  bottom: auto;
+  top: -4px;
+  border-top: 7px solid rgba(204, 204, 204, 0.2);
+  border-bottom: 0;
+}
+.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after {
+  bottom: auto;
+  top: -4px;
+  border-top: 6px solid white;
+  border-bottom: 0;
+}
+.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before {
+  right: 12px;
+  left: auto;
+}
+.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after {
+  right: 13px;
+  left: auto;
+}
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before,
+.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before,
+.bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after,
+.bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after {
+  display: block;
+}
+.bs-searchbox,
+.bs-actionsbox,
+.bs-donebutton {
+  padding: 4px 8px;
+}
+.bs-actionsbox {
+  width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bs-actionsbox .btn-group button {
+  width: 50%;
+}
+.bs-donebutton {
+  float: left;
+  width: 100%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.bs-donebutton .btn-group button {
+  width: 100%;
+}
+.bs-searchbox + .bs-actionsbox {
+  padding: 0 8px 4px;
+}
+.bs-searchbox .form-control {
+  margin-bottom: 0;
+  width: 100%;
+  float: none;
+}
+/*# sourceMappingURL=bootstrap-select.css.map */

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 3139 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 6 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 9 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.min.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 9 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/bootstrap-table.min.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1544 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 10 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.min.js


+ 663 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.css

@@ -0,0 +1,663 @@
+/*! X-editable - v1.5.1 
+* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
+* http://github.com/vitalets/x-editable
+* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
+.editableform {
+    margin-bottom: 0; /* overwrites bootstrap margin */
+}
+
+.editableform .control-group {
+    margin-bottom: 0; /* overwrites bootstrap margin */
+    white-space: nowrap; /* prevent wrapping buttons on new line */
+    line-height: 20px; /* overwriting bootstrap line-height. See #133 */
+}
+
+/* 
+  BS3 width:1005 for inputs breaks editable form in popup 
+  See: https://github.com/vitalets/x-editable/issues/393
+*/
+.editableform .form-control {
+    width: auto;
+}
+
+.editable-buttons {
+   display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
+   vertical-align: top;
+   margin-left: 7px;
+   /* inline-block emulation for IE7*/
+   zoom: 1; 
+   *display: inline;
+}
+
+.editable-buttons.editable-buttons-bottom {
+   display: block; 
+   margin-top: 7px;
+   margin-left: 0;
+}
+
+.editable-input {
+    vertical-align: top; 
+    display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
+    width: auto; /* bootstrap-responsive has width: 100% that breakes layout */
+    white-space: normal; /* reset white-space decalred in parent*/
+   /* display-inline emulation for IE7*/
+   zoom: 1; 
+   *display: inline;   
+}
+
+.editable-buttons .editable-cancel {
+   margin-left: 7px; 
+}
+
+/*for jquery-ui buttons need set height to look more pretty*/
+.editable-buttons button.ui-button-icon-only {
+   height: 24px; 
+   width: 30px;
+}
+
+.editableform-loading {
+    background: url('loading.gif') center center no-repeat;  
+    height: 25px;
+    width: auto; 
+    min-width: 25px; 
+}
+
+.editable-inline .editableform-loading {
+    background-position: left 5px;      
+}
+
+ .editable-error-block {
+    max-width: 300px;
+    margin: 5px 0 0 0;
+    width: auto;
+    white-space: normal;
+}
+
+/*add padding for jquery ui*/
+.editable-error-block.ui-state-error {
+    padding: 3px;  
+}  
+
+.editable-error {
+   color: red;  
+}
+
+/* ---- For specific types ---- */
+
+.editableform .editable-date {
+    padding: 0; 
+    margin: 0;
+    float: left;
+}
+
+/* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */
+.editable-inline .add-on .icon-th {
+   margin-top: 3px;
+   margin-left: 1px; 
+}
+
+
+/* checklist vertical alignment */
+.editable-checklist label input[type="checkbox"], 
+.editable-checklist label span {
+    vertical-align: middle;
+    margin: 0;
+}
+
+.editable-checklist label {
+    white-space: nowrap; 
+}
+
+/* set exact width of textarea to fit buttons toolbar */
+.editable-wysihtml5 {
+    width: 566px; 
+    height: 250px; 
+}
+
+/* clear button shown as link in date inputs */
+.editable-clear {
+   clear: both;
+   font-size: 0.9em;
+   text-decoration: none;
+   text-align: right;
+}
+
+/* IOS-style clear button for text inputs */
+.editable-clear-x {
+   background: url('clear.png') center center no-repeat;
+   display: block;
+   width: 13px;    
+   height: 13px;
+   position: absolute;
+   opacity: 0.6;
+   z-index: 100;
+   
+   top: 50%;
+   right: 6px;
+   margin-top: -6px;
+   
+}
+
+.editable-clear-x:hover {
+   opacity: 1;
+}
+
+.editable-pre-wrapped {
+   white-space: pre-wrap;
+}
+.editable-container.editable-popup {
+    max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
+}  
+
+.editable-container.popover {
+    width: auto; /* without this rule popover does not stretch */
+}
+
+.editable-container.editable-inline {
+    display: inline-block; 
+    vertical-align: middle;
+    width: auto;
+    /* inline-block emulation for IE7*/
+    zoom: 1; 
+    *display: inline;    
+}
+
+.editable-container.ui-widget {
+   font-size: inherit;  /* jqueryui widget font 1.1em too big, overwrite it */
+   z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */
+}
+.editable-click, 
+a.editable-click, 
+a.editable-click:hover {
+    text-decoration: none;
+    border-bottom: dashed 1px #0088cc;
+}
+
+.editable-click.editable-disabled, 
+a.editable-click.editable-disabled, 
+a.editable-click.editable-disabled:hover {
+   color: #585858;  
+   cursor: default;
+   border-bottom: none;
+}
+
+.editable-empty, .editable-empty:hover, .editable-empty:focus{
+  font-style: italic; 
+  color: #DD1144;  
+  /* border-bottom: none; */
+  text-decoration: none;
+}
+
+.editable-unsaved {
+  font-weight: bold; 
+}
+
+.editable-unsaved:after {
+/*    content: '*'*/
+}
+
+.editable-bg-transition {
+  -webkit-transition: background-color 1400ms ease-out;
+  -moz-transition: background-color 1400ms ease-out;
+  -o-transition: background-color 1400ms ease-out;
+  -ms-transition: background-color 1400ms ease-out;
+  transition: background-color 1400ms ease-out;  
+}
+
+/*see https://github.com/vitalets/x-editable/issues/139 */
+.form-horizontal .editable
+{ 
+    padding-top: 5px;
+    display:inline-block;
+}
+
+
+/*!
+ * Datepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.datepicker {
+  padding: 4px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  direction: ltr;
+  /*.dow {
+		border-top: 1px solid #ddd !important;
+	}*/
+
+}
+.datepicker-inline {
+  width: 220px;
+}
+.datepicker.datepicker-rtl {
+  direction: rtl;
+}
+.datepicker.datepicker-rtl table tr td span {
+  float: right;
+}
+.datepicker-dropdown {
+  top: 0;
+  left: 0;
+}
+.datepicker-dropdown:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+  top: -7px;
+  left: 6px;
+}
+.datepicker-dropdown:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  position: absolute;
+  top: -6px;
+  left: 7px;
+}
+.datepicker > div {
+  display: none;
+}
+.datepicker.days div.datepicker-days {
+  display: block;
+}
+.datepicker.months div.datepicker-months {
+  display: block;
+}
+.datepicker.years div.datepicker-years {
+  display: block;
+}
+.datepicker table {
+  margin: 0;
+}
+.datepicker td,
+.datepicker th {
+  text-align: center;
+  width: 20px;
+  height: 20px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  border: none;
+}
+.table-striped .datepicker table tr td,
+.table-striped .datepicker table tr th {
+  background-color: transparent;
+}
+.datepicker table tr td.day:hover {
+  background: #eeeeee;
+  cursor: pointer;
+}
+.datepicker table tr td.old,
+.datepicker table tr td.new {
+  color: #999999;
+}
+.datepicker table tr td.disabled,
+.datepicker table tr td.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td.today,
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today.disabled:hover {
+  background-color: #fde19a;
+  background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
+  background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: linear-gradient(top, #fdd49a, #fdf59a);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
+  border-color: #fdf59a #fdf59a #fbed50;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #000;
+}
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today:hover:hover,
+.datepicker table tr td.today.disabled:hover,
+.datepicker table tr td.today.disabled:hover:hover,
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today:hover.disabled,
+.datepicker table tr td.today.disabled.disabled,
+.datepicker table tr td.today.disabled:hover.disabled,
+.datepicker table tr td.today[disabled],
+.datepicker table tr td.today:hover[disabled],
+.datepicker table tr td.today.disabled[disabled],
+.datepicker table tr td.today.disabled:hover[disabled] {
+  background-color: #fdf59a;
+}
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active {
+  background-color: #fbf069 \9;
+}
+.datepicker table tr td.today:hover:hover {
+  color: #000;
+}
+.datepicker table tr td.today.active:hover {
+  color: #fff;
+}
+.datepicker table tr td.range,
+.datepicker table tr td.range:hover,
+.datepicker table tr td.range.disabled,
+.datepicker table tr td.range.disabled:hover {
+  background: #eeeeee;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today,
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today.disabled:hover {
+  background-color: #f3d17a;
+  background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
+  background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: linear-gradient(top, #f3c17a, #f3e97a);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
+  border-color: #f3e97a #f3e97a #edde34;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today:hover:hover,
+.datepicker table tr td.range.today.disabled:hover,
+.datepicker table tr td.range.today.disabled:hover:hover,
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today:hover.disabled,
+.datepicker table tr td.range.today.disabled.disabled,
+.datepicker table tr td.range.today.disabled:hover.disabled,
+.datepicker table tr td.range.today[disabled],
+.datepicker table tr td.range.today:hover[disabled],
+.datepicker table tr td.range.today.disabled[disabled],
+.datepicker table tr td.range.today.disabled:hover[disabled] {
+  background-color: #f3e97a;
+}
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active {
+  background-color: #efe24b \9;
+}
+.datepicker table tr td.selected,
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected.disabled:hover {
+  background-color: #9e9e9e;
+  background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
+  background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -o-linear-gradient(top, #b3b3b3, #808080);
+  background-image: linear-gradient(top, #b3b3b3, #808080);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
+  border-color: #808080 #808080 #595959;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected:hover:hover,
+.datepicker table tr td.selected.disabled:hover,
+.datepicker table tr td.selected.disabled:hover:hover,
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected:hover.disabled,
+.datepicker table tr td.selected.disabled.disabled,
+.datepicker table tr td.selected.disabled:hover.disabled,
+.datepicker table tr td.selected[disabled],
+.datepicker table tr td.selected:hover[disabled],
+.datepicker table tr td.selected.disabled[disabled],
+.datepicker table tr td.selected.disabled:hover[disabled] {
+  background-color: #808080;
+}
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active {
+  background-color: #666666 \9;
+}
+.datepicker table tr td.active,
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active.disabled:hover {
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(top, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active:hover:hover,
+.datepicker table tr td.active.disabled:hover,
+.datepicker table tr td.active.disabled:hover:hover,
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active:hover.disabled,
+.datepicker table tr td.active.disabled.disabled,
+.datepicker table tr td.active.disabled:hover.disabled,
+.datepicker table tr td.active[disabled],
+.datepicker table tr td.active:hover[disabled],
+.datepicker table tr td.active.disabled[disabled],
+.datepicker table tr td.active.disabled:hover[disabled] {
+  background-color: #0044cc;
+}
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active {
+  background-color: #003399 \9;
+}
+.datepicker table tr td span {
+  display: block;
+  width: 23%;
+  height: 54px;
+  line-height: 54px;
+  float: left;
+  margin: 1%;
+  cursor: pointer;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.datepicker table tr td span:hover {
+  background: #eeeeee;
+}
+.datepicker table tr td span.disabled,
+.datepicker table tr td span.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td span.active,
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active.disabled:hover {
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(top, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active:hover:hover,
+.datepicker table tr td span.active.disabled:hover,
+.datepicker table tr td span.active.disabled:hover:hover,
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active:hover.disabled,
+.datepicker table tr td span.active.disabled.disabled,
+.datepicker table tr td span.active.disabled:hover.disabled,
+.datepicker table tr td span.active[disabled],
+.datepicker table tr td span.active:hover[disabled],
+.datepicker table tr td span.active.disabled[disabled],
+.datepicker table tr td span.active.disabled:hover[disabled] {
+  background-color: #0044cc;
+}
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active {
+  background-color: #003399 \9;
+}
+.datepicker table tr td span.old,
+.datepicker table tr td span.new {
+  color: #999999;
+}
+.datepicker th.datepicker-switch {
+  width: 145px;
+}
+.datepicker thead tr:first-child th,
+.datepicker tfoot tr th {
+  cursor: pointer;
+}
+.datepicker thead tr:first-child th:hover,
+.datepicker tfoot tr th:hover {
+  background: #eeeeee;
+}
+.datepicker .cw {
+  font-size: 10px;
+  width: 12px;
+  padding: 0 2px 0 5px;
+  vertical-align: middle;
+}
+.datepicker thead tr:first-child th.cw {
+  cursor: default;
+  background-color: transparent;
+}
+.input-append.date .add-on i,
+.input-prepend.date .add-on i {
+  display: block;
+  cursor: pointer;
+  width: 16px;
+  height: 16px;
+}
+.input-daterange input {
+  text-align: center;
+}
+.input-daterange input:first-child {
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.input-daterange input:last-child {
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.input-daterange .add-on {
+  display: inline-block;
+  width: auto;
+  min-width: 16px;
+  height: 18px;
+  padding: 4px 5px;
+  font-weight: normal;
+  line-height: 18px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  vertical-align: middle;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+  margin-left: -5px;
+  margin-right: -5px;
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 7 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.min.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2462 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 10 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.min.js


BIN
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/clear.png


BIN
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/loading.gif


+ 119 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/bootstrap-table-export.js

@@ -0,0 +1,119 @@
+/**
+ * @author zhixin wen <wenzhixin2010@gmail.com>
+ * extensions: https://github.com/kayalshri/tableExport.jquery.plugin
+ */
+
+(function ($) {
+    'use strict';
+    var sprintf = $.fn.bootstrapTable.utils.sprintf;
+
+    var TYPE_NAME = {
+        csv: 'CSV',
+        txt: 'TXT',
+        doc: 'Word',
+        excel: 'Excel'
+    };
+
+    $.extend($.fn.bootstrapTable.defaults, {
+        showExport: false,
+        exportDataType: 'all', // basic, all, selected
+        exportTypes: ['csv', 'txt', 'doc', 'excel'],
+        exportOptions: {
+        	ignoreColumn: [0]  //忽略列索引
+        }
+    });
+
+    $.extend($.fn.bootstrapTable.defaults.icons, {
+        export: 'glyphicon glyphicon-save'
+    });
+
+    $.extend($.fn.bootstrapTable.locales, {
+        formatExport: function () {
+            return '导出';
+        }
+    });
+    $.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales);
+
+    var BootstrapTable = $.fn.bootstrapTable.Constructor,
+        _initToolbar = BootstrapTable.prototype.initToolbar;
+
+    BootstrapTable.prototype.initToolbar = function () {
+        this.showToolbar = this.options.showExport;
+
+        _initToolbar.apply(this, Array.prototype.slice.apply(arguments));
+
+        if (this.options.showExport) {
+            var that = this,
+                $btnGroup = this.$toolbar.find('>.btn-group'),
+                $export = $btnGroup.find('div.export');
+
+            if (!$export.length) {
+                $export = $([
+                    '<div class="export btn-group">',
+                        '<button class="btn' +
+                            sprintf(' btn-%s', this.options.buttonsClass) +
+                            sprintf(' btn-%s', this.options.iconSize) +
+                            ' dropdown-toggle" ' +
+                            'title="' + this.options.formatExport() + '" ' +
+                            'data-toggle="dropdown" type="button">',
+                            sprintf('<i class="%s %s"></i> ', this.options.iconsPrefix, this.options.icons.export),
+                            '<span class="caret"></span>',
+                        '</button>',
+                        '<ul class="dropdown-menu" role="menu">',
+                        '</ul>',
+                    '</div>'].join('')).appendTo($btnGroup);
+
+                var $menu = $export.find('.dropdown-menu'),
+                    exportTypes = this.options.exportTypes;
+
+                if (typeof this.options.exportTypes === 'string') {
+                    var types = this.options.exportTypes.slice(1, -1).replace(/ /g, '').split(',');
+
+                    exportTypes = [];
+                    $.each(types, function (i, value) {
+                        exportTypes.push(value.slice(1, -1));
+                    });
+                }
+                $.each(exportTypes, function (i, type) {
+                    if (TYPE_NAME.hasOwnProperty(type)) {
+                        $menu.append(['<li data-type="' + type + '">',
+                                '<a href="javascript:void(0)">',
+                                    TYPE_NAME[type],
+                                '</a>',
+                            '</li>'].join(''));
+                    }
+                });
+
+                $menu.find('li').click(function () {
+                    var type = $(this).data('type'),
+                        doExport = function () {
+                            that.$el.tableExport($.extend({}, that.options.exportOptions, {
+                                type: type,
+                                escape: false
+                            }));
+                        };
+
+                    if (that.options.exportDataType === 'all' && that.options.pagination) {
+                        that.$el.one(that.options.sidePagination === 'server' ? 'post-body.bs.table' : 'page-change.bs.table', function () {
+                            doExport();
+                            that.togglePagination();
+                        });
+                        that.togglePagination();
+                    } else if (that.options.exportDataType === 'selected') {
+                        //修改sidePagination属性为server无法导出选中数据
+                    	var trs = that.$body.children(); 
+                    	for (var i = 0; i < trs.length; i++) {
+                    	    var $this = $(trs[i]);
+                    	    if(!$this.find(sprintf('[name="%s"]',that.options.selectItemName)).prop('checked')){
+                    	      $this['hide']();
+                    	 }}
+                    	doExport();
+                    	that.getRowsHidden(true);
+                    } else {
+                        doExport();
+                    }
+                });
+            }
+        }
+    };
+})(jQuery);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2257 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/tableExport.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1239 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js


+ 117 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder/bootstrap-table-reorder.js

@@ -0,0 +1,117 @@
+/**
+ * @author: Dennis Hernández
+ * 实现表格拖拽功能
+ * @version: v1.0.1
+ */
+(function ($) {
+
+    'use strict';
+
+    var isSearch = false;
+
+    var rowAttr = function (row, index) {
+        return {
+            id: 'customId_' + index
+        };
+    };
+
+    $.extend($.fn.bootstrapTable.defaults, {
+        reorderableRows: false,
+        onDragStyle: null,
+        onDropStyle: null,
+        onDragClass: "reorder_rows_onDragClass",
+        dragHandle: null,
+        useRowAttrFunc: false,
+        onReorderRowsDrag: function (table, row) {
+            return false;
+        },
+        onReorderRowsDrop: function (table, row) {
+            return false;
+        },
+        onReorderRow: function (newData) {
+             return false;
+        }
+    });
+
+    $.extend($.fn.bootstrapTable.Constructor.EVENTS, {
+        'reorder-row.bs.table': 'onReorderRow'
+    });
+
+    var BootstrapTable = $.fn.bootstrapTable.Constructor,
+        _init = BootstrapTable.prototype.init,
+        _initSearch = BootstrapTable.prototype.initSearch;
+
+    BootstrapTable.prototype.init = function () {
+
+        if (!this.options.reorderableRows) {
+            _init.apply(this, Array.prototype.slice.apply(arguments));
+            return;
+        }
+
+        var that = this;
+        if (this.options.useRowAttrFunc) {
+            this.options.rowAttributes = rowAttr;
+        }
+
+        var onPostBody = this.options.onPostBody;
+        this.options.onPostBody = function () {
+            setTimeout(function () {
+                that.makeRowsReorderable();
+                onPostBody.apply();
+            }, 1);
+        };
+
+        _init.apply(this, Array.prototype.slice.apply(arguments));
+    };
+
+    BootstrapTable.prototype.initSearch = function () {
+        _initSearch.apply(this, Array.prototype.slice.apply(arguments));
+
+        if (!this.options.reorderableRows) {
+            return;
+        }
+
+        //Known issue after search if you reorder the rows the data is not display properly
+        //isSearch = true;
+    };
+
+    BootstrapTable.prototype.makeRowsReorderable = function () {
+        if (this.options.cardView) {
+            return;
+        }
+
+        var that = this;
+        this.$el.tableDnD({
+            onDragStyle: that.options.onDragStyle,
+            onDropStyle: that.options.onDropStyle,
+            onDragClass: that.options.onDragClass,
+            onDrop: that.onDrop,
+            onDragStart: that.options.onReorderRowsDrag,
+            dragHandle: that.options.dragHandle
+        });
+    };
+
+    BootstrapTable.prototype.onDrop = function (table, droppedRow) {
+        var tableBs = $(table),
+            tableBsData = tableBs.data('bootstrap.table'),
+            tableBsOptions = tableBs.data('bootstrap.table').options,
+            row = null,
+            newData = [];
+
+        for (var i = 0; i < table.tBodies[0].rows.length; i++) {
+            row = $(table.tBodies[0].rows[i]);
+            newData.push(tableBsOptions.data[row.data('index')]);
+            row.data('index', i).attr('data-index', i);
+        }
+
+        tableBsOptions.data = tableBsOptions.data.slice(0, tableBsData.pageFrom - 1)
+            .concat(newData)
+            .concat(tableBsOptions.data.slice(tableBsData.pageTo));
+
+        //Call the user defined function
+        tableBsOptions.onReorderRowsDrop.apply(table, [table, droppedRow]);
+
+        //Call the event reorder-row
+        tableBsData.trigger('reorder-row', newData);
+    };
+})(jQuery);

+ 598 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder/jquery.tablednd.js

@@ -0,0 +1,598 @@
+/**
+ * TableDnD plug-in for JQuery, allows you to drag and drop table rows
+ * You can set up various options to control how the system will work
+ * Copyright (c) Denis Howlett <denish@isocra.com>
+ * License: MIT.
+ * See https://github.com/isocra/TableDnD
+ */
+!function ($, window, document, undefined) {
+
+var startEvent = 'touchstart mousedown',
+    moveEvent  = 'touchmove mousemove',
+    endEvent   = 'touchend mouseup';
+
+$(document).ready(function () {
+    function parseStyle(css) {
+        var objMap = {},
+            parts = css.match(/([^;:]+)/g) || [];
+        while (parts.length)
+            objMap[parts.shift()] = parts.shift().trim();
+
+        return objMap;
+    }
+    $('table').each(function () {
+        if ($(this).data('table') === 'dnd') {
+
+            $(this).tableDnD({
+                onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null,
+                onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null,
+                onDragClass: $(this).data('ondragclass') === undefined && "tDnD_whileDrag" || $(this).data('ondragclass'),
+                onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null,
+                onDragStart: $(this).data('ondragstart') && new Function('table', 'row' ,$(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null,
+                onDragStop: $(this).data('ondragstop') && new Function('table', 'row' ,$(this).data('ondragstop')),
+                scrollAmount: $(this).data('scrollamount') || 5,
+                sensitivity: $(this).data('sensitivity') || 10,
+                hierarchyLevel: $(this).data('hierarchylevel') || 0,
+                indentArtifact: $(this).data('indentartifact') || '<div class="indent">&nbsp;</div>',
+                autoWidthAdjust: $(this).data('autowidthadjust') || true,
+                autoCleanRelations: $(this).data('autocleanrelations') || true,
+                jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t',
+                serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/,
+                serializeParamName: $(this).data('serializeparamname') || false,
+                dragHandle: $(this).data('draghandle') || null
+            });
+        }
+
+
+    });
+});
+
+jQuery.tableDnD = {
+    /** Keep hold of the current table being dragged */
+    currentTable: null,
+    /** Keep hold of the current drag object if any */
+    dragObject: null,
+    /** The current mouse offset */
+    mouseOffset: null,
+    /** Remember the old value of X and Y so that we don't do too much processing */
+    oldX: 0,
+    oldY: 0,
+
+    /** Actually build the structure */
+    build: function(options) {
+        // Set up the defaults if any
+
+        this.each(function() {
+            // This is bound to each matching table, set up the defaults and override with user options
+            this.tableDnDConfig = $.extend({
+                onDragStyle: null,
+                onDropStyle: null,
+                // Add in the default class for whileDragging
+                onDragClass: "tDnD_whileDrag",
+                onDrop: null,
+                onDragStart: null,
+                onDragStop: null,
+                scrollAmount: 5,
+                /** Sensitivity setting will throttle the trigger rate for movement detection */
+                sensitivity: 10,
+                /** Hierarchy level to support parent child. 0 switches this functionality off */
+                hierarchyLevel: 0,
+                /** The html artifact to prepend the first cell with as indentation */
+                indentArtifact: '<div class="indent">&nbsp;</div>',
+                /** Automatically adjust width of first cell */
+                autoWidthAdjust: true,
+                /** Automatic clean-up to ensure relationship integrity */
+                autoCleanRelations: true,
+                /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */
+                jsonPretifySeparator: '\t',
+                /** The regular expression to use to trim row IDs */
+                serializeRegexp: /[^\-]*$/,
+                /** If you want to specify another parameter name instead of the table ID */
+                serializeParamName: false,
+                /** If you give the name of a class here, then only Cells with this class will be draggable */
+                dragHandle: null
+            }, options || {});
+
+            // Now make the rows draggable
+            $.tableDnD.makeDraggable(this);
+            // Prepare hierarchy support
+            this.tableDnDConfig.hierarchyLevel
+                && $.tableDnD.makeIndented(this);
+        });
+
+        // Don't break the chain
+        return this;
+    },
+    makeIndented: function (table) {
+        var config = table.tableDnDConfig,
+            rows = table.rows,
+            firstCell = $(rows).first().find('td:first')[0],
+            indentLevel = 0,
+            cellWidth = 0,
+            longestCell,
+            tableStyle;
+
+        if ($(table).hasClass('indtd'))
+            return null;
+
+        tableStyle = $(table).addClass('indtd').attr('style');
+        $(table).css({whiteSpace: "nowrap"});
+
+        for (var w = 0; w < rows.length; w++) {
+            if (cellWidth < $(rows[w]).find('td:first').text().length) {
+                cellWidth = $(rows[w]).find('td:first').text().length;
+                longestCell = w;
+            }
+        }
+        $(firstCell).css({width: 'auto'});
+        for (w = 0; w < config.hierarchyLevel; w++)
+            $(rows[longestCell]).find('td:first').prepend(config.indentArtifact);
+        firstCell && $(firstCell).css({width: firstCell.offsetWidth});
+        tableStyle && $(table).css(tableStyle);
+
+        for (w = 0; w < config.hierarchyLevel; w++)
+            $(rows[longestCell]).find('td:first').children(':first').remove();
+
+        config.hierarchyLevel
+            && $(rows).each(function () {
+                indentLevel = $(this).data('level') || 0;
+                indentLevel <= config.hierarchyLevel
+                    && $(this).data('level', indentLevel)
+                    || $(this).data('level', 0);
+                for (var i = 0; i < $(this).data('level'); i++)
+                    $(this).find('td:first').prepend(config.indentArtifact);
+            });
+
+        return this;
+    },
+    /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
+    makeDraggable: function(table) {
+        var config = table.tableDnDConfig;
+
+        config.dragHandle
+            // We only need to add the event to the specified cells
+            && $(config.dragHandle, table).each(function() {
+                // The cell is bound to "this"
+                $(this).bind(startEvent, function(e) {
+                    $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config);
+                    return false;
+                });
+            })
+            // For backwards compatibility, we add the event to the whole row
+            // get all the rows as a wrapped set
+            || $(table.rows).each(function() {
+                // Iterate through each row, the row is bound to "this"
+                if (! $(this).hasClass("nodrag")) {
+                    $(this).bind(startEvent, function(e) {
+                        if (e.target.tagName === "TD" && event.target.className !== "nodrag") {
+                            $.tableDnD.initialiseDrag(this, table, this, e, config);
+                            return false;
+                        }
+                    }).css("cursor", "move"); // Store the tableDnD object
+                } else {
+                    $(this).css("cursor", ""); // Remove the cursor if we don't have the nodrag class
+                }
+            });
+    },
+    currentOrder: function() {
+        var rows = this.currentTable.rows;
+        return $.map(rows, function (val) {
+            return ($(val).data('level') + val.id).replace(/\s/g, '');
+        }).join('');
+    },
+    initialiseDrag: function(dragObject, table, target, e, config) {
+        this.dragObject    = dragObject;
+        this.currentTable  = table;
+        this.mouseOffset   = this.getMouseOffset(target, e);
+        this.originalOrder = this.currentOrder();
+
+        // Now we need to capture the mouse up and mouse move event
+        // We can use bind so that we don't interfere with other event handlers
+        $(document)
+            .bind(moveEvent, this.mousemove)
+            .bind(endEvent, this.mouseup);
+
+        // Call the onDragStart method if there is one
+        config.onDragStart
+            && config.onDragStart(table, target);
+    },
+    updateTables: function() {
+        this.each(function() {
+            // this is now bound to each matching table
+            if (this.tableDnDConfig)
+                $.tableDnD.makeDraggable(this);
+        });
+    },
+    /** Get the mouse coordinates from the event (allowing for browser differences) */
+    mouseCoords: function(e) {
+        if (e.originalEvent.changedTouches)
+            return {
+                x: e.originalEvent.changedTouches[0].clientX,
+                y: e.originalEvent.changedTouches[0].clientY
+            };
+
+        if(e.pageX || e.pageY)
+            return {
+                x: e.pageX,
+                y: e.pageY
+            };
+
+        return {
+            x: e.clientX + document.body.scrollLeft - document.body.clientLeft,
+            y: e.clientY + document.body.scrollTop  - document.body.clientTop
+        };
+    },
+    /** Given a target element and a mouse eent, get the mouse offset from that element.
+     To do this we need the element's position and the mouse position */
+    getMouseOffset: function(target, e) {
+        var mousePos,
+            docPos;
+
+        e = e || window.event;
+
+        docPos    = this.getPosition(target);
+        mousePos  = this.mouseCoords(e);
+
+        return {
+            x: mousePos.x - docPos.x,
+            y: mousePos.y - docPos.y
+        };
+    },
+    /** Get the position of an element by going up the DOM tree and adding up all the offsets */
+    getPosition: function(element) {
+        var left = 0,
+            top  = 0;
+
+        // Safari fix -- thanks to Luis Chato for this!
+        // Safari 2 doesn't correctly grab the offsetTop of a table row
+        // this is detailed here:
+        // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
+        // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
+        // note that firefox will return a text node as a first child, so designing a more thorough
+        // solution may need to take that into account, for now this seems to work in firefox, safari, ie
+        if (element.offsetHeight === 0)
+            element = element.firstChild; // a table cell
+
+        while (element.offsetParent) {
+            left   += element.offsetLeft;
+            top    += element.offsetTop;
+            element = element.offsetParent;
+        }
+
+        left += element.offsetLeft;
+        top  += element.offsetTop;
+
+        return {
+            x: left,
+            y: top
+        };
+    },
+    autoScroll: function (mousePos) {
+      var config       = this.currentTable.tableDnDConfig,
+          yOffset      = window.pageYOffset,
+          windowHeight = window.innerHeight
+            ? window.innerHeight
+            : document.documentElement.clientHeight
+            ? document.documentElement.clientHeight
+            : document.body.clientHeight;
+
+        // Windows version
+        // yOffset=document.body.scrollTop;
+        if (document.all)
+            if (typeof document.compatMode !== 'undefined'
+                && document.compatMode !== 'BackCompat')
+                yOffset = document.documentElement.scrollTop;
+            else if (typeof document.body !== 'undefined')
+                yOffset = document.body.scrollTop;
+
+        mousePos.y - yOffset < config.scrollAmount
+            && window.scrollBy(0, - config.scrollAmount)
+        || windowHeight - (mousePos.y - yOffset) < config.scrollAmount
+            && window.scrollBy(0, config.scrollAmount);
+
+    },
+    moveVerticle: function (moving, currentRow) {
+
+        if (0 !== moving.vertical
+            // If we're over a row then move the dragged row to there so that the user sees the
+            // effect dynamically
+            && currentRow
+            && this.dragObject !== currentRow
+            && this.dragObject.parentNode === currentRow.parentNode)
+            0 > moving.vertical
+                && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling)
+            || 0 < moving.vertical
+                && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow);
+
+    },
+    moveHorizontal: function (moving, currentRow) {
+        var config       = this.currentTable.tableDnDConfig,
+            currentLevel;
+
+        if (!config.hierarchyLevel
+            || 0 === moving.horizontal
+            // We only care if moving left or right on the current row
+            || !currentRow
+            || this.dragObject !== currentRow)
+                return null;
+
+            currentLevel = $(currentRow).data('level');
+
+            0 < moving.horizontal
+                && currentLevel > 0
+                && $(currentRow).find('td:first').children(':first').remove()
+                && $(currentRow).data('level', --currentLevel);
+
+            0 > moving.horizontal
+                && currentLevel < config.hierarchyLevel
+                && $(currentRow).prev().data('level') >= currentLevel
+                && $(currentRow).children(':first').prepend(config.indentArtifact)
+                && $(currentRow).data('level', ++currentLevel);
+
+    },
+    mousemove: function(e) {
+        var dragObj      = $($.tableDnD.dragObject),
+            config       = $.tableDnD.currentTable.tableDnDConfig,
+            currentRow,
+            mousePos,
+            moving,
+            x,
+            y;
+
+        e && e.preventDefault();
+
+        if (!$.tableDnD.dragObject)
+            return false;
+
+        // prevent touch device screen scrolling
+        e.type === 'touchmove'
+            && event.preventDefault(); // TODO verify this is event and not really e
+
+        // update the style to show we're dragging
+        config.onDragClass
+            && dragObj.addClass(config.onDragClass)
+            || dragObj.css(config.onDragStyle);
+
+        mousePos = $.tableDnD.mouseCoords(e);
+        x = mousePos.x - $.tableDnD.mouseOffset.x;
+        y = mousePos.y - $.tableDnD.mouseOffset.y;
+
+        // auto scroll the window
+        $.tableDnD.autoScroll(mousePos);
+
+        currentRow = $.tableDnD.findDropTargetRow(dragObj, y);
+        moving = $.tableDnD.findDragDirection(x, y);
+
+        $.tableDnD.moveVerticle(moving, currentRow);
+        $.tableDnD.moveHorizontal(moving, currentRow);
+
+        return false;
+    },
+    findDragDirection: function (x,y) {
+        var sensitivity = this.currentTable.tableDnDConfig.sensitivity,
+            oldX        = this.oldX,
+            oldY        = this.oldY,
+            xMin        = oldX - sensitivity,
+            xMax        = oldX + sensitivity,
+            yMin        = oldY - sensitivity,
+            yMax        = oldY + sensitivity,
+            moving      = {
+                horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1,
+                vertical  : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1
+            };
+
+        // update the old value
+        if (moving.horizontal !== 0)
+            this.oldX    = x;
+        if (moving.vertical   !== 0)
+            this.oldY    = y;
+
+        return moving;
+    },
+    /** We're only worried about the y position really, because we can only move rows up and down */
+    findDropTargetRow: function(draggedRow, y) {
+        var rowHeight = 0,
+            rows      = this.currentTable.rows,
+            config    = this.currentTable.tableDnDConfig,
+            rowY      = 0,
+            row       = null;
+
+        for (var i = 0; i < rows.length; i++) {
+            row       = rows[i];
+            rowY      = this.getPosition(row).y;
+            rowHeight = parseInt(row.offsetHeight) / 2;
+            if (row.offsetHeight === 0) {
+                rowY      = this.getPosition(row.firstChild).y;
+                rowHeight = parseInt(row.firstChild.offsetHeight) / 2;
+            }
+            // Because we always have to insert before, we need to offset the height a bit
+            if (y > (rowY - rowHeight) && y < (rowY + rowHeight))
+                // that's the row we're over
+                // If it's the same as the current row, ignore it
+                if (draggedRow.is(row)
+                    || (config.onAllowDrop
+                    && !config.onAllowDrop(draggedRow, row))
+                    // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
+                    || $(row).hasClass("nodrop"))
+                        return null;
+                else
+                    return row;
+        }
+        return null;
+    },
+    processMouseup: function() {
+        if (!this.currentTable || !this.dragObject)
+            return null;
+
+        var config      = this.currentTable.tableDnDConfig,
+            droppedRow  = this.dragObject,
+            parentLevel = 0,
+            myLevel     = 0;
+
+        // Unbind the event handlers
+        $(document)
+            .unbind(moveEvent, this.mousemove)
+            .unbind(endEvent,  this.mouseup);
+
+        config.hierarchyLevel
+            && config.autoCleanRelations
+            && $(this.currentTable.rows).first().find('td:first').children().each(function () {
+                myLevel = $(this).parents('tr:first').data('level');
+                myLevel
+                    && $(this).parents('tr:first').data('level', --myLevel)
+                    && $(this).remove();
+            })
+            && config.hierarchyLevel > 1
+            && $(this.currentTable.rows).each(function () {
+                myLevel = $(this).data('level');
+                if (myLevel > 1) {
+                    parentLevel = $(this).prev().data('level');
+                    while (myLevel > parentLevel + 1) {
+                        $(this).find('td:first').children(':first').remove();
+                        $(this).data('level', --myLevel);
+                    }
+                }
+            });
+
+        // If we have a dragObject, then we need to release it,
+        // The row will already have been moved to the right place so we just reset stuff
+        config.onDragClass
+            && $(droppedRow).removeClass(config.onDragClass)
+            || $(droppedRow).css(config.onDropStyle);
+
+        this.dragObject = null;
+        // Call the onDrop method if there is one
+        config.onDrop
+            && this.originalOrder !== this.currentOrder()
+            && $(droppedRow).hide().fadeIn('fast')
+            && config.onDrop(this.currentTable, droppedRow);
+
+        // Call the onDragStop method if there is one
+        config.onDragStop
+            && config.onDragStop(this.currentTable, droppedRow);
+
+        this.currentTable = null; // let go of the table too
+    },
+    mouseup: function(e) {
+        e && e.preventDefault();
+        $.tableDnD.processMouseup();
+        return false;
+    },
+    jsonize: function(pretify) {
+        var table = this.currentTable;
+        if (pretify)
+            return JSON.stringify(
+                this.tableData(table),
+                null,
+                table.tableDnDConfig.jsonPretifySeparator
+            );
+        return JSON.stringify(this.tableData(table));
+    },
+    serialize: function() {
+        return $.param(this.tableData(this.currentTable));
+    },
+    serializeTable: function(table) {
+        var result = "";
+        var paramName = table.tableDnDConfig.serializeParamName || table.id;
+        var rows = table.rows;
+        for (var i=0; i<rows.length; i++) {
+            if (result.length > 0) result += "&";
+            var rowId = rows[i].id;
+            if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
+                rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
+                result += paramName + '[]=' + rowId;
+            }
+        }
+        return result;
+    },
+    serializeTables: function() {
+        var result = [];
+        $('table').each(function() {
+            this.id && result.push($.param($.tableDnD.tableData(this)));
+        });
+        return result.join('&');
+    },
+    tableData: function (table) {
+        var config = table.tableDnDConfig,
+            previousIDs  = [],
+            currentLevel = 0,
+            indentLevel  = 0,
+            rowID        = null,
+            data         = {},
+            getSerializeRegexp,
+            paramName,
+            currentID,
+            rows;
+
+        if (!table)
+            table = this.currentTable;
+        if (!table || !table.rows || !table.rows.length)
+            return {error: { code: 500, message: "Not a valid table."}};
+        if (!table.id && !config.serializeParamName)
+            return {error: { code: 500, message: "No serializable unique id provided."}};
+        
+        rows      = config.autoCleanRelations
+                        && table.rows
+                        || $.makeArray(table.rows);
+        paramName = config.serializeParamName || table.id;
+        currentID = paramName;
+
+        getSerializeRegexp = function (rowId) {
+            if (rowId && config && config.serializeRegexp)
+                return rowId.match(config.serializeRegexp)[0];
+            return rowId;
+        };
+
+        data[currentID] = [];
+        !config.autoCleanRelations
+            && $(rows[0]).data('level')
+            && rows.unshift({id: 'undefined'});
+
+
+
+        for (var i=0; i < rows.length; i++) {
+            if (config.hierarchyLevel) {
+                indentLevel = $(rows[i]).data('level') || 0;
+                if (indentLevel === 0) {
+                    currentID   = paramName;
+                    previousIDs = [];
+                }
+                else if (indentLevel > currentLevel) {
+                    previousIDs.push([currentID, currentLevel]);
+                    currentID = getSerializeRegexp(rows[i-1].id);
+                }
+                else if (indentLevel < currentLevel) {
+                    for (var h = 0; h < previousIDs.length; h++) {
+                        if (previousIDs[h][1] === indentLevel)
+                            currentID         = previousIDs[h][0];
+                        if (previousIDs[h][1] >= currentLevel)
+                            previousIDs[h][1] = 0;
+                    }
+                }
+                currentLevel = indentLevel;
+
+                if (!$.isArray(data[currentID]))
+                    data[currentID] = [];
+                rowID = getSerializeRegexp(rows[i].id);
+                rowID && data[currentID].push(rowID);
+            }
+            else {
+                rowID = getSerializeRegexp(rows[i].id);
+                rowID && data[currentID].push(rowID);
+            }
+        }
+        return data;
+    }
+};
+
+jQuery.fn.extend(
+    {
+        tableDnD             : $.tableDnD.build,
+        tableDnDUpdate       : $.tableDnD.updateTables,
+        tableDnDSerialize    : $.proxy($.tableDnD.serialize, $.tableDnD),
+        tableDnDSerializeAll : $.tableDnD.serializeTables,
+        tableDnDData         : $.proxy($.tableDnD.tableData, $.tableDnD)
+    }
+);
+
+}(jQuery, window, window.document);

+ 771 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.js

@@ -0,0 +1,771 @@
+(function (global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
+	typeof define === 'function' && define.amd ? define(['jquery'], factory) :
+	(global = global || self, factory(global.jQuery));
+}(this, (function ($) { 'use strict';
+
+	$ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;
+
+	var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+	function createCommonjsModule(fn, module) {
+		return module = { exports: {} }, fn(module, module.exports), module.exports;
+	}
+
+	var check = function (it) {
+	  return it && it.Math == Math && it;
+	};
+
+	// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
+	var global_1 =
+	  // eslint-disable-next-line no-undef
+	  check(typeof globalThis == 'object' && globalThis) ||
+	  check(typeof window == 'object' && window) ||
+	  check(typeof self == 'object' && self) ||
+	  check(typeof commonjsGlobal == 'object' && commonjsGlobal) ||
+	  // eslint-disable-next-line no-new-func
+	  Function('return this')();
+
+	var fails = function (exec) {
+	  try {
+	    return !!exec();
+	  } catch (error) {
+	    return true;
+	  }
+	};
+
+	// Thank's IE8 for his funny defineProperty
+	var descriptors = !fails(function () {
+	  return Object.defineProperty({}, 'a', { get: function () { return 7; } }).a != 7;
+	});
+
+	var nativePropertyIsEnumerable = {}.propertyIsEnumerable;
+	var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+
+	// Nashorn ~ JDK8 bug
+	var NASHORN_BUG = getOwnPropertyDescriptor && !nativePropertyIsEnumerable.call({ 1: 2 }, 1);
+
+	// `Object.prototype.propertyIsEnumerable` method implementation
+	// https://tc39.github.io/ecma262/#sec-object.prototype.propertyisenumerable
+	var f = NASHORN_BUG ? function propertyIsEnumerable(V) {
+	  var descriptor = getOwnPropertyDescriptor(this, V);
+	  return !!descriptor && descriptor.enumerable;
+	} : nativePropertyIsEnumerable;
+
+	var objectPropertyIsEnumerable = {
+		f: f
+	};
+
+	var createPropertyDescriptor = function (bitmap, value) {
+	  return {
+	    enumerable: !(bitmap & 1),
+	    configurable: !(bitmap & 2),
+	    writable: !(bitmap & 4),
+	    value: value
+	  };
+	};
+
+	var toString = {}.toString;
+
+	var classofRaw = function (it) {
+	  return toString.call(it).slice(8, -1);
+	};
+
+	var split = ''.split;
+
+	// fallback for non-array-like ES3 and non-enumerable old V8 strings
+	var indexedObject = fails(function () {
+	  // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346
+	  // eslint-disable-next-line no-prototype-builtins
+	  return !Object('z').propertyIsEnumerable(0);
+	}) ? function (it) {
+	  return classofRaw(it) == 'String' ? split.call(it, '') : Object(it);
+	} : Object;
+
+	// `RequireObjectCoercible` abstract operation
+	// https://tc39.github.io/ecma262/#sec-requireobjectcoercible
+	var requireObjectCoercible = function (it) {
+	  if (it == undefined) throw TypeError("Can't call method on " + it);
+	  return it;
+	};
+
+	// toObject with fallback for non-array-like ES3 strings
+
+
+
+	var toIndexedObject = function (it) {
+	  return indexedObject(requireObjectCoercible(it));
+	};
+
+	var isObject = function (it) {
+	  return typeof it === 'object' ? it !== null : typeof it === 'function';
+	};
+
+	// `ToPrimitive` abstract operation
+	// https://tc39.github.io/ecma262/#sec-toprimitive
+	// instead of the ES6 spec version, we didn't implement @@toPrimitive case
+	// and the second argument - flag - preferred type is a string
+	var toPrimitive = function (input, PREFERRED_STRING) {
+	  if (!isObject(input)) return input;
+	  var fn, val;
+	  if (PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val;
+	  if (typeof (fn = input.valueOf) == 'function' && !isObject(val = fn.call(input))) return val;
+	  if (!PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val;
+	  throw TypeError("Can't convert object to primitive value");
+	};
+
+	var hasOwnProperty = {}.hasOwnProperty;
+
+	var has = function (it, key) {
+	  return hasOwnProperty.call(it, key);
+	};
+
+	var document = global_1.document;
+	// typeof document.createElement is 'object' in old IE
+	var EXISTS = isObject(document) && isObject(document.createElement);
+
+	var documentCreateElement = function (it) {
+	  return EXISTS ? document.createElement(it) : {};
+	};
+
+	// Thank's IE8 for his funny defineProperty
+	var ie8DomDefine = !descriptors && !fails(function () {
+	  return Object.defineProperty(documentCreateElement('div'), 'a', {
+	    get: function () { return 7; }
+	  }).a != 7;
+	});
+
+	var nativeGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+
+	// `Object.getOwnPropertyDescriptor` method
+	// https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptor
+	var f$1 = descriptors ? nativeGetOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) {
+	  O = toIndexedObject(O);
+	  P = toPrimitive(P, true);
+	  if (ie8DomDefine) try {
+	    return nativeGetOwnPropertyDescriptor(O, P);
+	  } catch (error) { /* empty */ }
+	  if (has(O, P)) return createPropertyDescriptor(!objectPropertyIsEnumerable.f.call(O, P), O[P]);
+	};
+
+	var objectGetOwnPropertyDescriptor = {
+		f: f$1
+	};
+
+	var anObject = function (it) {
+	  if (!isObject(it)) {
+	    throw TypeError(String(it) + ' is not an object');
+	  } return it;
+	};
+
+	var nativeDefineProperty = Object.defineProperty;
+
+	// `Object.defineProperty` method
+	// https://tc39.github.io/ecma262/#sec-object.defineproperty
+	var f$2 = descriptors ? nativeDefineProperty : function defineProperty(O, P, Attributes) {
+	  anObject(O);
+	  P = toPrimitive(P, true);
+	  anObject(Attributes);
+	  if (ie8DomDefine) try {
+	    return nativeDefineProperty(O, P, Attributes);
+	  } catch (error) { /* empty */ }
+	  if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported');
+	  if ('value' in Attributes) O[P] = Attributes.value;
+	  return O;
+	};
+
+	var objectDefineProperty = {
+		f: f$2
+	};
+
+	var createNonEnumerableProperty = descriptors ? function (object, key, value) {
+	  return objectDefineProperty.f(object, key, createPropertyDescriptor(1, value));
+	} : function (object, key, value) {
+	  object[key] = value;
+	  return object;
+	};
+
+	var setGlobal = function (key, value) {
+	  try {
+	    createNonEnumerableProperty(global_1, key, value);
+	  } catch (error) {
+	    global_1[key] = value;
+	  } return value;
+	};
+
+	var SHARED = '__core-js_shared__';
+	var store = global_1[SHARED] || setGlobal(SHARED, {});
+
+	var sharedStore = store;
+
+	var functionToString = Function.toString;
+
+	// this helper broken in `3.4.1-3.4.4`, so we can't use `shared` helper
+	if (typeof sharedStore.inspectSource != 'function') {
+	  sharedStore.inspectSource = function (it) {
+	    return functionToString.call(it);
+	  };
+	}
+
+	var inspectSource = sharedStore.inspectSource;
+
+	var WeakMap = global_1.WeakMap;
+
+	var nativeWeakMap = typeof WeakMap === 'function' && /native code/.test(inspectSource(WeakMap));
+
+	var shared = createCommonjsModule(function (module) {
+	(module.exports = function (key, value) {
+	  return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
+	})('versions', []).push({
+	  version: '3.6.0',
+	  mode:  'global',
+	  copyright: '© 2019 Denis Pushkarev (zloirock.ru)'
+	});
+	});
+
+	var id = 0;
+	var postfix = Math.random();
+
+	var uid = function (key) {
+	  return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id + postfix).toString(36);
+	};
+
+	var keys = shared('keys');
+
+	var sharedKey = function (key) {
+	  return keys[key] || (keys[key] = uid(key));
+	};
+
+	var hiddenKeys = {};
+
+	var WeakMap$1 = global_1.WeakMap;
+	var set, get, has$1;
+
+	var enforce = function (it) {
+	  return has$1(it) ? get(it) : set(it, {});
+	};
+
+	var getterFor = function (TYPE) {
+	  return function (it) {
+	    var state;
+	    if (!isObject(it) || (state = get(it)).type !== TYPE) {
+	      throw TypeError('Incompatible receiver, ' + TYPE + ' required');
+	    } return state;
+	  };
+	};
+
+	if (nativeWeakMap) {
+	  var store$1 = new WeakMap$1();
+	  var wmget = store$1.get;
+	  var wmhas = store$1.has;
+	  var wmset = store$1.set;
+	  set = function (it, metadata) {
+	    wmset.call(store$1, it, metadata);
+	    return metadata;
+	  };
+	  get = function (it) {
+	    return wmget.call(store$1, it) || {};
+	  };
+	  has$1 = function (it) {
+	    return wmhas.call(store$1, it);
+	  };
+	} else {
+	  var STATE = sharedKey('state');
+	  hiddenKeys[STATE] = true;
+	  set = function (it, metadata) {
+	    createNonEnumerableProperty(it, STATE, metadata);
+	    return metadata;
+	  };
+	  get = function (it) {
+	    return has(it, STATE) ? it[STATE] : {};
+	  };
+	  has$1 = function (it) {
+	    return has(it, STATE);
+	  };
+	}
+
+	var internalState = {
+	  set: set,
+	  get: get,
+	  has: has$1,
+	  enforce: enforce,
+	  getterFor: getterFor
+	};
+
+	var redefine = createCommonjsModule(function (module) {
+	var getInternalState = internalState.get;
+	var enforceInternalState = internalState.enforce;
+	var TEMPLATE = String(String).split('String');
+
+	(module.exports = function (O, key, value, options) {
+	  var unsafe = options ? !!options.unsafe : false;
+	  var simple = options ? !!options.enumerable : false;
+	  var noTargetGet = options ? !!options.noTargetGet : false;
+	  if (typeof value == 'function') {
+	    if (typeof key == 'string' && !has(value, 'name')) createNonEnumerableProperty(value, 'name', key);
+	    enforceInternalState(value).source = TEMPLATE.join(typeof key == 'string' ? key : '');
+	  }
+	  if (O === global_1) {
+	    if (simple) O[key] = value;
+	    else setGlobal(key, value);
+	    return;
+	  } else if (!unsafe) {
+	    delete O[key];
+	  } else if (!noTargetGet && O[key]) {
+	    simple = true;
+	  }
+	  if (simple) O[key] = value;
+	  else createNonEnumerableProperty(O, key, value);
+	// add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative
+	})(Function.prototype, 'toString', function toString() {
+	  return typeof this == 'function' && getInternalState(this).source || inspectSource(this);
+	});
+	});
+
+	var path = global_1;
+
+	var aFunction = function (variable) {
+	  return typeof variable == 'function' ? variable : undefined;
+	};
+
+	var getBuiltIn = function (namespace, method) {
+	  return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global_1[namespace])
+	    : path[namespace] && path[namespace][method] || global_1[namespace] && global_1[namespace][method];
+	};
+
+	var ceil = Math.ceil;
+	var floor = Math.floor;
+
+	// `ToInteger` abstract operation
+	// https://tc39.github.io/ecma262/#sec-tointeger
+	var toInteger = function (argument) {
+	  return isNaN(argument = +argument) ? 0 : (argument > 0 ? floor : ceil)(argument);
+	};
+
+	var min = Math.min;
+
+	// `ToLength` abstract operation
+	// https://tc39.github.io/ecma262/#sec-tolength
+	var toLength = function (argument) {
+	  return argument > 0 ? min(toInteger(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991
+	};
+
+	var max = Math.max;
+	var min$1 = Math.min;
+
+	// Helper for a popular repeating case of the spec:
+	// Let integer be ? ToInteger(index).
+	// If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length).
+	var toAbsoluteIndex = function (index, length) {
+	  var integer = toInteger(index);
+	  return integer < 0 ? max(integer + length, 0) : min$1(integer, length);
+	};
+
+	// `Array.prototype.{ indexOf, includes }` methods implementation
+	var createMethod = function (IS_INCLUDES) {
+	  return function ($this, el, fromIndex) {
+	    var O = toIndexedObject($this);
+	    var length = toLength(O.length);
+	    var index = toAbsoluteIndex(fromIndex, length);
+	    var value;
+	    // Array#includes uses SameValueZero equality algorithm
+	    // eslint-disable-next-line no-self-compare
+	    if (IS_INCLUDES && el != el) while (length > index) {
+	      value = O[index++];
+	      // eslint-disable-next-line no-self-compare
+	      if (value != value) return true;
+	    // Array#indexOf ignores holes, Array#includes - not
+	    } else for (;length > index; index++) {
+	      if ((IS_INCLUDES || index in O) && O[index] === el) return IS_INCLUDES || index || 0;
+	    } return !IS_INCLUDES && -1;
+	  };
+	};
+
+	var arrayIncludes = {
+	  // `Array.prototype.includes` method
+	  // https://tc39.github.io/ecma262/#sec-array.prototype.includes
+	  includes: createMethod(true),
+	  // `Array.prototype.indexOf` method
+	  // https://tc39.github.io/ecma262/#sec-array.prototype.indexof
+	  indexOf: createMethod(false)
+	};
+
+	var indexOf = arrayIncludes.indexOf;
+
+
+	var objectKeysInternal = function (object, names) {
+	  var O = toIndexedObject(object);
+	  var i = 0;
+	  var result = [];
+	  var key;
+	  for (key in O) !has(hiddenKeys, key) && has(O, key) && result.push(key);
+	  // Don't enum bug & hidden keys
+	  while (names.length > i) if (has(O, key = names[i++])) {
+	    ~indexOf(result, key) || result.push(key);
+	  }
+	  return result;
+	};
+
+	// IE8- don't enum bug keys
+	var enumBugKeys = [
+	  'constructor',
+	  'hasOwnProperty',
+	  'isPrototypeOf',
+	  'propertyIsEnumerable',
+	  'toLocaleString',
+	  'toString',
+	  'valueOf'
+	];
+
+	var hiddenKeys$1 = enumBugKeys.concat('length', 'prototype');
+
+	// `Object.getOwnPropertyNames` method
+	// https://tc39.github.io/ecma262/#sec-object.getownpropertynames
+	var f$3 = Object.getOwnPropertyNames || function getOwnPropertyNames(O) {
+	  return objectKeysInternal(O, hiddenKeys$1);
+	};
+
+	var objectGetOwnPropertyNames = {
+		f: f$3
+	};
+
+	var f$4 = Object.getOwnPropertySymbols;
+
+	var objectGetOwnPropertySymbols = {
+		f: f$4
+	};
+
+	// all object keys, includes non-enumerable and symbols
+	var ownKeys = getBuiltIn('Reflect', 'ownKeys') || function ownKeys(it) {
+	  var keys = objectGetOwnPropertyNames.f(anObject(it));
+	  var getOwnPropertySymbols = objectGetOwnPropertySymbols.f;
+	  return getOwnPropertySymbols ? keys.concat(getOwnPropertySymbols(it)) : keys;
+	};
+
+	var copyConstructorProperties = function (target, source) {
+	  var keys = ownKeys(source);
+	  var defineProperty = objectDefineProperty.f;
+	  var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
+	  for (var i = 0; i < keys.length; i++) {
+	    var key = keys[i];
+	    if (!has(target, key)) defineProperty(target, key, getOwnPropertyDescriptor(source, key));
+	  }
+	};
+
+	var replacement = /#|\.prototype\./;
+
+	var isForced = function (feature, detection) {
+	  var value = data[normalize(feature)];
+	  return value == POLYFILL ? true
+	    : value == NATIVE ? false
+	    : typeof detection == 'function' ? fails(detection)
+	    : !!detection;
+	};
+
+	var normalize = isForced.normalize = function (string) {
+	  return String(string).replace(replacement, '.').toLowerCase();
+	};
+
+	var data = isForced.data = {};
+	var NATIVE = isForced.NATIVE = 'N';
+	var POLYFILL = isForced.POLYFILL = 'P';
+
+	var isForced_1 = isForced;
+
+	var getOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f;
+
+
+
+
+
+
+	/*
+	  options.target      - name of the target object
+	  options.global      - target is the global object
+	  options.stat        - export as static methods of target
+	  options.proto       - export as prototype methods of target
+	  options.real        - real prototype method for the `pure` version
+	  options.forced      - export even if the native feature is available
+	  options.bind        - bind methods to the target, required for the `pure` version
+	  options.wrap        - wrap constructors to preventing global pollution, required for the `pure` version
+	  options.unsafe      - use the simple assignment of property instead of delete + defineProperty
+	  options.sham        - add a flag to not completely full polyfills
+	  options.enumerable  - export as enumerable property
+	  options.noTargetGet - prevent calling a getter on target
+	*/
+	var _export = function (options, source) {
+	  var TARGET = options.target;
+	  var GLOBAL = options.global;
+	  var STATIC = options.stat;
+	  var FORCED, target, key, targetProperty, sourceProperty, descriptor;
+	  if (GLOBAL) {
+	    target = global_1;
+	  } else if (STATIC) {
+	    target = global_1[TARGET] || setGlobal(TARGET, {});
+	  } else {
+	    target = (global_1[TARGET] || {}).prototype;
+	  }
+	  if (target) for (key in source) {
+	    sourceProperty = source[key];
+	    if (options.noTargetGet) {
+	      descriptor = getOwnPropertyDescriptor$1(target, key);
+	      targetProperty = descriptor && descriptor.value;
+	    } else targetProperty = target[key];
+	    FORCED = isForced_1(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced);
+	    // contained in target
+	    if (!FORCED && targetProperty !== undefined) {
+	      if (typeof sourceProperty === typeof targetProperty) continue;
+	      copyConstructorProperties(sourceProperty, targetProperty);
+	    }
+	    // add a flag to not completely full polyfills
+	    if (options.sham || (targetProperty && targetProperty.sham)) {
+	      createNonEnumerableProperty(sourceProperty, 'sham', true);
+	    }
+	    // extend global
+	    redefine(target, key, sourceProperty, options);
+	  }
+	};
+
+	// `IsArray` abstract operation
+	// https://tc39.github.io/ecma262/#sec-isarray
+	var isArray = Array.isArray || function isArray(arg) {
+	  return classofRaw(arg) == 'Array';
+	};
+
+	// `ToObject` abstract operation
+	// https://tc39.github.io/ecma262/#sec-toobject
+	var toObject = function (argument) {
+	  return Object(requireObjectCoercible(argument));
+	};
+
+	var createProperty = function (object, key, value) {
+	  var propertyKey = toPrimitive(key);
+	  if (propertyKey in object) objectDefineProperty.f(object, propertyKey, createPropertyDescriptor(0, value));
+	  else object[propertyKey] = value;
+	};
+
+	var nativeSymbol = !!Object.getOwnPropertySymbols && !fails(function () {
+	  // Chrome 38 Symbol has incorrect toString conversion
+	  // eslint-disable-next-line no-undef
+	  return !String(Symbol());
+	});
+
+	var useSymbolAsUid = nativeSymbol
+	  // eslint-disable-next-line no-undef
+	  && !Symbol.sham
+	  // eslint-disable-next-line no-undef
+	  && typeof Symbol() == 'symbol';
+
+	var WellKnownSymbolsStore = shared('wks');
+	var Symbol$1 = global_1.Symbol;
+	var createWellKnownSymbol = useSymbolAsUid ? Symbol$1 : uid;
+
+	var wellKnownSymbol = function (name) {
+	  if (!has(WellKnownSymbolsStore, name)) {
+	    if (nativeSymbol && has(Symbol$1, name)) WellKnownSymbolsStore[name] = Symbol$1[name];
+	    else WellKnownSymbolsStore[name] = createWellKnownSymbol('Symbol.' + name);
+	  } return WellKnownSymbolsStore[name];
+	};
+
+	var SPECIES = wellKnownSymbol('species');
+
+	// `ArraySpeciesCreate` abstract operation
+	// https://tc39.github.io/ecma262/#sec-arrayspeciescreate
+	var arraySpeciesCreate = function (originalArray, length) {
+	  var C;
+	  if (isArray(originalArray)) {
+	    C = originalArray.constructor;
+	    // cross-realm fallback
+	    if (typeof C == 'function' && (C === Array || isArray(C.prototype))) C = undefined;
+	    else if (isObject(C)) {
+	      C = C[SPECIES];
+	      if (C === null) C = undefined;
+	    }
+	  } return new (C === undefined ? Array : C)(length === 0 ? 0 : length);
+	};
+
+	var userAgent = getBuiltIn('navigator', 'userAgent') || '';
+
+	var process = global_1.process;
+	var versions = process && process.versions;
+	var v8 = versions && versions.v8;
+	var match, version;
+
+	if (v8) {
+	  match = v8.split('.');
+	  version = match[0] + match[1];
+	} else if (userAgent) {
+	  match = userAgent.match(/Edge\/(\d+)/);
+	  if (!match || match[1] >= 74) {
+	    match = userAgent.match(/Chrome\/(\d+)/);
+	    if (match) version = match[1];
+	  }
+	}
+
+	var v8Version = version && +version;
+
+	var SPECIES$1 = wellKnownSymbol('species');
+
+	var arrayMethodHasSpeciesSupport = function (METHOD_NAME) {
+	  // We can't use this feature detection in V8 since it causes
+	  // deoptimization and serious performance degradation
+	  // https://github.com/zloirock/core-js/issues/677
+	  return v8Version >= 51 || !fails(function () {
+	    var array = [];
+	    var constructor = array.constructor = {};
+	    constructor[SPECIES$1] = function () {
+	      return { foo: 1 };
+	    };
+	    return array[METHOD_NAME](Boolean).foo !== 1;
+	  });
+	};
+
+	var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable');
+	var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF;
+	var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded';
+
+	// We can't use this feature detection in V8 since it causes
+	// deoptimization and serious performance degradation
+	// https://github.com/zloirock/core-js/issues/679
+	var IS_CONCAT_SPREADABLE_SUPPORT = v8Version >= 51 || !fails(function () {
+	  var array = [];
+	  array[IS_CONCAT_SPREADABLE] = false;
+	  return array.concat()[0] !== array;
+	});
+
+	var SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('concat');
+
+	var isConcatSpreadable = function (O) {
+	  if (!isObject(O)) return false;
+	  var spreadable = O[IS_CONCAT_SPREADABLE];
+	  return spreadable !== undefined ? !!spreadable : isArray(O);
+	};
+
+	var FORCED = !IS_CONCAT_SPREADABLE_SUPPORT || !SPECIES_SUPPORT;
+
+	// `Array.prototype.concat` method
+	// https://tc39.github.io/ecma262/#sec-array.prototype.concat
+	// with adding support of @@isConcatSpreadable and @@species
+	_export({ target: 'Array', proto: true, forced: FORCED }, {
+	  concat: function concat(arg) { // eslint-disable-line no-unused-vars
+	    var O = toObject(this);
+	    var A = arraySpeciesCreate(O, 0);
+	    var n = 0;
+	    var i, k, length, len, E;
+	    for (i = -1, length = arguments.length; i < length; i++) {
+	      E = i === -1 ? O : arguments[i];
+	      if (isConcatSpreadable(E)) {
+	        len = toLength(E.length);
+	        if (n + len > MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED);
+	        for (k = 0; k < len; k++, n++) if (k in E) createProperty(A, n, E[k]);
+	      } else {
+	        if (n >= MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED);
+	        createProperty(A, n++, E);
+	      }
+	    }
+	    A.length = n;
+	    return A;
+	  }
+	});
+
+	/**
+	 * Bootstrap Table Chinese translation
+	 * Author: Zhixin Wen<wenzhixin2010@gmail.com>
+	 */
+
+	$.fn.bootstrapTable.locales['zh-CN'] = {
+	  formatLoadingMessage: function formatLoadingMessage() {
+	    return '正在努力地加载数据中,请稍候';
+	  },
+	  formatRecordsPerPage: function formatRecordsPerPage(pageNumber) {
+	    return "\u6BCF\u9875\u663E\u793A ".concat(pageNumber, " \u6761\u8BB0\u5F55");
+	  },
+	  formatShowingRows: function formatShowingRows(pageFrom, pageTo, totalRows, totalNotFiltered) {
+	    if (totalNotFiltered !== undefined && totalNotFiltered > 0 && totalNotFiltered > totalRows) {
+	      return "\u663E\u793A\u7B2C ".concat(pageFrom, " \u5230\u7B2C ").concat(pageTo, " \u6761\u8BB0\u5F55\uFF0C\u603B\u5171 ").concat(totalRows, " \u6761\u8BB0\u5F55\uFF08\u4ECE ").concat(totalNotFiltered, " \u603B\u8BB0\u5F55\u4E2D\u8FC7\u6EE4\uFF09");
+	    }
+
+	    return "\u663E\u793A\u7B2C ".concat(pageFrom, " \u5230\u7B2C ").concat(pageTo, " \u6761\u8BB0\u5F55\uFF0C\u603B\u5171 ").concat(totalRows, " \u6761\u8BB0\u5F55");
+	  },
+	  formatSRPaginationPreText: function formatSRPaginationPreText() {
+	    return '上一页';
+	  },
+	  formatSRPaginationPageText: function formatSRPaginationPageText(page) {
+	    return "\u7B2C".concat(page, "\u9875");
+	  },
+	  formatSRPaginationNextText: function formatSRPaginationNextText() {
+	    return '下一页';
+	  },
+	  formatDetailPagination: function formatDetailPagination(totalRows) {
+	    return "\u603B\u5171 ".concat(totalRows, " \u6761\u8BB0\u5F55");
+	  },
+	  formatClearSearch: function formatClearSearch() {
+	    return '清空过滤';
+	  },
+	  formatSearch: function formatSearch() {
+	    return '搜索';
+	  },
+	  formatNoMatches: function formatNoMatches() {
+	    return '没有找到匹配的记录';
+	  },
+	  formatPaginationSwitch: function formatPaginationSwitch() {
+	    return '隐藏/显示分页';
+	  },
+	  formatPaginationSwitchDown: function formatPaginationSwitchDown() {
+	    return '显示分页';
+	  },
+	  formatPaginationSwitchUp: function formatPaginationSwitchUp() {
+	    return '隐藏分页';
+	  },
+	  formatRefresh: function formatRefresh() {
+	    return '刷新';
+	  },
+	  formatToggle: function formatToggle() {
+	    return '切换';
+	  },
+	  formatToggleOn: function formatToggleOn() {
+	    return '显示卡片视图';
+	  },
+	  formatToggleOff: function formatToggleOff() {
+	    return '隐藏卡片视图';
+	  },
+	  formatColumns: function formatColumns() {
+	    return '列';
+	  },
+	  formatColumnsToggleAll: function formatColumnsToggleAll() {
+	    return '切换所有';
+	  },
+	  formatFullscreen: function formatFullscreen() {
+	    return '全屏';
+	  },
+	  formatAllRows: function formatAllRows() {
+	    return '所有';
+	  },
+	  formatAutoRefresh: function formatAutoRefresh() {
+	    return '自动刷新';
+	  },
+	  formatExport: function formatExport() {
+	    return '导出数据';
+	  },
+	  formatJumpTo: function formatJumpTo() {
+	    return '跳转';
+	  },
+	  formatAdvancedSearch: function formatAdvancedSearch() {
+	    return '高级搜索';
+	  },
+	  formatAdvancedCloseButton: function formatAdvancedCloseButton() {
+	    return '关闭';
+	  },
+	  formatFilterControlSwitch: function formatFilterControlSwitch() {
+	    return '隐藏/显示过滤控制';
+	  },
+	  formatFilterControlSwitchHide: function formatFilterControlSwitchHide() {
+	    return '隐藏过滤控制';
+	  },
+	  formatFilterControlSwitchShow: function formatFilterControlSwitchShow() {
+	    return '显示过滤控制';
+	  }
+	};
+	$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['zh-CN']);
+
+})));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 10 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.min.js


+ 747 - 0
leiSP-admin/src/main/resources/static/ajax/libs/bootstrap-treetable/bootstrap-treetable.js

@@ -0,0 +1,747 @@
+/**
+ * 基于bootstrapTreeTable/bootstrap-table-treegrid修改
+ * Copyright (c) 2019 LeiSP
+ */
+(function($) {
+    "use strict";
+
+    $.fn.bootstrapTreeTable = function(options, param) {
+        var target = $(this).data('bootstrap.tree.table');
+        target = target ? target : $(this);
+        // 如果是调用方法
+        if (typeof options == 'string') {
+            return $.fn.bootstrapTreeTable.methods[options](target, param);
+        }
+        // 如果是初始化组件
+        options = $.extend({}, $.fn.bootstrapTreeTable.defaults, options || {});
+        target.hasSelectItem = false;// 是否有radio或checkbox
+        target.data_list = null; //用于缓存格式化后的数据-按父分组
+        target.data_obj = null; //用于缓存格式化后的数据-按id存对象
+        target.hiddenColumns = []; //用于存放被隐藏列的field
+        target.lastAjaxParams; //用户最后一次请求的参数
+        target.isFixWidth=false; //是否有固定宽度
+        // 初始化
+        var init = function() {
+            // 初始化容器
+            initContainer();
+            // 初始化工具栏
+            initToolbar();
+            // 初始化表头
+            initHeader();
+            // 初始化表体
+            initBody();
+            // 初始化数据服务
+            initServer();
+            // 动态设置表头宽度
+            autoTheadWidth(true);
+            // 缓存target对象
+            target.data('bootstrap.tree.table', target);
+        }
+        // 初始化容器
+        var initContainer = function() {
+            // 在外层包装一下div,样式用的bootstrap-table的
+            var $main_div = $("<div class='bootstrap-tree-table'></div>");
+            var $treetable = $("<div class='treetable-table'></div>");
+            target.before($main_div);
+            $main_div.append($treetable);
+            $treetable.append(target);
+            target.addClass("table");
+            if (options.striped) {
+                target.addClass('table-striped');
+            }
+            if (options.bordered) {
+                target.addClass('table-bordered');
+            }
+            if (options.hover) {
+                target.addClass('table-hover');
+            }
+            if (options.condensed) {
+                target.addClass('table-condensed');
+            }
+            target.html("");
+        }
+        // 初始化工具栏
+        var initToolbar = function() {
+            var $toolbar = $("<div class='treetable-bars'></div>");
+            if (options.toolbar) {
+                $(options.toolbar).addClass('tool-left');
+                $toolbar.append($(options.toolbar));
+            }
+            var $rightToolbar = $('<div class="btn-group tool-right">');
+            $toolbar.append($rightToolbar);
+            target.parent().before($toolbar);
+            // ruoyi 是否显示检索信息
+            if (options.showSearch) {
+                var $searchBtn = $('<button class="btn btn-default btn-outline" type="button" aria-label="search" title="搜索"><i class="glyphicon glyphicon-search"></i></button>');
+                $rightToolbar.append($searchBtn);
+                registerSearchBtnClickEvent($searchBtn);
+            }
+            // 是否显示刷新按钮
+            if (options.showRefresh) {
+                var $refreshBtn = $('<button class="btn btn-default btn-outline" type="button" aria-label="refresh" title="刷新"><i class="glyphicon glyphicon-repeat"></i></button>');
+                $rightToolbar.append($refreshBtn);
+                registerRefreshBtnClickEvent($refreshBtn);
+            }
+            // 是否显示列选项
+            if (options.showColumns) {
+                var $columns_div = $('<div class="btn-group pull-right" title="列"><button type="button" aria-label="columns" class="btn btn-default btn-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="glyphicon glyphicon-list"></i> <span class="caret"></span></button></div>');
+                var $columns_ul = $('<ul class="dropdown-menu columns" role="menu"></ul>');
+                $.each(options.columns, function(i, column) {
+                    if (column.field != 'selectItem') {
+                        var _li = null;
+                        if(typeof column.visible == "undefined"||column.visible==true){
+                            _li = $('<li role="menuitem"><label><input type="checkbox" checked="checked" data-field="'+column.field+'" value="'+column.field+'" > '+column.title+'</label></li>');
+                        }else{
+                            _li = $('<li role="menuitem"><label><input type="checkbox" data-field="'+column.field+'" value="'+column.field+'" > '+column.title+'</label></li>');
+                            target.hiddenColumns.push(column.field);
+                        }
+                        $columns_ul.append(_li);
+                    }
+                });
+                $columns_div.append($columns_ul);
+                $rightToolbar.append($columns_div);
+                // 注册列选项事件
+                registerColumnClickEvent();
+            }else{
+                $.each(options.columns, function(i, column) {
+                    if (column.field != 'selectItem') {
+                        if(!(typeof column.visible == "undefined"||column.visible==true)){
+                            target.hiddenColumns.push(column.field);
+                        }
+                    }
+                });
+            }
+        }
+        // 初始化隐藏列
+        var initHiddenColumns = function(){
+            $.each(target.hiddenColumns, function(i, field) {
+                target.find("."+field+"_cls").hide();
+            });
+        }
+        // 初始化表头
+        var initHeader = function() {
+            var $thr = $('<tr></tr>');
+            $.each(options.columns, function(i, column) {
+                var $th = null;
+                // 判断有没有选择列
+                if (i == 0 && column.field == 'selectItem') {
+                    target.hasSelectItem = true;
+                    $th = $('<th style="width:36px"></th>');
+                } else {
+                    $th = $('<th style="' + ((column.width) ? ('width:' + column.width) : '') + '" class="' + column.field + '_cls"></th>');
+                }
+                if((!target.isFixWidth)&& column.width){
+                    target.isFixWidth = column.width.indexOf("px")>-1?true:false;
+                }
+                $th.text(column.title);
+                $thr.append($th);
+            });
+            var $thead = $('<thead class="treetable-thead"></thead>');
+            $thead.append($thr);
+            target.append($thead);
+        }
+        // 初始化表体
+        var initBody = function() {
+            var $tbody = $('<tbody class="treetable-tbody"></tbody>');
+            target.append($tbody);
+            // 默认高度
+            if (options.height) {
+                $tbody.css("height", options.height);
+            }
+        }
+        // 初始化数据服务
+        var initServer = function(parms) {
+            // 加载数据前先清空
+            target.data_list = {};
+            target.data_obj = {};
+            var $tbody = target.find("tbody");
+            // 添加加载loading
+            var $loading = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">正在努力地加载数据中,请稍候……</div></td></tr>'
+            $tbody.html($loading);
+            if (options.url) {
+                $.ajax({
+                    type: options.type,
+                    url: options.url,
+                    data: parms ? parms : options.ajaxParams,
+                    dataType: "JSON",
+                    success: function(data, textStatus, jqXHR) {
+                    	data = calculateObjectValue(options, options.responseHandler, [data], data);
+                        renderTable(data);
+                        calculateObjectValue(options, options.onLoadSuccess, [data], data);
+                    },
+                    error: function(xhr, textStatus) {
+                        var _errorMsg = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + xhr.responseText + '</div></td></tr>'
+                        $tbody.html(_errorMsg);
+                    },
+                });
+            } else {
+                renderTable(options.data);
+            }
+        }
+        // 加载完数据后渲染表格
+        var renderTable = function(data) {
+            var $tbody = target.find("tbody");
+            // 先清空
+            $tbody.html("");
+            if (!data || data.length <= 0) {
+                var _empty = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">没有找到匹配的记录</div></td></tr>'
+                $tbody.html(_empty);
+                return;
+            }
+            // 缓存并格式化数据
+            formatData(data);
+            // 获取所有根节点
+            var rootNode = target.data_list["_root_"];
+            // 开始绘制
+            if (rootNode) {
+                $.each(rootNode, function(i, item) {
+                    var _child_row_id = "row_id_" + i
+                    recursionNode(item, 1, _child_row_id, "row_root");
+                });
+            }
+            // 下边的操作主要是为了查询时让一些没有根节点的节点显示
+            $.each(data, function(i, item) {
+                if (!item.isShow) {
+                    var tr = renderRow(item, false, 1, "", "");
+                    $tbody.append(tr);
+                }
+            });
+            target.append($tbody);
+            registerExpanderEvent();
+            registerRowClickEvent();
+            initHiddenColumns();
+            // 动态设置表头宽度
+            autoTheadWidth()
+        }
+        // 动态设置表头宽度
+        var autoTheadWidth = function(initFlag) {
+            if(options.height>0){
+                var $thead = target.find("thead");
+                var $tbody = target.find("tbody");
+                var borderWidth = parseInt(target.css("border-left-width")) + parseInt(target.css("border-right-width"))
+                
+                $thead.css("width", $tbody.children(":first").width());
+                if(initFlag){
+                    var resizeWaiter = false;
+                    $(window).resize(function() {
+                        if(!resizeWaiter){
+                            resizeWaiter = true;
+                            setTimeout(function(){
+                                if(!target.isFixWidth){
+                                    $tbody.css("width", target.parent().width()-borderWidth);
+                                }
+                                $thead.css("width", $tbody.children(":first").width());
+                                resizeWaiter = false;
+                            }, 300);
+                        }
+                    });
+                }
+            }
+        
+        }
+        // 缓存并格式化数据
+        var formatData = function(data) {
+            var _root = options.rootIdValue ? options.rootIdValue : null;
+            // 父节点属性列表
+            var parentCodes = [];
+            var rootFlag = false;
+            $.each(data, function(index, item) {
+            	if($.inArray(item[options.parentCode], parentCodes) == -1){
+            		parentCodes.push(item[options.parentCode]);
+                }
+            });
+            $.each(data, function(index, item) {
+                // 添加一个默认属性,用来判断当前节点有没有被显示
+                item.isShow = false;
+                // 顶级节点校验判断,兼容0,'0','',null
+                var _defaultRootFlag = item[options.parentCode] == '0' ||
+                item[options.parentCode] == 0 ||
+                item[options.parentCode] == null ||
+                item[options.parentCode] == '' ||
+                $.inArray(item[options.code], parentCodes) > 0 && !rootFlag;
+                if (!item[options.parentCode] || (_root ? (item[options.parentCode] == options.rootIdValue) : _defaultRootFlag)) {
+                	rootFlag = true;
+                	if (!target.data_list["_root_"]) {
+                        target.data_list["_root_"] = [];
+                    }
+                    if (!target.data_obj["id_" + item[options.code]]) {
+                        target.data_list["_root_"].push(item);
+                    }
+                } else {
+                    if (!target.data_list["_n_" + item[options.parentCode]]) {
+                        target.data_list["_n_" + item[options.parentCode]] = [];
+                    }
+                    if (!target.data_obj["id_" + item[options.code]]) {
+                        target.data_list["_n_" + item[options.parentCode]].push(item);
+                    }
+                }
+                target.data_obj["id_" + item[options.code]] = item;
+            });
+        }
+        // 递归获取子节点并且设置子节点
+        var recursionNode = function(parentNode, lv, row_id, p_id) {
+            var $tbody = target.find("tbody");
+            var _ls = target.data_list["_n_" + parentNode[options.code]];
+            var $tr = renderRow(parentNode, _ls ? true : false, lv, row_id, p_id);
+            $tbody.append($tr);
+            if (_ls) {
+                $.each(_ls, function(i, item) {
+                    var _child_row_id = row_id + "_" + i
+                    recursionNode(item, (lv + 1), _child_row_id, row_id)
+                });
+            }
+        };
+        // 绘制行
+        var renderRow = function(item, isP, lv, row_id, p_id) {
+            // 标记已显示
+            item.isShow = true;
+            item.row_id = row_id;
+            item.p_id = p_id;
+            item.lv = lv;
+            var $tr = $('<tr id="' + row_id + '" pid="' + p_id + '"></tr>');
+            var _icon = options.expanderCollapsedClass;
+            if (options.expandAll) {
+                $tr.css("display", "table");
+                _icon = options.expanderExpandedClass;
+            } else if (lv == 1) {
+                $tr.css("display", "table");
+                _icon = (options.expandFirst) ? options.expanderExpandedClass : options.expanderCollapsedClass;
+            } else if (lv == 2) {
+                if (options.expandFirst) {
+                    $tr.css("display", "table");
+                } else {
+                    $tr.css("display", "none");
+                }
+                _icon = options.expanderCollapsedClass;
+            } else {
+                $tr.css("display", "none");
+                _icon = options.expanderCollapsedClass;
+            }
+            $.each(options.columns, function(index, column) {
+                // 判断有没有选择列
+                if (column.field == 'selectItem') {
+                    target.hasSelectItem = true;
+                    var $td = $('<td style="text-align:center;width:36px"></td>');
+                    if (column.radio) {
+                        var _ipt = $('<input name="select_item" type="radio" value="' + item[options.code] + '"></input>');
+                        $td.append(_ipt);
+                    }
+                    if (column.checkbox) {
+                        var _ipt = $('<input name="select_item" type="checkbox" value="' + item[options.code] + '"></input>');
+                        $td.append(_ipt);
+                    }
+                    $tr.append($td);
+                } else {
+                    var $td = $('<td name="' + column.field + '" class="' + column.field + '_cls"></td>');
+                    if(column.width){
+                        $td.css("width",column.width);
+                    }
+                    if(column.align){
+                        $td.css("text-align",column.align);
+                    }
+                    if(options.expandColumn == index){
+                        $td.css("text-align","left");
+                    }
+                    if(column.valign){
+                        $td.css("vertical-align",column.valign);
+                    }
+                    if(options.showTitle){
+                        $td.addClass("ellipsis");
+                    }
+                    // 增加formatter渲染
+                    if (column.formatter) {
+                        $td.html(column.formatter.call(this, getItemField(item, column.field), item, index));
+                    } else {
+                        if(options.showTitle){
+                            // 只在字段没有formatter时才添加title属性
+                            $td.attr("title",item[column.field]);
+                        }
+                        $td.text(getItemField(item, column.field));
+                    }
+                    if (options.expandColumn == index) {
+                        if (!isP) {
+                            $td.prepend('<span class="treetable-expander"></span>')
+                        } else {
+                            $td.prepend('<span class="treetable-expander ' + _icon + '"></span>')
+                        }
+                        for (var int = 0; int < (lv - 1); int++) {
+                            $td.prepend('<span class="treetable-indent"></span>')
+                        }
+                    }
+                    $tr.append($td);
+                }
+            });
+            return $tr;
+        }
+        // 检索信息按钮点击事件
+        var registerSearchBtnClickEvent = function(btn) {
+            $(btn).off('click').on('click', function () {
+                $(".search-collapse").slideToggle();
+            });
+        }
+        // 注册刷新按钮点击事件
+        var registerRefreshBtnClickEvent = function(btn) {
+            $(btn).off('click').on('click', function () {
+                target.refresh();
+            });
+        }
+        // 注册列选项事件
+        var registerColumnClickEvent = function() {
+            $(".bootstrap-tree-table .treetable-bars .columns label input").off('click').on('click', function () {
+                var $this = $(this);
+                if($this.prop('checked')){
+                    target.showColumn($(this).val());
+                }else{
+                    target.hideColumn($(this).val());
+                }
+            });
+        }
+        // 注册行点击选中事件
+        var registerRowClickEvent = function() {
+            target.find("tbody").find("tr").unbind();
+            target.find("tbody").find("tr").click(function() {
+                if (target.hasSelectItem) {
+                    var _ipt = $(this).find("input[name='select_item']");
+                    if (_ipt.attr("type") == "radio") {
+                        _ipt.prop('checked', true);
+                        target.find("tbody").find("tr").removeClass("treetable-selected");
+                        $(this).addClass("treetable-selected");
+                    } else if (_ipt.attr("type") == "checkbox") {
+                    	if (_ipt.prop('checked')) {
+                    		_ipt.prop('checked', true);
+                    		target.find("tbody").find("tr").removeClass("treetable-selected");
+                    		$(this).addClass("treetable-selected");
+                    	} else {
+                    		_ipt.prop('checked', false);
+                    		target.find("tbody").find("tr").removeClass("treetable-selected");
+                    	}
+                    } else {
+                        if (_ipt.prop('checked')) {
+                            _ipt.prop('checked', false);
+                            $(this).removeClass("treetable-selected");
+                        } else {
+                            _ipt.prop('checked', true);
+                            $(this).addClass("treetable-selected");
+                        }
+                    }
+                }
+            });
+        }
+        // 注册小图标点击事件--展开缩起
+        var registerExpanderEvent = function() {
+            target.find("tbody").find("tr").find(".treetable-expander").unbind();
+            target.find("tbody").find("tr").find(".treetable-expander").click(function() {
+                var _isExpanded = $(this).hasClass(options.expanderExpandedClass);
+                var _isCollapsed = $(this).hasClass(options.expanderCollapsedClass);
+                if (_isExpanded || _isCollapsed) {
+                    var tr = $(this).parent().parent();
+                    var row_id = tr.attr("id");
+                    var _ls = target.find("tbody").find("tr[id^='" + row_id + "_']"); //下所有
+                    if (_isExpanded) {
+                        $(this).removeClass(options.expanderExpandedClass);
+                        $(this).addClass(options.expanderCollapsedClass);
+                        if (_ls && _ls.length > 0) {
+                            $.each(_ls, function(index, item) {
+                                $(item).css("display", "none");
+                            });
+                        }
+                    } else {
+                        $(this).removeClass(options.expanderCollapsedClass);
+                        $(this).addClass(options.expanderExpandedClass);
+                        if (_ls && _ls.length > 0) {
+                            $.each(_ls, function(index, item) {
+                                // 父icon
+                                var _p_icon = $("#" + $(item).attr("pid")).children().eq(options.expandColumn).find(".treetable-expander");
+                                if (_p_icon.hasClass(options.expanderExpandedClass)) {
+                                    $(item).css("display", "table");
+                                }
+                            });
+                        }
+                    }
+                }
+            });
+        }
+        // 刷新数据
+        target.refresh = function(parms) {
+            if(parms){
+                target.lastAjaxParams=parms;
+            }
+            initServer(target.lastAjaxParams);
+        }
+        // 添加数据刷新表格
+        target.appendData = function(data) {
+            // 下边的操作主要是为了查询时让一些没有根节点的节点显示
+            $.each(data, function(i, item) {
+                var _data = target.data_obj["id_" + item[options.code]];
+                var _p_data = target.data_obj["id_" + item[options.parentCode]];
+                var _c_list = target.data_list["_n_" + item[options.parentCode]];
+                var row_id = ""; //行id
+                var p_id = ""; //父行id
+                var _lv = 1; //如果没有父就是1默认显示
+                var tr; //要添加行的对象
+                if (_data && _data.row_id && _data.row_id != "") {
+                    row_id = _data.row_id; // 如果已经存在了,就直接引用原来的
+                }
+                if (_p_data) {
+                    p_id = _p_data.row_id;
+                    if (row_id == "") {
+                        var _tmp = 0
+                        if (_c_list && _c_list.length > 0) {
+                            _tmp = _c_list.length;
+                        }
+                        row_id = _p_data.row_id + "_" + _tmp;
+                    }
+                    _lv = _p_data.lv + 1; //如果有父
+                    // 绘制行
+                    tr = renderRow(item, false, _lv, row_id, p_id);
+
+                    var _p_icon = $("#" + _p_data.row_id).children().eq(options.expandColumn).find(".treetable-expander");
+                    var _isExpanded = _p_icon.hasClass(options.expanderExpandedClass);
+                    var _isCollapsed = _p_icon.hasClass(options.expanderCollapsedClass);
+                    // 父节点有没有展开收缩按钮
+                    if (_isExpanded || _isCollapsed) {
+                        // 父节点展开状态显示新加行
+                        if (_isExpanded) {
+                            tr.css("display", "table");
+                        }
+                    } else {
+                        // 父节点没有展开收缩按钮则添加
+                        _p_icon.addClass(options.expanderCollapsedClass);
+                    }
+
+                    if (_data) {
+                        $("#" + _data.row_id).before(tr);
+                        $("#" + _data.row_id).remove();
+                    } else {
+                        // 计算父的同级下一行
+                        var _tmp_ls = _p_data.row_id.split("_");
+                        var _p_next = _p_data.row_id.substring(0, _p_data.row_id.length - 1) + (parseInt(_tmp_ls[_tmp_ls.length - 1]) + 1);
+                        // 画上
+                        $("#" + _p_next).before(tr);
+                    }
+                } else {
+                    tr = renderRow(item, false, _lv, row_id, p_id);
+                    if (_data) {
+                        $("#" + _data.row_id).before(tr);
+                        $("#" + _data.row_id).remove();
+                    } else {
+                        // 画上
+                        var tbody = target.find("tbody");
+                        tbody.append(tr);
+                    }
+                }
+                item.isShow = true;
+                // 缓存并格式化数据
+                formatData([item]);
+            });
+            registerExpanderEvent();
+            registerRowClickEvent();
+            initHiddenColumns();
+        }
+
+        // 展开/折叠指定的行
+        target.toggleRow=function(id) {
+            var _rowData = target.data_obj["id_" + id];
+            var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander");
+            $row_expander.trigger("click");
+        }
+        // 展开指定的行
+        target.expandRow=function(id) {
+            var _rowData = target.data_obj["id_" + id];
+            var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander");
+            var _isCollapsed = $row_expander.hasClass(target.options.expanderCollapsedClass);
+            if (_isCollapsed) {
+                $row_expander.trigger("click");
+            }
+        }
+        // 折叠 指定的行
+        target.collapseRow=function(id) {
+            var _rowData = target.data_obj["id_" + id];
+            var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander");
+            var _isExpanded = $row_expander.hasClass(target.options.expanderExpandedClass);
+            if (_isExpanded) {
+                $row_expander.trigger("click");
+            }
+        }
+        // 展开所有的行
+        target.expandAll=function() {
+            target.find("tbody").find("tr").find(".treetable-expander").each(function(i,n){
+                var _isCollapsed = $(n).hasClass(options.expanderCollapsedClass);
+                if (_isCollapsed) {
+                    $(n).trigger("click");
+                }
+            })
+        }
+        // 折叠所有的行
+        target.collapseAll=function() {
+            target.find("tbody").find("tr").find(".treetable-expander").each(function(i,n){
+                var _isExpanded = $(n).hasClass(options.expanderExpandedClass);
+                if (_isExpanded) {
+                    $(n).trigger("click");
+                }
+            })
+        }
+        // 显示指定列
+        target.showColumn=function(field,flag) {
+            var _index = $.inArray(field, target.hiddenColumns);
+            if (_index > -1) {
+                target.hiddenColumns.splice(_index, 1);
+            }
+            target.find("."+field+"_cls").show();
+            //是否更新列选项状态
+            if(flag&&options.showColumns){
+                var $input = $(".bootstrap-tree-table .treetable-bars .columns label").find("input[value='"+field+"']")
+                $input.prop("checked", 'checked');
+            }
+        }
+        // 隐藏指定列
+        target.hideColumn=function(field,flag) {
+            target.hiddenColumns.push(field);
+            target.find("."+field+"_cls").hide();
+            //是否更新列选项状态
+            if(flag&&options.showColumns){
+                var $input = $(".bootstrap-tree-table .treetable-bars .columns label").find("input[value='"+field+"']")
+                $input.prop("checked", '');
+            }
+        }
+        // ruoyi 解析数据,支持多层级访问
+        var getItemField = function (item, field) {
+            var value = item;
+
+            if (typeof field !== 'string' || item.hasOwnProperty(field)) {
+                return item[field];
+            }
+            var props = field.split('.');
+            for (var p in props) {
+                value = value && value[props[p]];
+            }
+            return value;
+        };
+        // ruoyi 发起对目标(target)函数的调用
+        var calculateObjectValue = function (self, name, args, defaultValue) {
+            var func = name;
+
+            if (typeof name === 'string') {
+                var names = name.split('.');
+
+                if (names.length > 1) {
+                    func = window;
+                    $.each(names, function (i, f) {
+                        func = func[f];
+                    });
+                } else {
+                    func = window[name];
+                }
+            }
+            if (typeof func === 'object') {
+                return func;
+            }
+            if (typeof func === 'function') {
+                return func.apply(self, args);
+            }
+            if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) {
+                return sprintf.apply(this, [name].concat(args));
+            }
+            return defaultValue;
+        };
+        // 初始化
+        init();
+        return target;
+    };
+
+    // 组件方法封装........
+    $.fn.bootstrapTreeTable.methods = {
+        // 为了兼容bootstrap-table的写法,统一返回数组,这里返回了表格显示列的数据
+        getSelections: function(target, data) {
+            // 所有被选中的记录input
+            var _ipt = target.find("tbody").find("tr").find("input[name='select_item']:checked");
+            var chk_value = [];
+            // 如果是radio
+            if (_ipt.attr("type") == "radio") {
+                var _data = target.data_obj["id_" + _ipt.val()];
+                chk_value.push(_data);
+            } else {
+                _ipt.each(function(_i, _item) {
+                    var _data = target.data_obj["id_" + $(_item).val()];
+                    chk_value.push(_data);
+                });
+            }
+            return chk_value;
+        },
+        // 刷新记录
+        refresh: function(target, parms) {
+            if (parms) {
+                target.refresh(parms);
+            } else {
+                target.refresh();
+            }
+        },
+        // 添加数据到表格
+        appendData: function(target, data) {
+            if (data) {
+                target.appendData(data);
+            }
+        },
+        // 展开/折叠指定的行
+        toggleRow: function(target, id) {
+            target.toggleRow(id);
+        },
+        // 展开指定的行
+        expandRow: function(target, id) {
+            target.expandRow(id);
+        },
+        // 折叠 指定的行
+        collapseRow: function(target, id) {
+            target.collapseRow(id);
+        },
+        // 展开所有的行
+        expandAll: function(target) {
+            target.expandAll();
+        },
+        // 折叠所有的行
+        collapseAll: function(target) {
+            target.collapseAll();
+        },
+        // 显示指定列
+        showColumn: function(target,field) {
+            target.showColumn(field,true);
+        },
+        // 隐藏指定列
+        hideColumn: function(target,field) {
+            target.hideColumn(field,true);
+        }
+        // 组件的其他方法也可以进行类似封装........
+    };
+
+    $.fn.bootstrapTreeTable.defaults = {
+        code: 'code',              // 选取记录返回的值,用于设置父子关系
+        parentCode: 'parentCode',  // 用于设置父子关系
+        rootIdValue: null,         // 设置根节点id值----可指定根节点,默认为null,"",0,"0"
+        data: null,                // 构造table的数据集合
+        type: "GET",               // 请求数据的ajax类型
+        url: null,                 // 请求数据的ajax的url
+        ajaxParams: {},            // 请求数据的ajax的data属性
+        expandColumn: 0,           // 在哪一列上面显示展开按钮
+        expandAll: false,          // 是否全部展开
+        expandFirst: true,         // 是否默认第一级展开--expandAll为false时生效
+        striped: false,            // 是否各行渐变色
+        bordered: true,            // 是否显示边框
+        hover: true,               // 是否鼠标悬停
+        condensed: false,          // 是否紧缩表格
+        columns: [],               // 列
+        toolbar: null,             // 顶部工具条
+        height: 0,                 // 表格高度
+        showTitle: true,           // 是否采用title属性显示字段内容(被formatter格式化的字段不会显示)
+        showSearch: true,          // 是否显示检索信息
+        showColumns: true,         // 是否显示内容列下拉框
+        showRefresh: true,         // 是否显示刷新按钮
+        expanderExpandedClass: 'glyphicon glyphicon-chevron-down', // 展开的按钮的图标
+        expanderCollapsedClass: 'glyphicon glyphicon-chevron-right', // 缩起的按钮的图标
+        responseHandler: function(res) {
+            return false;
+        },
+        onLoadSuccess: function(res) {
+            return false;
+        }
+    };
+})(jQuery);

+ 304 - 0
leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.css

@@ -0,0 +1,304 @@
+/*!
+ * Cropper.js v1.5.7
+ * https://fengyuanchen.github.io/cropperjs
+ *
+ * Copyright 2015-present Chen Fengyuan
+ * Released under the MIT license
+ *
+ * Date: 2020-05-23T05:22:57.283Z
+ */
+
+.cropper-container {
+  direction: ltr;
+  font-size: 0;
+  line-height: 0;
+  position: relative;
+  -ms-touch-action: none;
+  touch-action: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+.cropper-container img {
+  display: block;
+  height: 100%;
+  image-orientation: 0deg;
+  max-height: none !important;
+  max-width: none !important;
+  min-height: 0 !important;
+  min-width: 0 !important;
+  width: 100%;
+}
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+  bottom: 0;
+  left: 0;
+  position: absolute;
+  right: 0;
+  top: 0;
+}
+
+.cropper-wrap-box,
+.cropper-canvas {
+  overflow: hidden;
+}
+
+.cropper-drag-box {
+  background-color: #fff;
+  opacity: 0;
+}
+
+.cropper-modal {
+  background-color: #000;
+  opacity: 0.5;
+}
+
+.cropper-view-box {
+  display: block;
+  height: 100%;
+  outline: 1px solid #39f;
+  outline-color: rgba(51, 153, 255, 0.75);
+  overflow: hidden;
+  width: 100%;
+}
+
+.cropper-dashed {
+  border: 0 dashed #eee;
+  display: block;
+  opacity: 0.5;
+  position: absolute;
+}
+
+.cropper-dashed.dashed-h {
+  border-bottom-width: 1px;
+  border-top-width: 1px;
+  height: calc(100% / 3);
+  left: 0;
+  top: calc(100% / 3);
+  width: 100%;
+}
+
+.cropper-dashed.dashed-v {
+  border-left-width: 1px;
+  border-right-width: 1px;
+  height: 100%;
+  left: calc(100% / 3);
+  top: 0;
+  width: calc(100% / 3);
+}
+
+.cropper-center {
+  display: block;
+  height: 0;
+  left: 50%;
+  opacity: 0.75;
+  position: absolute;
+  top: 50%;
+  width: 0;
+}
+
+.cropper-center::before,
+.cropper-center::after {
+  background-color: #eee;
+  content: ' ';
+  display: block;
+  position: absolute;
+}
+
+.cropper-center::before {
+  height: 1px;
+  left: -3px;
+  top: 0;
+  width: 7px;
+}
+
+.cropper-center::after {
+  height: 7px;
+  left: 0;
+  top: -3px;
+  width: 1px;
+}
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+  display: block;
+  height: 100%;
+  opacity: 0.1;
+  position: absolute;
+  width: 100%;
+}
+
+.cropper-face {
+  background-color: #fff;
+  left: 0;
+  top: 0;
+}
+
+.cropper-line {
+  background-color: #39f;
+}
+
+.cropper-line.line-e {
+  cursor: ew-resize;
+  right: -3px;
+  top: 0;
+  width: 5px;
+}
+
+.cropper-line.line-n {
+  cursor: ns-resize;
+  height: 5px;
+  left: 0;
+  top: -3px;
+}
+
+.cropper-line.line-w {
+  cursor: ew-resize;
+  left: -3px;
+  top: 0;
+  width: 5px;
+}
+
+.cropper-line.line-s {
+  bottom: -3px;
+  cursor: ns-resize;
+  height: 5px;
+  left: 0;
+}
+
+.cropper-point {
+  background-color: #39f;
+  height: 5px;
+  opacity: 0.75;
+  width: 5px;
+}
+
+.cropper-point.point-e {
+  cursor: ew-resize;
+  margin-top: -3px;
+  right: -3px;
+  top: 50%;
+}
+
+.cropper-point.point-n {
+  cursor: ns-resize;
+  left: 50%;
+  margin-left: -3px;
+  top: -3px;
+}
+
+.cropper-point.point-w {
+  cursor: ew-resize;
+  left: -3px;
+  margin-top: -3px;
+  top: 50%;
+}
+
+.cropper-point.point-s {
+  bottom: -3px;
+  cursor: s-resize;
+  left: 50%;
+  margin-left: -3px;
+}
+
+.cropper-point.point-ne {
+  cursor: nesw-resize;
+  right: -3px;
+  top: -3px;
+}
+
+.cropper-point.point-nw {
+  cursor: nwse-resize;
+  left: -3px;
+  top: -3px;
+}
+
+.cropper-point.point-sw {
+  bottom: -3px;
+  cursor: nesw-resize;
+  left: -3px;
+}
+
+.cropper-point.point-se {
+  bottom: -3px;
+  cursor: nwse-resize;
+  height: 20px;
+  opacity: 1;
+  right: -3px;
+  width: 20px;
+}
+
+@media (min-width: 768px) {
+  .cropper-point.point-se {
+    height: 15px;
+    width: 15px;
+  }
+}
+
+@media (min-width: 992px) {
+  .cropper-point.point-se {
+    height: 10px;
+    width: 10px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .cropper-point.point-se {
+    height: 5px;
+    opacity: 0.75;
+    width: 5px;
+  }
+}
+
+.cropper-point.point-se::before {
+  background-color: #39f;
+  bottom: -50%;
+  content: ' ';
+  display: block;
+  height: 200%;
+  opacity: 0;
+  position: absolute;
+  right: -50%;
+  width: 200%;
+}
+
+.cropper-invisible {
+  opacity: 0;
+}
+
+.cropper-bg {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
+}
+
+.cropper-hide {
+  display: block;
+  height: 0;
+  position: absolute;
+  width: 0;
+}
+
+.cropper-hidden {
+  display: none !important;
+}
+
+.cropper-move {
+  cursor: move;
+}
+
+.cropper-crop {
+  cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+  cursor: not-allowed;
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 3616 - 0
leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 9 - 0
leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 10 - 0
leiSP-admin/src/main/resources/static/ajax/libs/cropper/cropper.min.js


+ 406 - 0
leiSP-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js

@@ -0,0 +1,406 @@
+/*!
+ * jQuery cxSelect
+ * @name jquery.cxselect.js
+ * @version 1.4.2
+ * @date 2017-09-26
+ * @author ciaoca
+ * @email ciaoca@gmail.com
+ * @site https://github.com/ciaoca/cxSelect
+ * @license Released under the MIT license
+ */
+(function(factory) {
+  if (typeof define === 'function' && define.amd) {
+    define(['jquery'], factory);
+  } else {
+    factory(window.jQuery || window.Zepto || window.$);
+  };
+}(function($) {
+  var cxSelect = function() {
+    var self = this;
+    var dom, settings, callback;
+
+    // 分配参数
+    for (var i = 0, l = arguments.length; i < l; i++) {
+      if (cxSelect.isJquery(arguments[i]) || cxSelect.isZepto(arguments[i])) {
+        dom = arguments[i];
+      } else if (cxSelect.isElement(arguments[i])) {
+        dom = $(arguments[i]);
+      } else if (typeof arguments[i] === 'function') {
+        callback = arguments[i];
+      } else if (typeof arguments[i] === 'object') {
+        settings = arguments[i];
+      };
+    };
+
+    var api = new cxSelect.init(dom, settings);
+
+    if (typeof callback === 'function') {
+      callback(api);
+    };
+
+    return api;
+  };
+
+  cxSelect.isElement = function(o){
+    if (o && (typeof HTMLElement === 'function' || typeof HTMLElement === 'object') && o instanceof HTMLElement) {
+      return true;
+    } else {
+      return (o && o.nodeType && o.nodeType === 1) ? true : false;
+    };
+  };
+
+  cxSelect.isJquery = function(o){
+    return (o && o.length && (typeof jQuery === 'function' || typeof jQuery === 'object') && o instanceof jQuery) ? true : false;
+  };
+
+  cxSelect.isZepto = function(o){
+    return (o && o.length && (typeof Zepto === 'function' || typeof Zepto === 'object') && Zepto.zepto.isZ(o)) ? true : false;
+  };
+
+  cxSelect.getIndex = function(n, required) {
+    return required ? n : n - 1;
+  };
+
+  cxSelect.getData = function(data, space) {
+    if (typeof space === 'string' && space.length) {
+      space = space.split('.');
+      for (var i = 0, l = space.length; i < l; i++) {
+        data = data[space[i]];
+      };
+    };
+    return data;
+  };
+
+  cxSelect.init = function(dom, settings) {
+    var self = this;
+
+    if (!cxSelect.isJquery(dom) && !cxSelect.isZepto(dom)) {return};
+
+    var theSelect = {
+      dom: {
+        box: dom
+      }
+    };
+
+    self.attach = cxSelect.attach.bind(theSelect);
+    self.detach = cxSelect.detach.bind(theSelect);
+    self.setOptions = cxSelect.setOptions.bind(theSelect);
+    self.clear = cxSelect.clear.bind(theSelect);
+
+    theSelect.changeEvent = function() {
+      cxSelect.selectChange.call(theSelect, this.className);
+    };
+
+    theSelect.settings = $.extend({}, $.cxSelect.defaults, settings, {
+      url: theSelect.dom.box.data('url'),
+      emptyStyle: theSelect.dom.box.data('emptyStyle'),
+      required: theSelect.dom.box.data('required'),
+      firstTitle: theSelect.dom.box.data('firstTitle'),
+      firstValue: theSelect.dom.box.data('firstValue'),
+      jsonSpace: theSelect.dom.box.data('jsonSpace'),
+      jsonName: theSelect.dom.box.data('jsonName'),
+      jsonValue: theSelect.dom.box.data('jsonValue'),
+      jsonSub: theSelect.dom.box.data('jsonSub')
+    });
+
+    var _dataSelects = theSelect.dom.box.data('selects');
+
+    if (typeof _dataSelects === 'string' && _dataSelects.length) {
+      theSelect.settings.selects = _dataSelects.split(',');
+    };
+
+    self.setOptions();
+    self.attach();
+
+    // 使用独立接口获取数据
+    if (!theSelect.settings.url && !theSelect.settings.data) {
+      cxSelect.start.apply(theSelect);
+
+    // 设置自定义数据
+    } else if ($.isArray(theSelect.settings.data)) {
+      cxSelect.start.call(theSelect, theSelect.settings.data);
+
+    // 设置 URL,通过 Ajax 获取数据
+    } else if (typeof theSelect.settings.url === 'string' && theSelect.settings.url.length) {
+      $.getJSON(theSelect.settings.url, function(json) {
+        cxSelect.start.call(theSelect, json);
+      });
+    };
+  };
+
+  // 设置参数
+  cxSelect.setOptions = function(opts) {
+    var self = this;
+
+    if (opts) {
+      $.extend(self.settings, opts);
+    };
+
+    // 初次或重设选择器组
+    if (!$.isArray(self.selectArray) || !self.selectArray.length || (opts && opts.selects)) {
+      self.selectArray = [];
+
+      if ($.isArray(self.settings.selects) && self.settings.selects.length) {
+        var _tempSelect;
+
+        for (var i = 0, l = self.settings.selects.length; i < l; i++) {
+          _tempSelect = self.dom.box.find('select.' + self.settings.selects[i]);
+
+          if (!_tempSelect || !_tempSelect.length) {break};
+
+          self.selectArray.push(_tempSelect);
+        };
+      };
+    };
+
+    if (opts) {
+      if (!$.isArray(opts.data) && typeof opts.url === 'string' && opts.url.length) {
+        $.getJSON(self.settings.url, function(json) {
+          cxSelect.start.call(self, json);
+        });
+
+      } else {
+        cxSelect.start.call(self, opts.data);
+      };
+    };
+  };
+
+  // 绑定
+  cxSelect.attach = function() {
+    var self = this;
+
+    if (!self.attachStatus) {
+      self.dom.box.on('change', 'select', self.changeEvent);
+    };
+
+    if (typeof self.attachStatus === 'boolean') {
+      cxSelect.start.call(self);
+    };
+
+    self.attachStatus = true;
+  };
+
+  // 移除绑定
+  cxSelect.detach = function() {
+    var self = this;
+    self.dom.box.off('change', 'select', self.changeEvent);
+    self.attachStatus = false;
+  };
+
+  // 清空选项
+  cxSelect.clear = function(index) {
+    var self = this;
+    var _style = {
+      display: '',
+      visibility: ''
+    };
+
+    index = isNaN(index) ? 0 : index;
+
+    // 清空后面的 select
+    for (var i = index, l = self.selectArray.length; i < l; i++) {
+      self.selectArray[i].empty().prop('disabled', true);
+
+      if (self.settings.emptyStyle === 'none') {
+        _style.display = 'none';
+      } else if (self.settings.emptyStyle === 'hidden') {
+        _style.visibility = 'hidden';
+      };
+
+      self.selectArray[i].css(_style);
+    };
+  };
+
+  cxSelect.start = function(data) {
+    var self = this;
+
+    if ($.isArray(data)) {
+      self.settings.data = cxSelect.getData(data, self.settings.jsonSpace);
+    };
+
+    if (!self.selectArray.length) {return};
+
+    // 保存默认值
+    for (var i = 0, l = self.selectArray.length; i < l; i++) {
+      if (typeof self.selectArray[i].attr('data-value') !== 'string' && self.selectArray[i][0].options.length) {
+        self.selectArray[i].attr('data-value', self.selectArray[i].val());
+      };
+    };
+
+    if (self.settings.data || (typeof self.selectArray[0].data('url') === 'string' && self.selectArray[0].data('url').length)) {
+      cxSelect.getOptionData.call(self, 0);
+    } else if (self.selectArray[0][0].options.length && typeof self.selectArray[0].attr('data-value') === 'string' && self.selectArray[0].attr('data-value').length) {
+      self.selectArray[0].val(self.selectArray[0].attr('data-value'));
+      cxSelect.getOptionData.call(self, 1);
+    } else {
+      self.selectArray[0].prop('disabled', false).css({
+        'display': '',
+        'visibility': ''
+      });
+    };
+  };
+
+  // 获取选项数据
+  cxSelect.getOptionData = function(index) {
+    var self = this;
+
+    if (typeof index !== 'number' || isNaN(index) || index < 0 || index >= self.selectArray.length) {return};
+
+    var _indexPrev = index - 1;
+    var _select = self.selectArray[index];
+    var _selectData;
+    var _valueIndex;
+    var _dataUrl = _select.data('url');
+    var _jsonSpace = typeof _select.data('jsonSpace') === 'undefined' ? self.settings.jsonSpace : _select.data('jsonSpace');
+    var _query = {};
+    var _queryName;
+    var _selectName;
+    var _selectValue;
+
+    cxSelect.clear.call(self, index);
+
+    // 使用独立接口
+    if (typeof _dataUrl === 'string' && _dataUrl.length) {
+      if (index > 0) {
+        for (var i = 0, j = 1; i < index; i++, j++) {
+          _queryName = self.selectArray[j].data('queryName');
+          _selectName = self.selectArray[i].attr('name');
+          _selectValue = self.selectArray[i].val();
+
+          if (typeof _queryName === 'string' && _queryName.length) {
+            _query[_queryName] = _selectValue;
+          } else if (typeof _selectName === 'string' && _selectName.length) {
+            _query[_selectName] = _selectValue;
+          };
+        };
+      };
+
+      $.getJSON(_dataUrl, _query, function(json) {
+        _selectData = cxSelect.getData(json, _jsonSpace);
+
+        cxSelect.buildOption.call(self, index, _selectData);
+      });
+
+    // 使用整合数据
+    } else if (self.settings.data && typeof self.settings.data === 'object') {
+      _selectData = self.settings.data;
+
+      for (var i = 0; i < index; i++) {
+        _valueIndex = cxSelect.getIndex(self.selectArray[i][0].selectedIndex, typeof self.selectArray[i].data('required') === 'boolean' ? self.selectArray[i].data('required') : self.settings.required);
+
+        if (typeof _selectData[_valueIndex] === 'object' && $.isArray(_selectData[_valueIndex][self.settings.jsonSub]) && _selectData[_valueIndex][self.settings.jsonSub].length) {
+          _selectData = _selectData[_valueIndex][self.settings.jsonSub];
+        } else {
+          _selectData = null;
+          break;
+        };
+      };
+
+      cxSelect.buildOption.call(self, index, _selectData);
+    };
+  };
+
+  // 构建选项列表
+  cxSelect.buildOption = function(index, data) {
+    var self = this;
+
+    var _select = self.selectArray[index];
+    var _required = typeof _select.data('required') === 'boolean' ? _select.data('required') : self.settings.required;
+    var _firstTitle = typeof _select.data('firstTitle') === 'undefined' ? self.settings.firstTitle : _select.data('firstTitle');
+    var _firstValue = typeof _select.data('firstValue') === 'undefined' ? self.settings.firstValue : _select.data('firstValue');
+    var _jsonName = typeof _select.data('jsonName') === 'undefined' ? self.settings.jsonName : _select.data('jsonName');
+    var _jsonValue = typeof _select.data('jsonValue') === 'undefined' ? self.settings.jsonValue : _select.data('jsonValue');
+
+    if (!$.isArray(data)) {return};
+
+    var _html = !_required ? '<option value="' + String(_firstValue) + '">' + String(_firstTitle) + '</option>' : '';
+
+    // 区分标题、值的数据
+    if (typeof _jsonName === 'string' && _jsonName.length) {
+      // 无值字段时使用标题作为值
+      if (typeof _jsonValue !== 'string' || !_jsonValue.length) {
+        _jsonValue = _jsonName;
+      };
+
+      for (var i = 0, l = data.length; i < l; i++) {
+        _html += '<option value="' + String(data[i][_jsonValue]) + '">' + String(data[i][_jsonName]) + '</option>';
+      };
+
+    // 数组即为值的数据
+    } else {
+      for (var i = 0, l = data.length; i < l; i++) {
+        _html += '<option value="' + String(data[i]) + '">' + String(data[i]) + '</option>';
+      };
+    };
+
+    _select.html(_html).prop('disabled', false).css({
+      'display': '',
+      'visibility': ''
+    });
+
+    // 初次加载设置默认值
+    if (typeof _select.attr('data-value') === 'string') {
+      _select.val(String(_select.attr('data-value'))).removeAttr('data-value');
+
+      if (_select[0].selectedIndex < 0) {
+        _select[0].options[0].selected = true;
+      };
+    };
+
+    if (_required || _select[0].selectedIndex > 0) {
+      _select.trigger('change');
+    };
+
+  };
+
+  // 改变选择时的处理
+  cxSelect.selectChange = function(name) {
+    var self = this;
+
+    if (typeof name !== 'string' || !name.length) {return};
+
+    var index;
+
+    name = name.replace(/\s+/g, ',');
+    name = ',' + name + ',';
+
+    // 获取当前 select 位置
+    for (var i = 0, l = self.selectArray.length; i < l; i++) {
+      if (name.indexOf(',' + self.settings.selects[i] + ',') > -1) {
+        index = i;
+        break;
+      };
+    };
+
+    if (typeof index === 'number' && index > -1) {
+      index += 1;
+      cxSelect.getOptionData.call(self, index);
+    };
+  };
+
+  $.cxSelect = function() {
+    return cxSelect.apply(this, arguments);
+  };
+
+  // 默认值
+  $.cxSelect.defaults = {
+    selects: [],            // 下拉选框组
+    url: null,              // 列表数据文件路径(URL)或数组数据
+    data: null,             // 自定义数据
+    emptyStyle: null,       // 无数据状态显示方式
+    required: false,        // 是否为必选
+    firstTitle: '请选择',    // 第一个选项的标题
+    firstValue: '',         // 第一个选项的值
+    jsonSpace: '',          // 数据命名空间
+    jsonName: 'n',          // 数据标题字段名称
+    jsonValue: '',          // 数据值字段名称
+    jsonSub: 's'            // 子集数据字段名称
+  };
+
+  $.fn.cxSelect = function(settings, callback) {
+    this.each(function(i) {
+      $.cxSelect(this, settings, callback);
+    });
+    return this;
+  };
+}));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 11 - 0
leiSP-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.min.js


+ 418 - 0
leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.css

@@ -0,0 +1,418 @@
+/*!
+ * Datetimepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.datetimepicker {
+	padding: 4px;
+	margin-top: 1px;
+	-webkit-border-radius: 4px;
+	-moz-border-radius: 4px;
+	border-radius: 4px;
+	direction: ltr;
+}
+
+.datetimepicker-inline {
+	width: 220px;
+}
+
+.datetimepicker.datetimepicker-rtl {
+	direction: rtl;
+}
+
+.datetimepicker.datetimepicker-rtl table tr td span {
+	float: right;
+}
+
+.datetimepicker-dropdown, .datetimepicker-dropdown-left {
+	top: 0;
+	left: 0;
+}
+
+[class*=" datetimepicker-dropdown"]:before {
+	content: '';
+	display: inline-block;
+	border-left: 7px solid transparent;
+	border-right: 7px solid transparent;
+	border-bottom: 7px solid #cccccc;
+	border-bottom-color: rgba(0, 0, 0, 0.2);
+	position: absolute;
+}
+
+[class*=" datetimepicker-dropdown"]:after {
+	content: '';
+	display: inline-block;
+	border-left: 6px solid transparent;
+	border-right: 6px solid transparent;
+	border-bottom: 6px solid #ffffff;
+	position: absolute;
+}
+
+[class*=" datetimepicker-dropdown-top"]:before {
+	content: '';
+	display: inline-block;
+	border-left: 7px solid transparent;
+	border-right: 7px solid transparent;
+	border-top: 7px solid #cccccc;
+	border-top-color: rgba(0, 0, 0, 0.2);
+	border-bottom: 0;
+}
+
+[class*=" datetimepicker-dropdown-top"]:after {
+	content: '';
+	display: inline-block;
+	border-left: 6px solid transparent;
+	border-right: 6px solid transparent;
+	border-top: 6px solid #ffffff;
+	border-bottom: 0;
+}
+
+.datetimepicker-dropdown-bottom-left:before {
+	top: -7px;
+	right: 6px;
+}
+
+.datetimepicker-dropdown-bottom-left:after {
+	top: -6px;
+	right: 7px;
+}
+
+.datetimepicker-dropdown-bottom-right:before {
+	top: -7px;
+	left: 6px;
+}
+
+.datetimepicker-dropdown-bottom-right:after {
+	top: -6px;
+	left: 7px;
+}
+
+.datetimepicker-dropdown-top-left:before {
+	bottom: -7px;
+	right: 6px;
+}
+
+.datetimepicker-dropdown-top-left:after {
+	bottom: -6px;
+	right: 7px;
+}
+
+.datetimepicker-dropdown-top-right:before {
+	bottom: -7px;
+	left: 6px;
+}
+
+.datetimepicker-dropdown-top-right:after {
+	bottom: -6px;
+	left: 7px;
+}
+
+.datetimepicker > div {
+	display: none;
+}
+
+.datetimepicker.minutes div.datetimepicker-minutes {
+	display: block;
+}
+
+.datetimepicker.hours div.datetimepicker-hours {
+	display: block;
+}
+
+.datetimepicker.days div.datetimepicker-days {
+	display: block;
+}
+
+.datetimepicker.months div.datetimepicker-months {
+	display: block;
+}
+
+.datetimepicker.years div.datetimepicker-years {
+	display: block;
+}
+
+.datetimepicker table {
+	margin: 0;
+}
+
+.datetimepicker  td,
+.datetimepicker th {
+	text-align: center;
+	width: 20px;
+	height: 20px;
+	-webkit-border-radius: 4px;
+	-moz-border-radius: 4px;
+	border-radius: 4px;
+	border: none;
+}
+
+.table-striped .datetimepicker table tr td,
+.table-striped .datetimepicker table tr th {
+	background-color: transparent;
+}
+
+.datetimepicker table tr td.minute:hover {
+	background: #eeeeee;
+	cursor: pointer;
+}
+
+.datetimepicker table tr td.hour:hover {
+	background: #eeeeee;
+	cursor: pointer;
+}
+
+.datetimepicker table tr td.day:hover {
+	background: #eeeeee;
+	cursor: pointer;
+}
+
+.datetimepicker table tr td.old,
+.datetimepicker table tr td.new {
+	color: #999999;
+}
+
+.datetimepicker table tr td.disabled,
+.datetimepicker table tr td.disabled:hover {
+	background: none;
+	color: #999999;
+	cursor: default;
+}
+
+.datetimepicker table tr td.today,
+.datetimepicker table tr td.today:hover,
+.datetimepicker table tr td.today.disabled,
+.datetimepicker table tr td.today.disabled:hover {
+	background-color: #fde19a;
+	background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
+	background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
+	background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
+	background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
+	background-image: linear-gradient(to bottom, #fdd49a, #fdf59a);
+	background-repeat: repeat-x;
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
+	border-color: #fdf59a #fdf59a #fbed50;
+	border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+	filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.datetimepicker table tr td.today:hover,
+.datetimepicker table tr td.today:hover:hover,
+.datetimepicker table tr td.today.disabled:hover,
+.datetimepicker table tr td.today.disabled:hover:hover,
+.datetimepicker table tr td.today:active,
+.datetimepicker table tr td.today:hover:active,
+.datetimepicker table tr td.today.disabled:active,
+.datetimepicker table tr td.today.disabled:hover:active,
+.datetimepicker table tr td.today.active,
+.datetimepicker table tr td.today:hover.active,
+.datetimepicker table tr td.today.disabled.active,
+.datetimepicker table tr td.today.disabled:hover.active,
+.datetimepicker table tr td.today.disabled,
+.datetimepicker table tr td.today:hover.disabled,
+.datetimepicker table tr td.today.disabled.disabled,
+.datetimepicker table tr td.today.disabled:hover.disabled,
+.datetimepicker table tr td.today[disabled],
+.datetimepicker table tr td.today:hover[disabled],
+.datetimepicker table tr td.today.disabled[disabled],
+.datetimepicker table tr td.today.disabled:hover[disabled] {
+	background-color: #fdf59a;
+}
+
+.datetimepicker table tr td.today:active,
+.datetimepicker table tr td.today:hover:active,
+.datetimepicker table tr td.today.disabled:active,
+.datetimepicker table tr td.today.disabled:hover:active,
+.datetimepicker table tr td.today.active,
+.datetimepicker table tr td.today:hover.active,
+.datetimepicker table tr td.today.disabled.active,
+.datetimepicker table tr td.today.disabled:hover.active {
+	background-color: #fbf069;
+}
+
+.datetimepicker table tr td.active,
+.datetimepicker table tr td.active:hover,
+.datetimepicker table tr td.active.disabled,
+.datetimepicker table tr td.active.disabled:hover {
+	background-color: #006dcc;
+	background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+	background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+	background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+	background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+	background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+	background-repeat: repeat-x;
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+	border-color: #0044cc #0044cc #002a80;
+	border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+	filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+	color: #ffffff;
+	text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.datetimepicker table tr td.active:hover,
+.datetimepicker table tr td.active:hover:hover,
+.datetimepicker table tr td.active.disabled:hover,
+.datetimepicker table tr td.active.disabled:hover:hover,
+.datetimepicker table tr td.active:active,
+.datetimepicker table tr td.active:hover:active,
+.datetimepicker table tr td.active.disabled:active,
+.datetimepicker table tr td.active.disabled:hover:active,
+.datetimepicker table tr td.active.active,
+.datetimepicker table tr td.active:hover.active,
+.datetimepicker table tr td.active.disabled.active,
+.datetimepicker table tr td.active.disabled:hover.active,
+.datetimepicker table tr td.active.disabled,
+.datetimepicker table tr td.active:hover.disabled,
+.datetimepicker table tr td.active.disabled.disabled,
+.datetimepicker table tr td.active.disabled:hover.disabled,
+.datetimepicker table tr td.active[disabled],
+.datetimepicker table tr td.active:hover[disabled],
+.datetimepicker table tr td.active.disabled[disabled],
+.datetimepicker table tr td.active.disabled:hover[disabled] {
+	background-color: #0044cc;
+}
+
+.datetimepicker table tr td.active:active,
+.datetimepicker table tr td.active:hover:active,
+.datetimepicker table tr td.active.disabled:active,
+.datetimepicker table tr td.active.disabled:hover:active,
+.datetimepicker table tr td.active.active,
+.datetimepicker table tr td.active:hover.active,
+.datetimepicker table tr td.active.disabled.active,
+.datetimepicker table tr td.active.disabled:hover.active {
+	background-color: #003399;
+}
+
+.datetimepicker table tr td span {
+	display: block;
+	width: 23%;
+	height: 54px;
+	line-height: 54px;
+	float: left;
+	margin: 1%;
+	cursor: pointer;
+	-webkit-border-radius: 4px;
+	-moz-border-radius: 4px;
+	border-radius: 4px;
+}
+
+.datetimepicker .datetimepicker-hours span {
+	height: 26px;
+	line-height: 26px;
+}
+
+.datetimepicker .datetimepicker-hours table tr td span.hour_am,
+.datetimepicker .datetimepicker-hours table tr td span.hour_pm {
+	width: 14.6%;
+}
+
+.datetimepicker .datetimepicker-hours fieldset legend,
+.datetimepicker .datetimepicker-minutes fieldset legend {
+	margin-bottom: inherit;
+	line-height: 30px;
+}
+
+.datetimepicker .datetimepicker-minutes span {
+	height: 26px;
+	line-height: 26px;
+}
+
+.datetimepicker table tr td span:hover {
+	background: #eeeeee;
+}
+
+.datetimepicker table tr td span.disabled,
+.datetimepicker table tr td span.disabled:hover {
+	background: none;
+	color: #999999;
+	cursor: default;
+}
+
+.datetimepicker table tr td span.active,
+.datetimepicker table tr td span.active:hover,
+.datetimepicker table tr td span.active.disabled,
+.datetimepicker table tr td span.active.disabled:hover {
+	background-color: #006dcc;
+	background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+	background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+	background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+	background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+	background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+	background-repeat: repeat-x;
+	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+	border-color: #0044cc #0044cc #002a80;
+	border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+	filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+	color: #ffffff;
+	text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.datetimepicker table tr td span.active:hover,
+.datetimepicker table tr td span.active:hover:hover,
+.datetimepicker table tr td span.active.disabled:hover,
+.datetimepicker table tr td span.active.disabled:hover:hover,
+.datetimepicker table tr td span.active:active,
+.datetimepicker table tr td span.active:hover:active,
+.datetimepicker table tr td span.active.disabled:active,
+.datetimepicker table tr td span.active.disabled:hover:active,
+.datetimepicker table tr td span.active.active,
+.datetimepicker table tr td span.active:hover.active,
+.datetimepicker table tr td span.active.disabled.active,
+.datetimepicker table tr td span.active.disabled:hover.active,
+.datetimepicker table tr td span.active.disabled,
+.datetimepicker table tr td span.active:hover.disabled,
+.datetimepicker table tr td span.active.disabled.disabled,
+.datetimepicker table tr td span.active.disabled:hover.disabled,
+.datetimepicker table tr td span.active[disabled],
+.datetimepicker table tr td span.active:hover[disabled],
+.datetimepicker table tr td span.active.disabled[disabled],
+.datetimepicker table tr td span.active.disabled:hover[disabled] {
+	background-color: #0044cc;
+}
+
+.datetimepicker table tr td span.active:active,
+.datetimepicker table tr td span.active:hover:active,
+.datetimepicker table tr td span.active.disabled:active,
+.datetimepicker table tr td span.active.disabled:hover:active,
+.datetimepicker table tr td span.active.active,
+.datetimepicker table tr td span.active:hover.active,
+.datetimepicker table tr td span.active.disabled.active,
+.datetimepicker table tr td span.active.disabled:hover.active {
+	background-color: #003399;
+}
+
+.datetimepicker table tr td span.old {
+	color: #999999;
+}
+
+.datetimepicker th.switch {
+	width: 145px;
+}
+
+.datetimepicker th span.glyphicon {
+	pointer-events: none;
+}
+
+.datetimepicker thead tr:first-child th,
+.datetimepicker tfoot th {
+	cursor: pointer;
+}
+
+.datetimepicker thead tr:first-child th:hover,
+.datetimepicker tfoot th:hover {
+	background: #eeeeee;
+}
+
+.input-append.date .add-on i,
+.input-prepend.date .add-on i,
+.input-group.date .input-group-addon span {
+	cursor: pointer;
+	width: 14px;
+	height: 14px;
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1978 - 0
leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 9 - 0
leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
leiSP-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.min.js


+ 86 - 0
leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.css

@@ -0,0 +1,86 @@
+/*
+ *  Bootstrap Duallistbox - v3.0.7
+ *  A responsive dual listbox widget optimized for Twitter Bootstrap. It works on all modern browsers and on touch devices.
+ *  https://www.virtuosoft.eu/code/bootstrap-duallistbox/
+ *
+ *  Made by István Ujj-Mészáros
+ *  Under Apache License v2.0 License
+ */
+.bootstrap-duallistbox-container .buttons {
+  width: 100%;
+  margin-bottom: -1px;
+}
+
+.bootstrap-duallistbox-container label {
+  display: block;
+}
+
+.bootstrap-duallistbox-container .info {
+  display: inline-block;
+  margin-bottom: 5px;
+  font-size: 11px;
+}
+
+.bootstrap-duallistbox-container .clear1,
+.bootstrap-duallistbox-container .clear2 {
+  display: none;
+  font-size: 10px;
+}
+
+.bootstrap-duallistbox-container .box1.filtered .clear1,
+.bootstrap-duallistbox-container .box2.filtered .clear2 {
+  display: inline-block;
+}
+
+.bootstrap-duallistbox-container .move,
+.bootstrap-duallistbox-container .remove {
+  width: 60%;
+}
+
+.bootstrap-duallistbox-container .btn-group .btn {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.bootstrap-duallistbox-container select {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+
+.bootstrap-duallistbox-container .moveall,
+.bootstrap-duallistbox-container .removeall {
+  width: 40%;
+}
+
+.bootstrap-duallistbox-container.bs2compatible .btn-group > .btn + .btn {
+  margin-left: 0;
+}
+
+.bootstrap-duallistbox-container select {
+  width: 100%;
+  height: 300px;
+  padding: 0;
+}
+
+.bootstrap-duallistbox-container .filter {
+  display: inline-block;
+  width: 100%;
+  height: 31px;
+  margin: 0 0 5px 0;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+.bootstrap-duallistbox-container .filter.placeholder {
+  color: #aaa;
+}
+
+.bootstrap-duallistbox-container.moveonselect .move,
+.bootstrap-duallistbox-container.moveonselect .remove {
+  display:none;
+}
+
+.bootstrap-duallistbox-container.moveonselect .moveall,
+.bootstrap-duallistbox-container.moveonselect .removeall {
+  width: 100%;
+}

+ 841 - 0
leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.js

@@ -0,0 +1,841 @@
+/*
+ *  Bootstrap Duallistbox - v3.0.7
+ *  A responsive dual listbox widget optimized for Twitter Bootstrap. It works on all modern browsers and on touch devices.
+ *  https://www.virtuosoft.eu/code/bootstrap-duallistbox/
+ *
+ *  Made by István Ujj-Mészáros
+ *  Under Apache License v2.0 License
+ */
+;(function ($, window, document, undefined) {
+  // Create the defaults once
+  var pluginName = 'bootstrapDualListbox',
+    defaults = {
+      bootstrap2Compatible: false,
+      filterTextClear: 'show all',
+      filterPlaceHolder: 'Filter',
+      moveSelectedLabel: 'Move selected',
+      moveAllLabel: 'Move all',
+      removeSelectedLabel: 'Remove selected',
+      removeAllLabel: 'Remove all',
+      moveOnSelect: true,                                                                 // true/false (forced true on androids, see the comment later)
+      moveOnDoubleClick: true,                                                            // true/false (forced false on androids, cause moveOnSelect is forced to true)
+      preserveSelectionOnMove: false,                                                     // 'all' / 'moved' / false
+      selectedListLabel: false,                                                           // 'string', false
+      nonSelectedListLabel: false,                                                        // 'string', false
+      helperSelectNamePostfix: '_helper',                                                 // 'string_of_postfix' / false
+      selectorMinimalHeight: 100,
+      showFilterInputs: true,                                                             // whether to show filter inputs
+      nonSelectedFilter: '',                                                              // string, filter the non selected options
+      selectedFilter: '',                                                                 // string, filter the selected options
+      infoText: 'Showing all {0}',                                                        // text when all options are visible / false for no info text
+      infoTextFiltered: '<span class="label label-warning">Filtered</span> {0} from {1}', // when not all of the options are visible due to the filter
+      infoTextEmpty: 'Empty list',                                                        // when there are no options present in the list
+      filterOnValues: false,                                                              // filter by selector's values, boolean
+      sortByInputOrder: false,
+      eventMoveOverride: false,                                                           // boolean, allows user to unbind default event behaviour and run their own instead
+      eventMoveAllOverride: false,                                                        // boolean, allows user to unbind default event behaviour and run their own instead
+      eventRemoveOverride: false,                                                         // boolean, allows user to unbind default event behaviour and run their own instead
+      eventRemoveAllOverride: false                                                       // boolean, allows user to unbind default event behaviour and run their own instead
+    },
+    // Selections are invisible on android if the containing select is styled with CSS
+    // http://code.google.com/p/android/issues/detail?id=16922
+    isBuggyAndroid = /android/i.test(navigator.userAgent.toLowerCase());
+
+  // The actual plugin constructor
+  function BootstrapDualListbox(element, options) {
+    this.element = $(element);
+    // jQuery has an extend method which merges the contents of two or
+    // more objects, storing the result in the first object. The first object
+    // is generally empty as we don't want to alter the default options for
+    // future instances of the plugin
+    this.settings = $.extend({}, defaults, options);
+    this._defaults = defaults;
+    this._name = pluginName;
+    this.init();
+  }
+
+  function triggerChangeEvent(dualListbox) {
+    dualListbox.element.trigger('change');
+  }
+
+  function updateSelectionStates(dualListbox) {
+    dualListbox.element.find('option').each(function(index, item) {
+      var $item = $(item);
+      if (typeof($item.data('original-index')) === 'undefined') {
+        $item.data('original-index', dualListbox.elementCount++);
+      }
+      if (typeof($item.data('_selected')) === 'undefined') {
+        $item.data('_selected', false);
+      }
+    });
+  }
+
+  function changeSelectionState(dualListbox, original_index, selected) {
+    dualListbox.element.find('option').each(function(index, item) {
+      var $item = $(item);
+      if ($item.data('original-index') === original_index) {
+        $item.prop('selected', selected);
+        if(selected){
+          $item.attr('data-sortindex', dualListbox.sortIndex);
+          dualListbox.sortIndex++;
+        } else {
+          $item.removeAttr('data-sortindex');
+        }
+      }
+    });
+  }
+
+  function formatString(s, args) {
+    return s.replace(/\{(\d+)\}/g, function(match, number) {
+      return typeof args[number] !== 'undefined' ? args[number] : match;
+    });
+  }
+
+  function refreshInfo(dualListbox) {
+    if (!dualListbox.settings.infoText) {
+      return;
+    }
+
+    var visible1 = dualListbox.elements.select1.find('option').length,
+      visible2 = dualListbox.elements.select2.find('option').length,
+      all1 = dualListbox.element.find('option').length - dualListbox.selectedElements,
+      all2 = dualListbox.selectedElements,
+      content = '';
+
+    if (all1 === 0) {
+      content = dualListbox.settings.infoTextEmpty;
+    } else if (visible1 === all1) {
+      content = formatString(dualListbox.settings.infoText, [visible1, all1]);
+    } else {
+      content = formatString(dualListbox.settings.infoTextFiltered, [visible1, all1]);
+    }
+
+    dualListbox.elements.info1.html(content);
+    dualListbox.elements.box1.toggleClass('filtered', !(visible1 === all1 || all1 === 0));
+
+    if (all2 === 0) {
+      content = dualListbox.settings.infoTextEmpty;
+    } else if (visible2 === all2) {
+      content = formatString(dualListbox.settings.infoText, [visible2, all2]);
+    } else {
+      content = formatString(dualListbox.settings.infoTextFiltered, [visible2, all2]);
+    }
+
+    dualListbox.elements.info2.html(content);
+    dualListbox.elements.box2.toggleClass('filtered', !(visible2 === all2 || all2 === 0));
+  }
+
+  function refreshSelects(dualListbox) {
+    dualListbox.selectedElements = 0;
+
+    dualListbox.elements.select1.empty();
+    dualListbox.elements.select2.empty();
+
+    dualListbox.element.find('option').each(function(index, item) {
+      var $item = $(item);
+      if ($item.prop('selected')) {
+        dualListbox.selectedElements++;
+        dualListbox.elements.select2.append($item.clone(true).prop('selected', $item.data('_selected')));
+      } else {
+        dualListbox.elements.select1.append($item.clone(true).prop('selected', $item.data('_selected')));
+      }
+    });
+
+    if (dualListbox.settings.showFilterInputs) {
+      filter(dualListbox, 1);
+      filter(dualListbox, 2);
+    }
+    refreshInfo(dualListbox);
+  }
+
+  function filter(dualListbox, selectIndex) {
+    if (!dualListbox.settings.showFilterInputs) {
+      return;
+    }
+
+    saveSelections(dualListbox, selectIndex);
+
+    dualListbox.elements['select'+selectIndex].empty().scrollTop(0);
+    var regex = new RegExp($.trim(dualListbox.elements['filterInput'+selectIndex].val()), 'gi'),
+      allOptions = dualListbox.element.find('option'),
+      options = dualListbox.element;
+
+    if (selectIndex === 1) {
+      options = allOptions.not(':selected');
+    } else  {
+      options = options.find('option:selected');
+    }
+
+    options.each(function(index, item) {
+      var $item = $(item),
+        isFiltered = true;
+      if (item.text.match(regex) || (dualListbox.settings.filterOnValues && $item.attr('value').match(regex) ) ) {
+        isFiltered = false;
+        dualListbox.elements['select'+selectIndex].append($item.clone(true).prop('selected', $item.data('_selected')));
+      }
+      allOptions.eq($item.data('original-index')).data('filtered'+selectIndex, isFiltered);
+    });
+
+    refreshInfo(dualListbox);
+  }
+
+  function saveSelections(dualListbox, selectIndex) {
+    var options = dualListbox.element.find('option');
+    dualListbox.elements['select'+selectIndex].find('option').each(function(index, item) {
+      var $item = $(item);
+      options.eq($item.data('original-index')).data('_selected', $item.prop('selected'));
+    });
+  }
+
+  function sortOptionsByInputOrder(select){
+    var selectopt = select.children('option');
+
+    selectopt.sort(function(a,b){
+      var an = parseInt(a.getAttribute('data-sortindex')),
+          bn = parseInt(b.getAttribute('data-sortindex'));
+
+          if(an > bn) {
+             return 1;
+          }
+          if(an < bn) {
+            return -1;
+          }
+          return 0;
+    });
+
+    selectopt.detach().appendTo(select);
+  }
+
+  function sortOptions(select) {
+    select.find('option').sort(function(a, b) {
+      return ($(a).data('original-index') > $(b).data('original-index')) ? 1 : -1;
+    }).appendTo(select);
+  }
+
+  function clearSelections(dualListbox) {
+    dualListbox.elements.select1.find('option').each(function() {
+      dualListbox.element.find('option').data('_selected', false);
+    });
+  }
+
+  function move(dualListbox) {
+    if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 1);
+      saveSelections(dualListbox, 2);
+    } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 1);
+    }
+
+    dualListbox.elements.select1.find('option:selected').each(function(index, item) {
+      var $item = $(item);
+      if (!$item.data('filtered1')) {
+        changeSelectionState(dualListbox, $item.data('original-index'), true);
+      }
+    });
+
+    refreshSelects(dualListbox);
+    triggerChangeEvent(dualListbox);
+    if(dualListbox.settings.sortByInputOrder){
+        sortOptionsByInputOrder(dualListbox.elements.select2);
+    } else {
+        sortOptions(dualListbox.elements.select2);
+    }
+  }
+
+  function remove(dualListbox) {
+    if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 1);
+      saveSelections(dualListbox, 2);
+    } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 2);
+    }
+
+    dualListbox.elements.select2.find('option:selected').each(function(index, item) {
+      var $item = $(item);
+      if (!$item.data('filtered2')) {
+        changeSelectionState(dualListbox, $item.data('original-index'), false);
+      }
+    });
+
+    refreshSelects(dualListbox);
+    triggerChangeEvent(dualListbox);
+    sortOptions(dualListbox.elements.select1);
+    if(dualListbox.settings.sortByInputOrder){
+        sortOptionsByInputOrder(dualListbox.elements.select2);
+    }
+  }
+
+  function moveAll(dualListbox) {
+    if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 1);
+      saveSelections(dualListbox, 2);
+    } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 1);
+    }
+
+    dualListbox.element.find('option').each(function(index, item) {
+      var $item = $(item);
+      if (!$item.data('filtered1')) {
+        $item.prop('selected', true);
+        $item.attr('data-sortindex', dualListbox.sortIndex);
+        dualListbox.sortIndex++;
+      }
+    });
+
+    refreshSelects(dualListbox);
+    triggerChangeEvent(dualListbox);
+  }
+
+  function removeAll(dualListbox) {
+    if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 1);
+      saveSelections(dualListbox, 2);
+    } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) {
+      saveSelections(dualListbox, 2);
+    }
+
+    dualListbox.element.find('option').each(function(index, item) {
+      var $item = $(item);
+      if (!$item.data('filtered2')) {
+        $item.prop('selected', false);
+        $item.removeAttr('data-sortindex');
+      }
+    });
+
+    refreshSelects(dualListbox);
+    triggerChangeEvent(dualListbox);
+  }
+
+  function bindEvents(dualListbox) {
+    dualListbox.elements.form.submit(function(e) {
+      if (dualListbox.elements.filterInput1.is(':focus')) {
+        e.preventDefault();
+        dualListbox.elements.filterInput1.focusout();
+      } else if (dualListbox.elements.filterInput2.is(':focus')) {
+        e.preventDefault();
+        dualListbox.elements.filterInput2.focusout();
+      }
+    });
+
+    dualListbox.element.on('bootstrapDualListbox.refresh', function(e, mustClearSelections){
+      dualListbox.refresh(mustClearSelections);
+    });
+
+    dualListbox.elements.filterClear1.on('click', function() {
+      dualListbox.setNonSelectedFilter('', true);
+    });
+
+    dualListbox.elements.filterClear2.on('click', function() {
+      dualListbox.setSelectedFilter('', true);
+    });
+
+    if (dualListbox.settings.eventMoveOverride === false) {
+      dualListbox.elements.moveButton.on('click', function() {
+        move(dualListbox);
+      });
+    }
+
+    if (dualListbox.settings.eventMoveAllOverride === false) {
+      dualListbox.elements.moveAllButton.on('click', function() {
+        moveAll(dualListbox);
+      });
+    }
+
+    if (dualListbox.settings.eventRemoveOverride === false) {
+      dualListbox.elements.removeButton.on('click', function() {
+        remove(dualListbox);
+      });
+    }
+
+    if (dualListbox.settings.eventRemoveAllOverride === false) {
+      dualListbox.elements.removeAllButton.on('click', function() {
+        removeAll(dualListbox);
+      });
+    }
+
+    dualListbox.elements.filterInput1.on('change keyup', function() {
+      filter(dualListbox, 1);
+    });
+
+    dualListbox.elements.filterInput2.on('change keyup', function() {
+      filter(dualListbox, 2);
+    });
+  }
+
+  BootstrapDualListbox.prototype = {
+    init: function () {
+      // Add the custom HTML template
+      this.container = $('' +
+        '<div class="bootstrap-duallistbox-container">' +
+        ' <div class="box1">' +
+        '   <label></label>' +
+        '   <span class="info-container">' +
+        '     <span class="info"></span>' +
+        '     <button type="button" class="btn clear1 pull-right"></button>' +
+        '   </span>' +
+        '   <input class="filter" type="text">' +
+        '   <div class="btn-group buttons">' +
+        '     <button type="button" class="btn moveall">' +
+        '       <i></i>' +
+        '       <i></i>' +
+        '     </button>' +
+        '     <button type="button" class="btn move">' +
+        '       <i></i>' +
+        '     </button>' +
+        '   </div>' +
+        '   <select multiple="multiple"></select>' +
+        ' </div>' +
+        ' <div class="box2">' +
+        '   <label></label>' +
+        '   <span class="info-container">' +
+        '     <span class="info"></span>' +
+        '     <button type="button" class="btn clear2 pull-right"></button>' +
+        '   </span>' +
+        '   <input class="filter" type="text">' +
+        '   <div class="btn-group buttons">' +
+        '     <button type="button" class="btn remove">' +
+        '       <i></i>' +
+        '     </button>' +
+        '     <button type="button" class="btn removeall">' +
+        '       <i></i>' +
+        '       <i></i>' +
+        '     </button>' +
+        '   </div>' +
+        '   <select multiple="multiple"></select>' +
+        ' </div>' +
+        '</div>')
+        .insertBefore(this.element);
+
+      // Cache the inner elements
+      this.elements = {
+        originalSelect: this.element,
+        box1: $('.box1', this.container),
+        box2: $('.box2', this.container),
+        filterInput1: $('.box1 .filter', this.container),
+        filterInput2: $('.box2 .filter', this.container),
+        filterClear1: $('.box1 .clear1', this.container),
+        filterClear2: $('.box2 .clear2', this.container),
+        label1: $('.box1 > label', this.container),
+        label2: $('.box2 > label', this.container),
+        info1: $('.box1 .info', this.container),
+        info2: $('.box2 .info', this.container),
+        select1: $('.box1 select', this.container),
+        select2: $('.box2 select', this.container),
+        moveButton: $('.box1 .move', this.container),
+        removeButton: $('.box2 .remove', this.container),
+        moveAllButton: $('.box1 .moveall', this.container),
+        removeAllButton: $('.box2 .removeall', this.container),
+        form: $($('.box1 .filter', this.container)[0].form)
+      };
+
+      // Set select IDs
+      this.originalSelectName = this.element.attr('name') || '';
+      var select1Id = 'bootstrap-duallistbox-nonselected-list_' + this.originalSelectName,
+        select2Id = 'bootstrap-duallistbox-selected-list_' + this.originalSelectName;
+      this.elements.select1.attr('id', select1Id);
+      this.elements.select2.attr('id', select2Id);
+      this.elements.label1.attr('for', select1Id);
+      this.elements.label2.attr('for', select2Id);
+
+      // Apply all settings
+      this.selectedElements = 0;
+      this.sortIndex = 0;
+      this.elementCount = 0;
+      this.setBootstrap2Compatible(this.settings.bootstrap2Compatible);
+      this.setFilterTextClear(this.settings.filterTextClear);
+      this.setFilterPlaceHolder(this.settings.filterPlaceHolder);
+      this.setMoveSelectedLabel(this.settings.moveSelectedLabel);
+      this.setMoveAllLabel(this.settings.moveAllLabel);
+      this.setRemoveSelectedLabel(this.settings.removeSelectedLabel);
+      this.setRemoveAllLabel(this.settings.removeAllLabel);
+      this.setMoveOnSelect(this.settings.moveOnSelect);
+      this.setMoveOnDoubleClick(this.settings.moveOnDoubleClick);
+      this.setPreserveSelectionOnMove(this.settings.preserveSelectionOnMove);
+      this.setSelectedListLabel(this.settings.selectedListLabel);
+      this.setNonSelectedListLabel(this.settings.nonSelectedListLabel);
+      this.setHelperSelectNamePostfix(this.settings.helperSelectNamePostfix);
+      this.setSelectOrMinimalHeight(this.settings.selectorMinimalHeight);
+
+      updateSelectionStates(this);
+
+      this.setShowFilterInputs(this.settings.showFilterInputs);
+      this.setNonSelectedFilter(this.settings.nonSelectedFilter);
+      this.setSelectedFilter(this.settings.selectedFilter);
+      this.setInfoText(this.settings.infoText);
+      this.setInfoTextFiltered(this.settings.infoTextFiltered);
+      this.setInfoTextEmpty(this.settings.infoTextEmpty);
+      this.setFilterOnValues(this.settings.filterOnValues);
+      this.setSortByInputOrder(this.settings.sortByInputOrder);
+      this.setEventMoveOverride(this.settings.eventMoveOverride);
+      this.setEventMoveAllOverride(this.settings.eventMoveAllOverride);
+      this.setEventRemoveOverride(this.settings.eventRemoveOverride);
+      this.setEventRemoveAllOverride(this.settings.eventRemoveAllOverride);
+
+      // Hide the original select
+      this.element.hide();
+
+      bindEvents(this);
+      refreshSelects(this);
+
+      return this.element;
+    },
+    setBootstrap2Compatible: function(value, refresh) {
+      this.settings.bootstrap2Compatible = value;
+      if (value) {
+        this.container.removeClass('row').addClass('row-fluid bs2compatible');
+        this.container.find('.box1, .box2').removeClass('col-md-6').addClass('span6');
+        this.container.find('.clear1, .clear2').removeClass('btn-white btn-xs').addClass('btn-mini');
+        this.container.find('input, select').removeClass('form-control');
+        this.container.find('.btn').removeClass('btn-white');
+        this.container.find('.moveall > i, .move > i').removeClass('glyphicon glyphicon-arrow-right').addClass('icon-arrow-right');
+        this.container.find('.removeall > i, .remove > i').removeClass('glyphicon glyphicon-arrow-left').addClass('icon-arrow-left');
+      } else {
+        this.container.removeClass('row-fluid bs2compatible').addClass('row');
+        this.container.find('.box1, .box2').removeClass('span6').addClass('col-md-6');
+        this.container.find('.clear1, .clear2').removeClass('btn-mini').addClass('btn-white btn-xs');
+        this.container.find('input, select').addClass('form-control');
+        this.container.find('.btn').addClass('btn-white');
+        this.container.find('.moveall > i, .move > i').removeClass('icon-arrow-right').addClass('glyphicon glyphicon-arrow-right');
+        this.container.find('.removeall > i, .remove > i').removeClass('icon-arrow-left').addClass('glyphicon glyphicon-arrow-left');
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setFilterTextClear: function(value, refresh) {
+      this.settings.filterTextClear = value;
+      this.elements.filterClear1.html(value);
+      this.elements.filterClear2.html(value);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setFilterPlaceHolder: function(value, refresh) {
+      this.settings.filterPlaceHolder = value;
+      this.elements.filterInput1.attr('placeholder', value);
+      this.elements.filterInput2.attr('placeholder', value);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setMoveSelectedLabel: function(value, refresh) {
+      this.settings.moveSelectedLabel = value;
+      this.elements.moveButton.attr('title', value);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setMoveAllLabel: function(value, refresh) {
+      this.settings.moveAllLabel = value;
+      this.elements.moveAllButton.attr('title', value);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setRemoveSelectedLabel: function(value, refresh) {
+      this.settings.removeSelectedLabel = value;
+      this.elements.removeButton.attr('title', value);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setRemoveAllLabel: function(value, refresh) {
+      this.settings.removeAllLabel = value;
+      this.elements.removeAllButton.attr('title', value);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setMoveOnSelect: function(value, refresh) {
+      if (isBuggyAndroid) {
+        value = true;
+      }
+      this.settings.moveOnSelect = value;
+      if (this.settings.moveOnSelect) {
+        this.container.addClass('moveonselect');
+        var self = this;
+        this.elements.select1.on('change', function() {
+          move(self);
+        });
+        this.elements.select2.on('change', function() {
+          remove(self);
+        });
+      } else {
+        this.container.removeClass('moveonselect');
+        this.elements.select1.off('change');
+        this.elements.select2.off('change');
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setMoveOnDoubleClick: function(value, refresh) {
+      if (isBuggyAndroid) {
+        value = false;
+      }
+      this.settings.moveOnDoubleClick = value;
+      if (this.settings.moveOnDoubleClick) {
+        this.container.addClass('moveondoubleclick');
+        var self = this;
+        this.elements.select1.on('dblclick', function() {
+          move(self);
+        });
+        this.elements.select2.on('dblclick', function() {
+          remove(self);
+        });
+      } else {
+        this.container.removeClass('moveondoubleclick');
+        this.elements.select1.off('dblclick');
+        this.elements.select2.off('dblclick');
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setPreserveSelectionOnMove: function(value, refresh) {
+      // We are forcing to move on select and disabling preserveSelectionOnMove on Android
+      if (isBuggyAndroid) {
+        value = false;
+      }
+      this.settings.preserveSelectionOnMove = value;
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setSelectedListLabel: function(value, refresh) {
+      this.settings.selectedListLabel = value;
+      if (value) {
+        this.elements.label2.show().html(value);
+      } else {
+        this.elements.label2.hide().html(value);
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setNonSelectedListLabel: function(value, refresh) {
+      this.settings.nonSelectedListLabel = value;
+      if (value) {
+        this.elements.label1.show().html(value);
+      } else {
+        this.elements.label1.hide().html(value);
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setHelperSelectNamePostfix: function(value, refresh) {
+      this.settings.helperSelectNamePostfix = value;
+      if (value) {
+        this.elements.select1.attr('name', this.originalSelectName + value + '1');
+        this.elements.select2.attr('name', this.originalSelectName + value + '2');
+      } else {
+        this.elements.select1.removeAttr('name');
+        this.elements.select2.removeAttr('name');
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setSelectOrMinimalHeight: function(value, refresh) {
+      this.settings.selectorMinimalHeight = value;
+      var height = this.element.height();
+      if (this.element.height() < value) {
+        height = value;
+      }
+      this.elements.select1.height(height);
+      this.elements.select2.height(height);
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setShowFilterInputs: function(value, refresh) {
+      if (!value) {
+        this.setNonSelectedFilter('');
+        this.setSelectedFilter('');
+        refreshSelects(this);
+        this.elements.filterInput1.hide();
+        this.elements.filterInput2.hide();
+      } else {
+        this.elements.filterInput1.show();
+        this.elements.filterInput2.show();
+      }
+      this.settings.showFilterInputs = value;
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setNonSelectedFilter: function(value, refresh) {
+      if (this.settings.showFilterInputs) {
+        this.settings.nonSelectedFilter = value;
+        this.elements.filterInput1.val(value);
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+      }
+    },
+    setSelectedFilter: function(value, refresh) {
+      if (this.settings.showFilterInputs) {
+        this.settings.selectedFilter = value;
+        this.elements.filterInput2.val(value);
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+      }
+    },
+    setInfoText: function(value, refresh) {
+      this.settings.infoText = value;
+      if (value) {
+        this.elements.info1.show();
+        this.elements.info2.show();
+      } else {
+        this.elements.info1.hide();
+        this.elements.info2.hide();
+      }
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setInfoTextFiltered: function(value, refresh) {
+      this.settings.infoTextFiltered = value;
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setInfoTextEmpty: function(value, refresh) {
+      this.settings.infoTextEmpty = value;
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setFilterOnValues: function(value, refresh) {
+      this.settings.filterOnValues = value;
+      if (refresh) {
+        refreshSelects(this);
+      }
+      return this.element;
+    },
+    setSortByInputOrder: function(value, refresh){
+        this.settings.sortByInputOrder = value;
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+    },
+    setEventMoveOverride: function(value, refresh) {
+        this.settings.eventMoveOverride = value;
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+    },
+    setEventMoveAllOverride: function(value, refresh) {
+        this.settings.eventMoveAllOverride = value;
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+    },
+    setEventRemoveOverride: function(value, refresh) {
+        this.settings.eventRemoveOverride = value;
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+    },
+    setEventRemoveAllOverride: function(value, refresh) {
+        this.settings.eventRemoveAllOverride = value;
+        if (refresh) {
+          refreshSelects(this);
+        }
+        return this.element;
+    },
+    getContainer: function() {
+      return this.container;
+    },
+    refresh: function(mustClearSelections) {
+      updateSelectionStates(this);
+
+      if (!mustClearSelections) {
+        saveSelections(this, 1);
+        saveSelections(this, 2);
+      } else {
+        clearSelections(this);
+      }
+
+      refreshSelects(this);
+    },
+    destroy: function() {
+      this.container.remove();
+      this.element.show();
+      $.data(this, 'plugin_' + pluginName, null);
+      return this.element;
+    }
+  };
+
+  // A really lightweight plugin wrapper around the constructor,
+  // preventing against multiple instantiations
+  $.fn[ pluginName ] = function (options) {
+    var args = arguments;
+
+    // Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin.
+    if (options === undefined || typeof options === 'object') {
+      return this.each(function () {
+        // If this is not a select
+        if (!$(this).is('select')) {
+          $(this).find('select').each(function(index, item) {
+            // For each nested select, instantiate the Dual List Box
+            $(item).bootstrapDualListbox(options);
+          });
+        } else if (!$.data(this, 'plugin_' + pluginName)) {
+          // Only allow the plugin to be instantiated once so we check that the element has no plugin instantiation yet
+
+          // if it has no instance, create a new one, pass options to our plugin constructor,
+          // and store the plugin instance in the elements jQuery data object.
+          $.data(this, 'plugin_' + pluginName, new BootstrapDualListbox(this, options));
+        }
+      });
+      // If the first parameter is a string and it doesn't start with an underscore or "contains" the `init`-function,
+      // treat this as a call to a public method.
+    } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
+
+      // Cache the method call to make it possible to return a value
+      var returns;
+
+      this.each(function () {
+        var instance = $.data(this, 'plugin_' + pluginName);
+        // Tests that there's already a plugin-instance and checks that the requested public method exists
+        if (instance instanceof BootstrapDualListbox && typeof instance[options] === 'function') {
+          // Call the method of our plugin instance, and pass it the supplied arguments.
+          returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
+        }
+      });
+
+      // If the earlier cached method gives a value back return the value,
+      // otherwise return this to preserve chainability.
+      return returns !== undefined ? returns : this;
+    }
+
+  };
+
+})(jQuery, window, document);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 10 - 0
leiSP-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.min.js


+ 315 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/curvedLines.js

@@ -0,0 +1,315 @@
+/* The MIT License
+
+ Copyright (c) 2011 by Michael Zinsmaier and nergal.dev
+ Copyright (c) 2012 by Thomas Ritou
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+/*
+
+ ____________________________________________________
+
+ what it is:
+ ____________________________________________________
+
+ curvedLines is a plugin for flot, that tries to display lines in a smoother way.
+ The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226
+ and further extended with a mode that forces the min/max points of the curves to be on the
+ points. Both modes are achieved through adding of more data points
+ => 1) with large data sets you may get trouble
+ => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines
+
+ && 3) consecutive x data points are not allowed to have the same value
+
+ This is version 0.5 of curvedLines so it will probably not work in every case. However
+ the basic form of use descirbed next works (:
+
+ Feel free to further improve the code
+
+ ____________________________________________________
+
+ how to use it:
+ ____________________________________________________
+
+ var d1 = [[5,5],[7,3],[9,12]];
+
+ var options = { series: { curvedLines: {  active: true }}};
+
+ $.plot($("#placeholder"), [{data = d1, lines: { show: true}, curvedLines: {apply: true}}], options);
+
+ _____________________________________________________
+
+ options:
+ _____________________________________________________
+
+ active:           bool true => plugin can be used
+ apply:            bool true => series will be drawn as curved line
+ fit:              bool true => forces the max,mins of the curve to be on the datapoints
+ curvePointFactor  int  defines how many "virtual" points are used per "real" data point to
+ emulate the curvedLines (points total = real points * curvePointFactor)
+ fitPointDist:     int  defines the x axis distance of the additional two points that are used
+ to enforce the min max condition.
+
+ + line options (since v0.5 curved lines use flots line implementation for drawing
+ => line options like fill, show ... are supported out of the box)
+
+ */
+
+/*
+ *  v0.1   initial commit
+ *  v0.15  negative values should work now (outcommented a negative -> 0 hook hope it does no harm)
+ *  v0.2   added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi)
+ *  v0.3   improved saddle handling and added basic handling of Dates
+ *  v0.4   rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug
+ *  v0.5   rewritten instead of implementing a own draw function CurvedLines is now based on the processDatapoints flot hook (credits go to thomas ritou).
+ * 		   This change breakes existing code however CurvedLines are now just many tiny straight lines to flot and therefore all flot lines options (like gradient fill,
+ * 	       shadow) are now supported out of the box
+ *  v0.6   flot 0.8 compatibility and some bug fixes
+ */
+
+(function($) {
+
+    var options = {
+        series : {
+            curvedLines : {
+                active : false,
+                apply: false,
+                fit : false,
+                curvePointFactor : 20,
+                fitPointDist : undefined
+            }
+        }
+    };
+
+    function init(plot) {
+
+        plot.hooks.processOptions.push(processOptions);
+
+        //if the plugin is active register processDatapoints method
+        function processOptions(plot, options) {
+            if (options.series.curvedLines.active) {
+                plot.hooks.processDatapoints.unshift(processDatapoints);
+            }
+        }
+
+        //only if the plugin is active
+        function processDatapoints(plot, series, datapoints) {
+            var nrPoints = datapoints.points.length / datapoints.pointsize;
+            var EPSILON = 0.5; //pretty large epsilon but save
+
+            if (series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) {
+                if (series.lines.fill) {
+
+                    var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1)
+                        ,pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); //flot makes sure for us that we've got a second y point if fill is true !
+
+                    //Merge top and bottom curve
+                    datapoints.pointsize = 3;
+                    datapoints.points = [];
+                    var j = 0;
+                    var k = 0;
+                    var i = 0;
+                    var ps = 2;
+                    while (i < pointsTop.length || j < pointsBottom.length) {
+                        if (pointsTop[i] == pointsBottom[j]) {
+                            datapoints.points[k] = pointsTop[i];
+                            datapoints.points[k + 1] = pointsTop[i + 1];
+                            datapoints.points[k + 2] = pointsBottom[j + 1];
+                            j += ps;
+                            i += ps;
+
+                        } else if (pointsTop[i] < pointsBottom[j]) {
+                            datapoints.points[k] = pointsTop[i];
+                            datapoints.points[k + 1] = pointsTop[i + 1];
+                            datapoints.points[k + 2] = k > 0 ? datapoints.points[k-1] : null;
+                            i += ps;
+                        } else {
+                            datapoints.points[k] = pointsBottom[j];
+                            datapoints.points[k + 1] = k > 1 ? datapoints.points[k-2] : null;
+                            datapoints.points[k + 2] = pointsBottom[j + 1];
+                            j += ps;
+                        }
+                        k += 3;
+                    }
+                } else if (series.lines.lineWidth > 0) {
+                    datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1);
+                    datapoints.pointsize = 2;
+                }
+            }
+        }
+
+        //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226
+        //if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code.
+        function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) {
+
+            var points = datapoints.points, ps = datapoints.pointsize;
+            var num = curvedLinesOptions.curvePointFactor * (points.length / ps);
+
+            var xdata = new Array;
+            var ydata = new Array;
+
+            var curX = -1;
+            var curY = -1;
+            var j = 0;
+
+            if (curvedLinesOptions.fit) {
+                //insert a point before and after the "real" data point to force the line
+                //to have a max,min at the data point.
+
+                var fpDist;
+                if(typeof curvedLinesOptions.fitPointDist == 'undefined') {
+                    //estimate it
+                    var minX = points[0];
+                    var maxX = points[points.length-ps];
+                    fpDist = (maxX - minX) / (500 * 100); //x range / (estimated pixel length of placeholder * factor)
+                } else {
+                    //use user defined value
+                    fpDist = curvedLinesOptions.fitPointDist;
+                }
+
+                for (var i = 0; i < points.length; i += ps) {
+
+                    var frontX;
+                    var backX;
+                    curX = i;
+                    curY = i + yPos;
+
+                    //add point X s
+                    frontX = points[curX] - fpDist;
+                    backX = points[curX] + fpDist;
+
+                    var factor = 2;
+                    while (frontX == points[curX] || backX == points[curX]) {
+                        //inside the ulp
+                        frontX = points[curX] - (fpDist * factor);
+                        backX = points[curX] + (fpDist * factor);
+                        factor++;
+                    }
+
+                    //add curve points
+                    xdata[j] = frontX;
+                    ydata[j] = points[curY];
+                    j++;
+
+                    xdata[j] = points[curX];
+                    ydata[j] = points[curY];
+                    j++;
+
+                    xdata[j] = backX;
+                    ydata[j] = points[curY];
+                    j++;
+                }
+            } else {
+                //just use the datapoints
+                for (var i = 0; i < points.length; i += ps) {
+                    curX = i;
+                    curY = i + yPos;
+
+                    xdata[j] = points[curX];
+                    ydata[j] = points[curY];
+                    j++;
+                }
+            }
+
+            var n = xdata.length;
+
+            var y2 = new Array();
+            var delta = new Array();
+            y2[0] = 0;
+            y2[n - 1] = 0;
+            delta[0] = 0;
+
+            for (var i = 1; i < n - 1; ++i) {
+                var d = (xdata[i + 1] - xdata[i - 1]);
+                if (d == 0) {
+                    //point before current point and after current point need some space in between
+                    return [];
+                }
+
+                var s = (xdata[i] - xdata[i - 1]) / d;
+                var p = s * y2[i - 1] + 2;
+                y2[i] = (s - 1) / p;
+                delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]);
+                delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p;
+            }
+
+            for (var j = n - 2; j >= 0; --j) {
+                y2[j] = y2[j] * y2[j + 1] + delta[j];
+            }
+
+            //   xmax  - xmin  / #points
+            var step = (xdata[n - 1] - xdata[0]) / (num - 1);
+
+            var xnew = new Array;
+            var ynew = new Array;
+            var result = new Array;
+
+            xnew[0] = xdata[0];
+            ynew[0] = ydata[0];
+
+            result.push(xnew[0]);
+            result.push(ynew[0]);
+
+            for ( j = 1; j < num; ++j) {
+                //new x point (sampling point for the created curve)
+                xnew[j] = xnew[0] + j * step;
+
+                var max = n - 1;
+                var min = 0;
+
+                while (max - min > 1) {
+                    var k = Math.round((max + min) / 2);
+                    if (xdata[k] > xnew[j]) {
+                        max = k;
+                    } else {
+                        min = k;
+                    }
+                }
+
+                //found point one to the left and one to the right of generated new point
+                var h = (xdata[max] - xdata[min]);
+
+                if (h == 0) {
+                    //similar to above two points from original x data need some space between them
+                    return [];
+                }
+
+                var a = (xdata[max] - xnew[j]) / h;
+                var b = (xnew[j] - xdata[min]) / h;
+
+                ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6;
+
+                result.push(xnew[j]);
+                result.push(ynew[j]);
+            }
+
+            return result;
+        }
+
+    }//end init
+
+    $.plot.plugins.push({
+        init : init,
+        options : options,
+        name : 'curvedLines',
+        version : '0.5'
+    });
+
+})(jQuery);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2599 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.js


+ 750 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.pie.js

@@ -0,0 +1,750 @@
+/*
+Flot plugin for rendering pie charts. The plugin assumes the data is
+coming is as a single data value for each series, and each of those
+values is a positive value or zero (negative numbers don't make
+any sense and will cause strange effects). The data values do
+NOT need to be passed in as percentage values because it
+internally calculates the total and percentages.
+
+* Created by Brian Medendorp, June 2009
+* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars
+
+* Changes:
+	2009-10-22: lineJoin set to round
+	2009-10-23: IE full circle fix, donut
+	2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera
+	2009-11-17: Added IE hover capability submitted by Anthony Aragues
+	2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well)
+
+
+Available options are:
+series: {
+	pie: {
+		show: true/false
+		radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
+		innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
+		startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
+		tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
+		offset: {
+			top: integer value to move the pie up or down
+			left: integer value to move the pie left or right, or 'auto'
+		},
+		stroke: {
+			color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
+			width: integer pixel width of the stroke
+		},
+		label: {
+			show: true/false, or 'auto'
+			formatter:  a user-defined function that modifies the text/style of the label text
+			radius: 0-1 for percentage of fullsize, or a specified pixel length
+			background: {
+				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
+				opacity: 0-1
+			},
+			threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
+		},
+		combine: {
+			threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
+			color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
+			label: any text value of what the combined slice should be labeled
+		}
+		highlight: {
+			opacity: 0-1
+		}
+	}
+}
+
+More detail and specific examples can be found in the included HTML file.
+
+*/
+
+(function ($)
+{
+	function init(plot) // this is the "body" of the plugin
+	{
+		var canvas = null;
+		var target = null;
+		var maxRadius = null;
+		var centerLeft = null;
+		var centerTop = null;
+		var total = 0;
+		var redraw = true;
+		var redrawAttempts = 10;
+		var shrink = 0.95;
+		var legendWidth = 0;
+		var processed = false;
+		var raw = false;
+
+		// interactive variables
+		var highlights = [];
+
+		// add hook to determine if pie plugin in enabled, and then perform necessary operations
+		plot.hooks.processOptions.push(checkPieEnabled);
+		plot.hooks.bindEvents.push(bindEvents);
+
+		// check to see if the pie plugin is enabled
+		function checkPieEnabled(plot, options)
+		{
+			if (options.series.pie.show)
+			{
+				//disable grid
+				options.grid.show = false;
+
+				// set labels.show
+				if (options.series.pie.label.show=='auto')
+					if (options.legend.show)
+						options.series.pie.label.show = false;
+					else
+						options.series.pie.label.show = true;
+
+				// set radius
+				if (options.series.pie.radius=='auto')
+					if (options.series.pie.label.show)
+						options.series.pie.radius = 3/4;
+					else
+						options.series.pie.radius = 1;
+
+				// ensure sane tilt
+				if (options.series.pie.tilt>1)
+					options.series.pie.tilt=1;
+				if (options.series.pie.tilt<0)
+					options.series.pie.tilt=0;
+
+				// add processData hook to do transformations on the data
+				plot.hooks.processDatapoints.push(processDatapoints);
+				plot.hooks.drawOverlay.push(drawOverlay);
+
+				// add draw hook
+				plot.hooks.draw.push(draw);
+			}
+		}
+
+		// bind hoverable events
+		function bindEvents(plot, eventHolder)
+		{
+			var options = plot.getOptions();
+
+			if (options.series.pie.show && options.grid.hoverable)
+				eventHolder.unbind('mousemove').mousemove(onMouseMove);
+
+			if (options.series.pie.show && options.grid.clickable)
+				eventHolder.unbind('click').click(onClick);
+		}
+
+
+		// debugging function that prints out an object
+		function alertObject(obj)
+		{
+			var msg = '';
+			function traverse(obj, depth)
+			{
+				if (!depth)
+					depth = 0;
+				for (var i = 0; i < obj.length; ++i)
+				{
+					for (var j=0; j<depth; j++)
+						msg += '\t';
+
+					if( typeof obj[i] == "object")
+					{	// its an object
+						msg += ''+i+':\n';
+						traverse(obj[i], depth+1);
+					}
+					else
+					{	// its a value
+						msg += ''+i+': '+obj[i]+'\n';
+					}
+				}
+			}
+			traverse(obj);
+			alert(msg);
+		}
+
+		function calcTotal(data)
+		{
+			for (var i = 0; i < data.length; ++i)
+			{
+				var item = parseFloat(data[i].data[0][1]);
+				if (item)
+					total += item;
+			}
+		}
+
+		function processDatapoints(plot, series, data, datapoints)
+		{
+			if (!processed)
+			{
+				processed = true;
+
+				canvas = plot.getCanvas();
+				target = $(canvas).parent();
+				options = plot.getOptions();
+
+				plot.setData(combine(plot.getData()));
+			}
+		}
+
+		function setupPie()
+		{
+			legendWidth = target.children().filter('.legend').children().width();
+
+			// calculate maximum radius and center point
+			maxRadius =  Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2;
+			centerTop = (canvas.height/2)+options.series.pie.offset.top;
+			centerLeft = (canvas.width/2);
+
+			if (options.series.pie.offset.left=='auto')
+				if (options.legend.position.match('w'))
+					centerLeft += legendWidth/2;
+				else
+					centerLeft -= legendWidth/2;
+			else
+				centerLeft += options.series.pie.offset.left;
+
+			if (centerLeft<maxRadius)
+				centerLeft = maxRadius;
+			else if (centerLeft>canvas.width-maxRadius)
+				centerLeft = canvas.width-maxRadius;
+		}
+
+		function fixData(data)
+		{
+			for (var i = 0; i < data.length; ++i)
+			{
+				if (typeof(data[i].data)=='number')
+					data[i].data = [[1,data[i].data]];
+				else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined')
+				{
+					if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined')
+						data[i].label = data[i].data.label; // fix weirdness coming from flot
+					data[i].data = [[1,0]];
+
+				}
+			}
+			return data;
+		}
+
+		function combine(data)
+		{
+			data = fixData(data);
+			calcTotal(data);
+			var combined = 0;
+			var numCombined = 0;
+			var color = options.series.pie.combine.color;
+
+			var newdata = [];
+			for (var i = 0; i < data.length; ++i)
+			{
+				// make sure its a number
+				data[i].data[0][1] = parseFloat(data[i].data[0][1]);
+				if (!data[i].data[0][1])
+					data[i].data[0][1] = 0;
+
+				if (data[i].data[0][1]/total<=options.series.pie.combine.threshold)
+				{
+					combined += data[i].data[0][1];
+					numCombined++;
+					if (!color)
+						color = data[i].color;
+				}
+				else
+				{
+					newdata.push({
+						data: [[1,data[i].data[0][1]]],
+						color: data[i].color,
+						label: data[i].label,
+						angle: (data[i].data[0][1]*(Math.PI*2))/total,
+						percent: (data[i].data[0][1]/total*100)
+					});
+				}
+			}
+			if (numCombined>0)
+				newdata.push({
+					data: [[1,combined]],
+					color: color,
+					label: options.series.pie.combine.label,
+					angle: (combined*(Math.PI*2))/total,
+					percent: (combined/total*100)
+				});
+			return newdata;
+		}
+
+		function draw(plot, newCtx)
+		{
+			if (!target) return; // if no series were passed
+			ctx = newCtx;
+
+			setupPie();
+			var slices = plot.getData();
+
+			var attempts = 0;
+			while (redraw && attempts<redrawAttempts)
+			{
+				redraw = false;
+				if (attempts>0)
+					maxRadius *= shrink;
+				attempts += 1;
+				clear();
+				if (options.series.pie.tilt<=0.8)
+					drawShadow();
+				drawPie();
+			}
+			if (attempts >= redrawAttempts) {
+				clear();
+				target.prepend('<div class="error">Could not draw pie with labels contained inside canvas</div>');
+			}
+
+			if ( plot.setSeries && plot.insertLegend )
+			{
+				plot.setSeries(slices);
+				plot.insertLegend();
+			}
+
+			// we're actually done at this point, just defining internal functions at this point
+
+			function clear()
+			{
+				ctx.clearRect(0,0,canvas.width,canvas.height);
+				target.children().filter('.pieLabel, .pieLabelBackground').remove();
+			}
+
+			function drawShadow()
+			{
+				var shadowLeft = 5;
+				var shadowTop = 15;
+				var edge = 10;
+				var alpha = 0.02;
+
+				// set radius
+				if (options.series.pie.radius>1)
+					var radius = options.series.pie.radius;
+				else
+					var radius = maxRadius * options.series.pie.radius;
+
+				if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge)
+					return;	// shadow would be outside canvas, so don't draw it
+
+				ctx.save();
+				ctx.translate(shadowLeft,shadowTop);
+				ctx.globalAlpha = alpha;
+				ctx.fillStyle = '#000';
+
+				// center and rotate to starting position
+				ctx.translate(centerLeft,centerTop);
+				ctx.scale(1, options.series.pie.tilt);
+
+				//radius -= edge;
+				for (var i=1; i<=edge; i++)
+				{
+					ctx.beginPath();
+					ctx.arc(0,0,radius,0,Math.PI*2,false);
+					ctx.fill();
+					radius -= i;
+				}
+
+				ctx.restore();
+			}
+
+			function drawPie()
+			{
+				startAngle = Math.PI*options.series.pie.startAngle;
+
+				// set radius
+				if (options.series.pie.radius>1)
+					var radius = options.series.pie.radius;
+				else
+					var radius = maxRadius * options.series.pie.radius;
+
+				// center and rotate to starting position
+				ctx.save();
+				ctx.translate(centerLeft,centerTop);
+				ctx.scale(1, options.series.pie.tilt);
+				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
+
+				// draw slices
+				ctx.save();
+				var currentAngle = startAngle;
+				for (var i = 0; i < slices.length; ++i)
+				{
+					slices[i].startAngle = currentAngle;
+					drawSlice(slices[i].angle, slices[i].color, true);
+				}
+				ctx.restore();
+
+				// draw slice outlines
+				ctx.save();
+				ctx.lineWidth = options.series.pie.stroke.width;
+				currentAngle = startAngle;
+				for (var i = 0; i < slices.length; ++i)
+					drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
+				ctx.restore();
+
+				// draw donut hole
+				drawDonutHole(ctx);
+
+				// draw labels
+				if (options.series.pie.label.show)
+					drawLabels();
+
+				// restore to original state
+				ctx.restore();
+
+				function drawSlice(angle, color, fill)
+				{
+					if (angle<=0)
+						return;
+
+					if (fill)
+						ctx.fillStyle = color;
+					else
+					{
+						ctx.strokeStyle = color;
+						ctx.lineJoin = 'round';
+					}
+
+					ctx.beginPath();
+					if (Math.abs(angle - Math.PI*2) > 0.000000001)
+						ctx.moveTo(0,0); // Center of the pie
+					else if ($.browser.msie)
+						angle -= 0.0001;
+					//ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera
+					ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false);
+					ctx.closePath();
+					//ctx.rotate(angle); // This doesn't work properly in Opera
+					currentAngle += angle;
+
+					if (fill)
+						ctx.fill();
+					else
+						ctx.stroke();
+				}
+
+				function drawLabels()
+				{
+					var currentAngle = startAngle;
+
+					// set radius
+					if (options.series.pie.label.radius>1)
+						var radius = options.series.pie.label.radius;
+					else
+						var radius = maxRadius * options.series.pie.label.radius;
+
+					for (var i = 0; i < slices.length; ++i)
+					{
+						if (slices[i].percent >= options.series.pie.label.threshold*100)
+							drawLabel(slices[i], currentAngle, i);
+						currentAngle += slices[i].angle;
+					}
+
+					function drawLabel(slice, startAngle, index)
+					{
+						if (slice.data[0][1]==0)
+							return;
+
+						// format label text
+						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
+						if (lf)
+							text = lf(slice.label, slice);
+						else
+							text = slice.label;
+						if (plf)
+							text = plf(text, slice);
+
+						var halfAngle = ((startAngle+slice.angle) + startAngle)/2;
+						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
+						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
+
+						var html = '<span class="pieLabel" id="pieLabel'+index+'" style="position:absolute;top:' + y + 'px;left:' + x + 'px;">' + text + "</span>";
+						target.append(html);
+						var label = target.children('#pieLabel'+index);
+						var labelTop = (y - label.height()/2);
+						var labelLeft = (x - label.width()/2);
+						label.css('top', labelTop);
+						label.css('left', labelLeft);
+
+						// check to make sure that the label is not outside the canvas
+						if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0)
+							redraw = true;
+
+						if (options.series.pie.label.background.opacity != 0) {
+							// put in the transparent background separately to avoid blended labels and label boxes
+							var c = options.series.pie.label.background.color;
+							if (c == null) {
+								c = slice.color;
+							}
+							var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;';
+							$('<div class="pieLabelBackground" style="position:absolute;width:' + label.width() + 'px;height:' + label.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').insertBefore(label).css('opacity', options.series.pie.label.background.opacity);
+						}
+					} // end individual label function
+				} // end drawLabels function
+			} // end drawPie function
+		} // end draw function
+
+		// Placed here because it needs to be accessed from multiple locations
+		function drawDonutHole(layer)
+		{
+			// draw donut hole
+			if(options.series.pie.innerRadius > 0)
+			{
+				// subtract the center
+				layer.save();
+				innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
+				layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color
+				layer.beginPath();
+				layer.fillStyle = options.series.pie.stroke.color;
+				layer.arc(0,0,innerRadius,0,Math.PI*2,false);
+				layer.fill();
+				layer.closePath();
+				layer.restore();
+
+				// add inner stroke
+				layer.save();
+				layer.beginPath();
+				layer.strokeStyle = options.series.pie.stroke.color;
+				layer.arc(0,0,innerRadius,0,Math.PI*2,false);
+				layer.stroke();
+				layer.closePath();
+				layer.restore();
+				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
+			}
+		}
+
+		//-- Additional Interactive related functions --
+
+		function isPointInPoly(poly, pt)
+		{
+			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
+				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
+				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
+				&& (c = !c);
+			return c;
+		}
+
+		function findNearbySlice(mouseX, mouseY)
+		{
+			var slices = plot.getData(),
+				options = plot.getOptions(),
+				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+			for (var i = 0; i < slices.length; ++i)
+			{
+				var s = slices[i];
+
+				if(s.pie.show)
+				{
+					ctx.save();
+					ctx.beginPath();
+					ctx.moveTo(0,0); // Center of the pie
+					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
+					ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false);
+					ctx.closePath();
+					x = mouseX-centerLeft;
+					y = mouseY-centerTop;
+					if(ctx.isPointInPath)
+					{
+						if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop))
+						{
+							//alert('found slice!');
+							ctx.restore();
+							return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};
+						}
+					}
+					else
+					{
+						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
+						p1X = (radius * Math.cos(s.startAngle));
+						p1Y = (radius * Math.sin(s.startAngle));
+						p2X = (radius * Math.cos(s.startAngle+(s.angle/4)));
+						p2Y = (radius * Math.sin(s.startAngle+(s.angle/4)));
+						p3X = (radius * Math.cos(s.startAngle+(s.angle/2)));
+						p3Y = (radius * Math.sin(s.startAngle+(s.angle/2)));
+						p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5)));
+						p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5)));
+						p5X = (radius * Math.cos(s.startAngle+s.angle));
+						p5Y = (radius * Math.sin(s.startAngle+s.angle));
+						arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]];
+						arrPoint = [x,y];
+						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
+						if(isPointInPoly(arrPoly, arrPoint))
+						{
+							ctx.restore();
+							return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i};
+						}
+					}
+					ctx.restore();
+				}
+			}
+
+			return null;
+		}
+
+		function onMouseMove(e)
+		{
+			triggerClickHoverEvent('plothover', e);
+		}
+
+        function onClick(e)
+		{
+			triggerClickHoverEvent('plotclick', e);
+        }
+
+		// trigger click or hover event (they send the same parameters so we share their code)
+		function triggerClickHoverEvent(eventname, e)
+		{
+			var offset = plot.offset(),
+				canvasX = parseInt(e.pageX - offset.left),
+				canvasY =  parseInt(e.pageY - offset.top),
+				item = findNearbySlice(canvasX, canvasY);
+
+			if (options.grid.autoHighlight)
+			{
+				// clear auto-highlights
+				for (var i = 0; i < highlights.length; ++i)
+				{
+					var h = highlights[i];
+					if (h.auto == eventname && !(item && h.series == item.series))
+						unhighlight(h.series);
+				}
+			}
+
+			// highlight the slice
+			if (item)
+			    highlight(item.series, eventname);
+
+			// trigger any hover bind events
+			var pos = { pageX: e.pageX, pageY: e.pageY };
+			target.trigger(eventname, [ pos, item ]);
+		}
+
+		function highlight(s, auto)
+		{
+			if (typeof s == "number")
+				s = series[s];
+
+			var i = indexOfHighlight(s);
+			if (i == -1)
+			{
+				highlights.push({ series: s, auto: auto });
+				plot.triggerRedrawOverlay();
+			}
+			else if (!auto)
+				highlights[i].auto = false;
+		}
+
+		function unhighlight(s)
+		{
+			if (s == null)
+			{
+				highlights = [];
+				plot.triggerRedrawOverlay();
+			}
+
+			if (typeof s == "number")
+				s = series[s];
+
+			var i = indexOfHighlight(s);
+			if (i != -1)
+			{
+				highlights.splice(i, 1);
+				plot.triggerRedrawOverlay();
+			}
+		}
+
+		function indexOfHighlight(s)
+		{
+			for (var i = 0; i < highlights.length; ++i)
+			{
+				var h = highlights[i];
+				if (h.series == s)
+					return i;
+			}
+			return -1;
+		}
+
+		function drawOverlay(plot, octx)
+		{
+			//alert(options.series.pie.radius);
+			var options = plot.getOptions();
+			//alert(options.series.pie.radius);
+
+			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+			octx.save();
+			octx.translate(centerLeft, centerTop);
+			octx.scale(1, options.series.pie.tilt);
+
+			for (i = 0; i < highlights.length; ++i)
+				drawHighlight(highlights[i].series);
+
+			drawDonutHole(octx);
+
+			octx.restore();
+
+			function drawHighlight(series)
+			{
+				if (series.angle < 0) return;
+
+				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
+				octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor
+
+				octx.beginPath();
+				if (Math.abs(series.angle - Math.PI*2) > 0.000000001)
+					octx.moveTo(0,0); // Center of the pie
+				octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false);
+				octx.closePath();
+				octx.fill();
+			}
+
+		}
+
+	} // end init (plugin body)
+
+	// define pie specific options and their default values
+	var options = {
+		series: {
+			pie: {
+				show: false,
+				radius: 'auto',	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
+				innerRadius:0, /* for donut */
+				startAngle: 3/2,
+				tilt: 1,
+				offset: {
+					top: 0,
+					left: 'auto'
+				},
+				stroke: {
+					color: '#FFF',
+					width: 1
+				},
+				label: {
+					show: 'auto',
+					formatter: function(label, slice){
+						return '<div style="font-size:x-small;text-align:center;padding:2px;color:'+slice.color+';">'+label+'<br/>'+Math.round(slice.percent)+'%</div>';
+					},	// formatter function
+					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
+					background: {
+						color: null,
+						opacity: 0
+					},
+					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
+				},
+				combine: {
+					threshold: -1,	// percentage at which to combine little slices into one larger slice
+					color: null,	// color to give the new slice (auto-generated if null)
+					label: 'Other'	// label to give the new slice
+				},
+				highlight: {
+					//color: '#FFF',		// will add this functionality once parseColor is available
+					opacity: 0.5
+				}
+			}
+		}
+	};
+
+	$.plot.plugins.push({
+		init: init,
+		options: options,
+		name: "pie",
+		version: "1.0"
+	});
+})(jQuery);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 60 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.resize.js


+ 212 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.spline.js

@@ -0,0 +1,212 @@
+/**
+ * Flot plugin that provides spline interpolation for line graphs
+ * author: Alex Bardas < alex.bardas@gmail.com >
+ * modified by: Avi Kohn https://github.com/AMKohn
+ * based on the spline interpolation described at:
+ *		 http://scaledinnovation.com/analytics/splines/aboutSplines.html
+ *
+ * Example usage: (add in plot options series object)
+ *		for linespline:
+ *			series: {
+ *				...
+ *				lines: {
+ *					show: false
+ *				},
+ *				splines: {
+ *					show: true,
+ *					tension: x, (float between 0 and 1, defaults to 0.5),
+ *					lineWidth: y (number, defaults to 2),
+ *					fill: z (float between 0 .. 1 or false, as in flot documentation)
+ *				},
+ *				...
+ *			}
+ *		areaspline:
+ *			series: {
+ *				...
+ *				lines: {
+ *					show: true,
+ *					lineWidth: 0, (line drawing will not execute)
+ *					fill: x, (float between 0 .. 1, as in flot documentation)
+ *					...
+ *				},
+ *				splines: {
+ *					show: true,
+ *					tension: 0.5 (float between 0 and 1)
+ *				},
+ *				...
+ *			}
+ *
+ */
+
+(function($) {
+    'use strict'
+
+    /**
+     * @param {Number} x0, y0, x1, y1: coordinates of the end (knot) points of the segment
+     * @param {Number} x2, y2: the next knot (not connected, but needed to calculate p2)
+     * @param {Number} tension: control how far the control points spread
+     * @return {Array}: p1 -> control point, from x1 back toward x0
+     * 					p2 -> the next control point, returned to become the next segment's p1
+     *
+     * @api private
+     */
+    function getControlPoints(x0, y0, x1, y1, x2, y2, tension) {
+
+        var pow = Math.pow,
+            sqrt = Math.sqrt,
+            d01, d12, fa, fb, p1x, p1y, p2x, p2y;
+
+        //  Scaling factors: distances from this knot to the previous and following knots.
+        d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
+        d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
+
+        fa = tension * d01 / (d01 + d12);
+        fb = tension - fa;
+
+        p1x = x1 + fa * (x0 - x2);
+        p1y = y1 + fa * (y0 - y2);
+
+        p2x = x1 - fb * (x0 - x2);
+        p2y = y1 - fb * (y0 - y2);
+
+        return [p1x, p1y, p2x, p2y];
+    }
+
+    var line = [];
+
+    function drawLine(points, ctx, height, fill, seriesColor) {
+        var c = $.color.parse(seriesColor);
+
+        c.a = typeof fill == "number" ? fill : .3;
+        c.normalize();
+        c = c.toString();
+
+        ctx.beginPath();
+        ctx.moveTo(points[0][0], points[0][1]);
+
+        var plength = points.length;
+
+        for (var i = 0; i < plength; i++) {
+            ctx[points[i][3]].apply(ctx, points[i][2]);
+        }
+
+        ctx.stroke();
+
+        ctx.lineWidth = 0;
+        ctx.lineTo(points[plength - 1][0], height);
+        ctx.lineTo(points[0][0], height);
+
+        ctx.closePath();
+
+        if (fill !== false) {
+            ctx.fillStyle = c;
+            ctx.fill();
+        }
+    }
+
+    /**
+     * @param {Object} ctx: canvas context
+     * @param {String} type: accepted strings: 'bezier' or 'quadratic' (defaults to quadratic)
+     * @param {Array} points: 2 points for which to draw the interpolation
+     * @param {Array} cpoints: control points for those segment points
+     *
+     * @api private
+     */
+    function queue(ctx, type, points, cpoints) {
+        if (type === void 0 || (type !== 'bezier' && type !== 'quadratic')) {
+            type = 'quadratic';
+        }
+        type = type + 'CurveTo';
+
+        if (line.length == 0) line.push([points[0], points[1], cpoints.concat(points.slice(2)), type]);
+        else if (type == "quadraticCurveTo" && points.length == 2) {
+            cpoints = cpoints.slice(0, 2).concat(points);
+
+            line.push([points[0], points[1], cpoints, type]);
+        }
+        else line.push([points[2], points[3], cpoints.concat(points.slice(2)), type]);
+    }
+
+    /**
+     * @param {Object} plot
+     * @param {Object} ctx: canvas context
+     * @param {Object} series
+     *
+     * @api private
+     */
+
+    function drawSpline(plot, ctx, series) {
+        // Not interested if spline is not requested
+        if (series.splines.show !== true) {
+            return;
+        }
+
+        var cp = [],
+        // array of control points
+            tension = series.splines.tension || 0.5,
+            idx, x, y, points = series.datapoints.points,
+            ps = series.datapoints.pointsize,
+            plotOffset = plot.getPlotOffset(),
+            len = points.length,
+            pts = [];
+
+        line = [];
+
+        // Cannot display a linespline/areaspline if there are less than 3 points
+        if (len / ps < 4) {
+            $.extend(series.lines, series.splines);
+            return;
+        }
+
+        for (idx = 0; idx < len; idx += ps) {
+            x = points[idx];
+            y = points[idx + 1];
+            if (x == null || x < series.xaxis.min || x > series.xaxis.max || y < series.yaxis.min || y > series.yaxis.max) {
+                continue;
+            }
+
+            pts.push(series.xaxis.p2c(x) + plotOffset.left, series.yaxis.p2c(y) + plotOffset.top);
+        }
+
+        len = pts.length;
+
+        // Draw an open curve, not connected at the ends
+        for (idx = 0; idx < len - 2; idx += 2) {
+            cp = cp.concat(getControlPoints.apply(this, pts.slice(idx, idx + 6).concat([tension])));
+        }
+
+        ctx.save();
+        ctx.strokeStyle = series.color;
+        ctx.lineWidth = series.splines.lineWidth;
+
+        queue(ctx, 'quadratic', pts.slice(0, 4), cp.slice(0, 2));
+
+        for (idx = 2; idx < len - 3; idx += 2) {
+            queue(ctx, 'bezier', pts.slice(idx, idx + 4), cp.slice(2 * idx - 2, 2 * idx + 2));
+        }
+
+        queue(ctx, 'quadratic', pts.slice(len - 2, len), [cp[2 * len - 10], cp[2 * len - 9], pts[len - 4], pts[len - 3]]);
+
+        drawLine(line, ctx, plot.height() + 10, series.splines.fill, series.color);
+
+        ctx.restore();
+    }
+
+    $.plot.plugins.push({
+        init: function(plot) {
+            plot.hooks.drawSeries.push(drawSpline);
+        },
+        options: {
+            series: {
+                splines: {
+                    show: false,
+                    lineWidth: 2,
+                    tension: 0.5,
+                    fill: false
+                }
+            }
+        },
+        name: 'spline',
+        version: '0.8.2'
+    });
+})(jQuery);

+ 71 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.symbol.js

@@ -0,0 +1,71 @@
+/* Flot plugin that adds some extra symbols for plotting points.
+
+ Copyright (c) 2007-2014 IOLA and Ole Laursen.
+ Licensed under the MIT license.
+
+ The symbols are accessed as strings through the standard symbol options:
+
+ series: {
+ points: {
+ symbol: "square" // or "diamond", "triangle", "cross"
+ }
+ }
+
+ */
+
+(function ($) {
+    function processRawData(plot, series, datapoints) {
+        // we normalize the area of each symbol so it is approximately the
+        // same as a circle of the given radius
+
+        var handlers = {
+            square: function (ctx, x, y, radius, shadow) {
+                // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
+                var size = radius * Math.sqrt(Math.PI) / 2;
+                ctx.rect(x - size, y - size, size + size, size + size);
+            },
+            diamond: function (ctx, x, y, radius, shadow) {
+                // pi * r^2 = 2s^2  =>  s = r * sqrt(pi/2)
+                var size = radius * Math.sqrt(Math.PI / 2);
+                ctx.moveTo(x - size, y);
+                ctx.lineTo(x, y - size);
+                ctx.lineTo(x + size, y);
+                ctx.lineTo(x, y + size);
+                ctx.lineTo(x - size, y);
+            },
+            triangle: function (ctx, x, y, radius, shadow) {
+                // pi * r^2 = 1/2 * s^2 * sin (pi / 3)  =>  s = r * sqrt(2 * pi / sin(pi / 3))
+                var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));
+                var height = size * Math.sin(Math.PI / 3);
+                ctx.moveTo(x - size/2, y + height/2);
+                ctx.lineTo(x + size/2, y + height/2);
+                if (!shadow) {
+                    ctx.lineTo(x, y - height/2);
+                    ctx.lineTo(x - size/2, y + height/2);
+                }
+            },
+            cross: function (ctx, x, y, radius, shadow) {
+                // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
+                var size = radius * Math.sqrt(Math.PI) / 2;
+                ctx.moveTo(x - size, y - size);
+                ctx.lineTo(x + size, y + size);
+                ctx.moveTo(x - size, y + size);
+                ctx.lineTo(x + size, y - size);
+            }
+        };
+
+        var s = series.points.symbol;
+        if (handlers[s])
+            series.points.symbol = handlers[s];
+    }
+
+    function init(plot) {
+        plot.hooks.processDatapoints.push(processRawData);
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        name: 'symbols',
+        version: '1.0'
+    });
+})(jQuery);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 12 - 0
leiSP-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.tooltip.min.js


+ 182 - 0
leiSP-admin/src/main/resources/static/ajax/libs/fullscreen/jquery.fullscreen.js

@@ -0,0 +1,182 @@
+/**
+ * 基于jQuery FullScreen修改
+ * 新增支持IE全屏显示
+ * Copyright (c) 2019 LeiSP
+ */
+(function(jQuery) {
+    
+    /**
+     * Sets or gets the fullscreen state.
+     * 
+     * @param {boolean=} state
+     *            True to enable fullscreen mode, false to disable it. If not
+     *            specified then the current fullscreen state is returned.
+     * @return {boolean|Element|jQuery|null}
+     *            When querying the fullscreen state then the current fullscreen
+     *            element (or true if browser doesn't support it) is returned
+     *            when browser is currently in full screen mode. False is returned
+     *            if browser is not in full screen mode. Null is returned if 
+     *            browser doesn't support fullscreen mode at all. When setting 
+     *            the fullscreen state then the current jQuery selection is 
+     *            returned for chaining.
+     * @this {jQuery}
+     */
+    function fullScreen(state)
+    {
+        var e, func, doc;
+        
+        // Do nothing when nothing was selected
+        if (!this.length) return this;
+        
+        // We only use the first selected element because it doesn't make sense
+        // to fullscreen multiple elements.
+        e = (/** @type {Element} */ this[0]);
+        
+        // Find the real element and the document (Depends on whether the
+        // document itself or a HTML element was selected)
+        if (e.ownerDocument)
+        {
+            doc = e.ownerDocument;
+        }
+        else
+        {
+            doc = e;
+            e = doc.documentElement;
+        }
+        
+        // When no state was specified then return the current state.
+        if (state == null)
+        {
+            // When fullscreen mode is not supported then return null
+            if (!((/** @type {?Function} */ doc["exitFullscreen"])
+                || (/** @type {?Function} */ doc["webkitExitFullscreen"])
+                || (/** @type {?Function} */ doc["webkitCancelFullScreen"])
+                || (/** @type {?Function} */ doc["msExitFullscreen"])
+                || (/** @type {?Function} */ doc["mozCancelFullScreen"])))
+            {
+                return null;
+            }
+            
+            // Check fullscreen state
+            state = !!doc["fullscreenElement"]
+                || !!doc["msFullscreenElement"]
+                || !!doc["webkitIsFullScreen"]
+                || !!doc["mozFullScreen"];
+            if (!state) return state;
+            
+            // Return current fullscreen element or "true" if browser doesn't
+            // support this
+            return (/** @type {?Element} */ doc["fullscreenElement"])
+                || (/** @type {?Element} */ doc["webkitFullscreenElement"])
+                || (/** @type {?Element} */ doc["webkitCurrentFullScreenElement"])
+                || (/** @type {?Element} */ doc["msFullscreenElement"])
+                || (/** @type {?Element} */ doc["mozFullScreenElement"])
+                || state;
+        }
+        
+        // When state was specified then enter or exit fullscreen mode.
+        if (state)
+        {
+            // Enter fullscreen
+            func = (/** @type {?Function} */ e["requestFullscreen"])
+                || (/** @type {?Function} */ e["webkitRequestFullscreen"])
+                || (/** @type {?Function} */ e["webkitRequestFullScreen"])
+                || (/** @type {?Function} */ e["msRequestFullscreen"])
+                || (/** @type {?Function} */ e["mozRequestFullScreen"]);
+            if (func) 
+            {
+                func.call(e);
+            }
+            return this;
+        }
+        else
+        {
+            // Exit fullscreen
+            func = (/** @type {?Function} */ doc["exitFullscreen"])
+                || (/** @type {?Function} */ doc["webkitExitFullscreen"])
+                || (/** @type {?Function} */ doc["webkitCancelFullScreen"])
+                || (/** @type {?Function} */ doc["msExitFullscreen"])
+                || (/** @type {?Function} */ doc["mozCancelFullScreen"]);
+            if (func) func.call(doc);
+            return this;
+        }
+    }
+    
+    /**
+     * Toggles the fullscreen mode.
+     * 
+     * @return {!jQuery}
+     *            The jQuery selection for chaining.
+     * @this {jQuery}
+     */
+    function toggleFullScreen()
+    {
+        return (/** @type {!jQuery} */ fullScreen.call(this, 
+            !fullScreen.call(this)));
+    }
+    
+    /**
+     * Handles the browser-specific fullscreenchange event and triggers
+     * a jquery event for it.
+     *
+     * @param {?Event} event
+     *            The fullscreenchange event.
+     */
+    function fullScreenChangeHandler(event)
+    {
+        jQuery(document).trigger(new jQuery.Event("fullscreenchange"));
+    }
+    
+    /**
+     * Handles the browser-specific fullscreenerror event and triggers
+     * a jquery event for it.
+     *
+     * @param {?Event} event
+     *            The fullscreenerror event.
+     */
+    function fullScreenErrorHandler(event)
+    {
+        jQuery(document).trigger(new jQuery.Event("fullscreenerror"));
+    }
+    
+    /**
+     * Installs the fullscreenchange event handler.
+     */
+    function installFullScreenHandlers()
+    {
+        var e, change, error;
+        
+        // Determine event name
+        e = document;
+        if (e["webkitCancelFullScreen"])
+        {
+            change = "webkitfullscreenchange";
+            error = "webkitfullscreenerror";
+        }
+        else if (e["msExitFullscreen"])
+        {
+            change = "MSFullscreenChange";
+            error = "MSFullscreenError";
+        }
+        else if (e["mozCancelFullScreen"])
+        {
+            change = "mozfullscreenchange";
+            error = "mozfullscreenerror";
+        }
+        else 
+        {
+            change = "fullscreenchange";
+            error = "fullscreenerror";
+        }
+    
+        // Install the event handlers
+        jQuery(document).bind(change, fullScreenChangeHandler);
+        jQuery(document).bind(error, fullScreenErrorHandler);
+    }
+    
+    jQuery.fn["fullScreen"] = fullScreen;
+    jQuery.fn["toggleFullScreen"] = toggleFullScreen;
+    installFullScreenHandlers();
+    
+    })(jQuery);
+    

+ 0 - 0
leiSP-admin/src/main/resources/static/ajax/libs/iCheck/custom.css


Vissa filer visades inte eftersom för många filer har ändrats