Forráskód Böngészése

1.优化上传个人头像错误提示框。
2.增加bootstrap fileinput插件。
3.增加通过Excel文件批量添加用户功能。

yangzhengze 7 éve
szülő
commit
dcbd2b2210
24 módosított fájl, 10614 hozzáadás és 85 törlés
  1. 15 10
      pom.xml
  2. 53 51
      sql/ry_20180604.sql
  3. 78 0
      src/main/java/com/ruoyi/common/utils/ExcelImportUtils.java
  4. 34 14
      src/main/java/com/ruoyi/project/system/user/controller/UserController.java
  5. 10 1
      src/main/java/com/ruoyi/project/system/user/mapper/UserMapper.java
  6. 10 1
      src/main/java/com/ruoyi/project/system/user/service/IUserService.java
  7. 261 5
      src/main/java/com/ruoyi/project/system/user/service/UserServiceImpl.java
  8. 1 1
      src/main/resources/application.yml
  9. 33 0
      src/main/resources/mybatis/system/UserMapper.xml
  10. 554 0
      src/main/resources/static/css/plugins/fileinput/fileinput.css
  11. 12 0
      src/main/resources/static/css/plugins/fileinput/fileinput.min.css
  12. 4463 0
      src/main/resources/static/js/plugins/fileinput/fileinput.js
  13. 12 0
      src/main/resources/static/js/plugins/fileinput/fileinput.min.js
  14. 100 0
      src/main/resources/static/js/plugins/fileinput/locales/zh.js
  15. 2471 0
      src/main/resources/static/js/plugins/fileinput/plugins/piexif.js
  16. 1 0
      src/main/resources/static/js/plugins/fileinput/plugins/piexif.min.js
  17. 812 0
      src/main/resources/static/js/plugins/fileinput/plugins/purify.js
  18. 1 0
      src/main/resources/static/js/plugins/fileinput/plugins/purify.min.js
  19. 1590 0
      src/main/resources/static/js/plugins/fileinput/plugins/sortable.js
  20. 1 0
      src/main/resources/static/js/plugins/fileinput/plugins/sortable.min.js
  21. 58 1
      src/main/resources/static/ruoyi/system/user/user.js
  22. BIN
      src/main/resources/static/template/用户导入模板.xlsx
  23. 1 1
      src/main/resources/templates/system/user/profile/avatar.html
  24. 43 0
      src/main/resources/templates/system/user/user.html

+ 15 - 10
pom.xml

@@ -40,6 +40,7 @@
 		<kaptcha.version>2.3.2</kaptcha.version>
 		<swagger.version>2.7.0</swagger.version>
 		<jsoup.version>1.11.3</jsoup.version>
+		<poi.version>3.17</poi.version>
 	</properties>
 
 	<dependencies>
@@ -83,13 +84,6 @@
 			<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>
-		
 		<!-- thymeleaf网页解析 -->
 		<dependency>
 			<groupId>net.sourceforge.nekohtml</groupId>
@@ -238,14 +232,25 @@
 			<artifactId>springfox-swagger-ui</artifactId>
 			<version>${swagger.version}</version>
 		</dependency>
-		
 		<!-- HTML解析器 -->
 		<dependency>
 			<groupId>org.jsoup</groupId>
 			<artifactId>jsoup</artifactId>
 			<version>${jsoup.version}</version>
 		</dependency>
-		 
+
+		<!-- POI-->
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi</artifactId>
+			<version>${poi.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+			<version>${poi.version}</version>
+		</dependency>
 	</dependencies>
 	
 	<build>
@@ -255,7 +260,7 @@
 				<groupId>org.springframework.boot</groupId>
 				<artifactId>spring-boot-maven-plugin</artifactId>
 				<configuration>
-					<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+					<executable>true</executable>
 				</configuration>
 			</plugin>
 		</plugins>

+ 53 - 51
sql/ry_20180604.sql

@@ -171,68 +171,69 @@ insert into sys_menu values('22', '用户删除', '4', '4',  '#',  'F', '0', 'sy
 insert into sys_menu values('23', '用户保存', '4', '5',  '#',  'F', '0', 'system:user:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 insert into sys_menu values('24', '批量删除', '4', '6',  '#',  'F', '0', 'system:user:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 insert into sys_menu values('25', '重置密码', '4', '7',  '#',  'F', '0', 'system:user:resetPwd',     '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('26', '批量新增', '4', '8',  '#',  'F', '0', 'system:user:batchAdd',  '#',   'admin', '2018-06-02 11-33-00',   'ry', '2018-06-02 11-33-00', '');
 -- 角色管理按钮
-insert into sys_menu values('26', '角色查询', '5', '1',  '#',  'F', '0', 'system:role:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('27', '角色新增', '5', '2',  '#',  'F', '0', 'system:role:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('28', '角色修改', '5', '3',  '#',  'F', '0', 'system:role:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('29', '角色删除', '5', '4',  '#',  'F', '0', 'system:role:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('30', '角色保存', '5', '5',  '#',  'F', '0', 'system:role:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('31', '批量删除', '5', '6',  '#',  'F', '0', 'system:role:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('27', '角色查询', '5', '1',  '#',  'F', '0', 'system:role:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('28', '角色新增', '5', '2',  '#',  'F', '0', 'system:role:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('29', '角色修改', '5', '3',  '#',  'F', '0', 'system:role:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('30', '角色删除', '5', '4',  '#',  'F', '0', 'system:role:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('31', '角色保存', '5', '5',  '#',  'F', '0', 'system:role:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('32', '批量删除', '5', '6',  '#',  'F', '0', 'system:role:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 菜单管理按钮
-insert into sys_menu values('32', '菜单查询', '6', '1',  '#',  'F', '0', 'system:menu:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('33', '菜单新增', '6', '2',  '#',  'F', '0', 'system:menu:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('34', '菜单修改', '6', '3',  '#',  'F', '0', 'system:menu:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('35', '菜单删除', '6', '4',  '#',  'F', '0', 'system:menu:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('36', '菜单保存', '6', '5',  '#',  'F', '0', 'system:menu:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('33', '菜单查询', '6', '1',  '#',  'F', '0', 'system:menu:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('34', '菜单新增', '6', '2',  '#',  'F', '0', 'system:menu:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('35', '菜单修改', '6', '3',  '#',  'F', '0', 'system:menu:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('36', '菜单删除', '6', '4',  '#',  'F', '0', 'system:menu:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('37', '菜单保存', '6', '5',  '#',  'F', '0', 'system:menu:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 部门管理按钮
-insert into sys_menu values('37', '部门查询', '7', '1',  '#',  'F', '0', 'system:dept:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('38', '部门新增', '7', '2',  '#',  'F', '0', 'system:dept:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('39', '部门修改', '7', '3',  '#',  'F', '0', 'system:dept:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('40', '部门删除', '7', '4',  '#',  'F', '0', 'system:dept:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('41', '部门保存', '7', '5',  '#',  'F', '0', 'system:dept:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('38', '部门查询', '7', '1',  '#',  'F', '0', 'system:dept:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('39', '部门新增', '7', '2',  '#',  'F', '0', 'system:dept:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('40', '部门修改', '7', '3',  '#',  'F', '0', 'system:dept:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('41', '部门删除', '7', '4',  '#',  'F', '0', 'system:dept:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('42', '部门保存', '7', '5',  '#',  'F', '0', 'system:dept:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 岗位管理按钮
-insert into sys_menu values('42', '岗位查询', '8', '1',  '#',  'F', '0', 'system:post:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('43', '岗位新增', '8', '2',  '#',  'F', '0', 'system:post:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('44', '岗位修改', '8', '3',  '#',  'F', '0', 'system:post:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('45', '岗位删除', '8', '4',  '#',  'F', '0', 'system:post:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('46', '岗位保存', '8', '5',  '#',  'F', '0', 'system:post:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('47', '批量删除', '8', '6',  '#',  'F', '0', 'system:post:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('43', '岗位查询', '8', '1',  '#',  'F', '0', 'system:post:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('44', '岗位新增', '8', '2',  '#',  'F', '0', 'system:post:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('45', '岗位修改', '8', '3',  '#',  'F', '0', 'system:post:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('46', '岗位删除', '8', '4',  '#',  'F', '0', 'system:post:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('47', '岗位保存', '8', '5',  '#',  'F', '0', 'system:post:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('48', '批量删除', '8', '6',  '#',  'F', '0', 'system:post:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 字典管理按钮
-insert into sys_menu values('48', '字典查询', '9', '1', '#',  'F', '0', 'system:dict:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('49', '字典新增', '9', '2', '#',  'F', '0', 'system:dict:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('50', '字典修改', '9', '3', '#',  'F', '0', 'system:dict:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('51', '字典删除', '9', '4', '#',  'F', '0', 'system:dict:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('52', '字典保存', '9', '5', '#',  'F', '0', 'system:dict:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('53', '批量删除', '9', '6', '#',  'F', '0', 'system:dict:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('49', '字典查询', '9', '1', '#',  'F', '0', 'system:dict:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('50', '字典新增', '9', '2', '#',  'F', '0', 'system:dict:add',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('51', '字典修改', '9', '3', '#',  'F', '0', 'system:dict:edit',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('52', '字典删除', '9', '4', '#',  'F', '0', 'system:dict:remove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('53', '字典保存', '9', '5', '#',  'F', '0', 'system:dict:save',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('54', '批量删除', '9', '6', '#',  'F', '0', 'system:dict:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 参数设置按钮
-insert into sys_menu values('54', '参数查询', '10', '1', '#',  'F', '0', 'system:config:list',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('55', '参数新增', '10', '2', '#',  'F', '0', 'system:config:add',               '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('56', '参数修改', '10', '3', '#',  'F', '0', 'system:config:edit',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('57', '参数删除', '10', '4', '#',  'F', '0', 'system:config:remove',            '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('58', '参数保存', '10', '5', '#',  'F', '0', 'system:config:save',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('59', '批量删除', '10', '6', '#',  'F', '0', 'system:config:batchRemove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('55', '参数查询', '10', '1', '#',  'F', '0', 'system:config:list',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('56', '参数新增', '10', '2', '#',  'F', '0', 'system:config:add',               '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('57', '参数修改', '10', '3', '#',  'F', '0', 'system:config:edit',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('58', '参数删除', '10', '4', '#',  'F', '0', 'system:config:remove',            '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('59', '参数保存', '10', '5', '#',  'F', '0', 'system:config:save',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('60', '批量删除', '10', '6', '#',  'F', '0', 'system:config:batchRemove',       '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 操作日志按钮
-insert into sys_menu values('60', '操作查询', '11', '1', '#',  'F', '0', 'monitor:operlog:list',            '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('61', '批量删除', '11', '2', '#',  'F', '0', 'monitor:operlog:batchRemove',     '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('62', '详细信息', '11', '3', '#',  'F', '0', 'monitor:operlog:detail',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('61', '操作查询', '11', '1', '#',  'F', '0', 'monitor:operlog:list',            '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('62', '批量删除', '11', '2', '#',  'F', '0', 'monitor:operlog:batchRemove',     '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('63', '详细信息', '11', '3', '#',  'F', '0', 'monitor:operlog:detail',          '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 登录日志按钮
-insert into sys_menu values('63', '登录查询', '12', '1', '#',  'F', '0', 'monitor:logininfor:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('64', '批量删除', '12', '2', '#',  'F', '0', 'monitor:logininfor:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('64', '登录查询', '12', '1', '#',  'F', '0', 'monitor:logininfor:list',         '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('65', '批量删除', '12', '2', '#',  'F', '0', 'monitor:logininfor:batchRemove',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 在线用户按钮
-insert into sys_menu values('65', '在线查询', '13', '1', '#',  'F', '0', 'monitor:online:list',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('66', '批量强退', '13', '2', '#',  'F', '0', 'monitor:online:batchForceLogout', '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('67', '单条强退', '13', '3', '#',  'F', '0', 'monitor:online:forceLogout',      '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('66', '在线查询', '13', '1', '#',  'F', '0', 'monitor:online:list',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('67', '批量强退', '13', '2', '#',  'F', '0', 'monitor:online:batchForceLogout', '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('68', '单条强退', '13', '3', '#',  'F', '0', 'monitor:online:forceLogout',      '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 定时任务按钮
-insert into sys_menu values('68', '任务查询', '14', '1', '#',  'F', '0', 'monitor:job:list',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('69', '任务新增', '14', '2', '#',  'F', '0', 'monitor:job:add',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('70', '任务修改', '14', '3', '#',  'F', '0', 'monitor:job:edit',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('71', '任务删除', '14', '4', '#',  'F', '0', 'monitor:job:remove',           '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('72', '任务保存', '14', '5', '#',  'F', '0', 'monitor:job:save',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('73', '状态修改', '14', '6', '#',  'F', '0', 'monitor:job:changeStatus',     '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('74', '批量删除', '14', '7', '#',  'F', '0', 'monitor:job:batchRemove',      '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('69', '任务查询', '14', '1', '#',  'F', '0', 'monitor:job:list',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('70', '任务新增', '14', '2', '#',  'F', '0', 'monitor:job:add',              '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('71', '任务修改', '14', '3', '#',  'F', '0', 'monitor:job:edit',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('72', '任务删除', '14', '4', '#',  'F', '0', 'monitor:job:remove',           '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('73', '任务保存', '14', '5', '#',  'F', '0', 'monitor:job:save',             '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('74', '状态修改', '14', '6', '#',  'F', '0', 'monitor:job:changeStatus',     '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('75', '批量删除', '14', '7', '#',  'F', '0', 'monitor:job:batchRemove',      '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 -- 代码生成按钮
-insert into sys_menu values('75', '生成查询', '16', '1', '#',  'F', '0', 'tool:gen:list',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
-insert into sys_menu values('76', '生成代码', '16', '2', '#',  'F', '0', 'tool:gen:code',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('76', '生成查询', '16', '1', '#',  'F', '0', 'tool:gen:list',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
+insert into sys_menu values('77', '生成代码', '16', '2', '#',  'F', '0', 'tool:gen:code',  '#', 'admin', '2018-03-16 11-33-00', 'ry', '2018-03-16 11-33-00', '');
 
 
 -- ----------------------------
@@ -341,6 +342,7 @@ insert into sys_role_menu values ('1', '73');
 insert into sys_role_menu values ('1', '74');
 insert into sys_role_menu values ('1', '75');
 insert into sys_role_menu values ('1', '76');
+insert into sys_role_menu values ('1', '77');
 
 -- ----------------------------
 -- 8、用户与岗位关联表  用户1-N岗位

+ 78 - 0
src/main/java/com/ruoyi/common/utils/ExcelImportUtils.java

@@ -0,0 +1,78 @@
+package com.ruoyi.common.utils;
+
+import org.apache.poi.hssf.usermodel.HSSFDateUtil;
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.text.DecimalFormat;
+import java.util.Date;
+
+/**
+ * 导入Excel工具类
+ */
+public class ExcelImportUtils {
+
+    /**  是否是2003的excel,返回true是2003Excel文件**/
+    public  static boolean isExcel2003(String filePath){
+         return filePath.matches("^.+\\.(?i)(xls)$");
+    }
+    /**  是否是2007以上的excel,返回true是2007Excel文件**/
+    public  static boolean isExcel2007(String filePath){
+         return filePath.matches("^.+\\.(?i)(xlsx)$");
+    }
+    /**
+     * 验证EXCEL文件
+     *
+     * @param filePath
+     * @return
+     */
+    public static boolean validateExcel(String filePath) {
+        if (filePath == null || !(isExcel2003(filePath) || isExcel2007(filePath))) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取单元格的值
+     * @param cell
+     * @return
+     */
+    public static String getCellValue(Cell cell) {
+        String value = "";
+        if (cell != null) {
+            switch(cell.getCellTypeEnum()){
+                case NUMERIC:// 数字
+                    value = cell.getNumericCellValue()+ " ";
+                    if(HSSFDateUtil.isCellDateFormatted(cell)){
+                        Date date = cell.getDateCellValue();
+                        if(date != null){
+                            value = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD,date); //  日期格式化
+                        }else{
+                            value = "";
+                        }
+                    }else {
+                        //  解析cell时候 数字类型默认是double类型的 但是想要获取整数类型 需要格式化
+                        value = new DecimalFormat("0").format(cell.getNumericCellValue());
+                    }
+                    break;
+                case STRING: //  字符串
+                    value = cell.getStringCellValue();
+                    break;
+                case BOOLEAN:   //  Boolean类型
+                    value = cell.getBooleanCellValue()+"";
+                    break;
+                case BLANK:   // 空值
+                    value = "";
+                    break;
+                case ERROR: // 错误类型
+                    value ="非法字符";
+                    break;
+                default:
+                    value = "未知类型";
+                    break;
+            }
+
+        }
+        return value.trim();
+    }
+}

+ 34 - 14
src/main/java/com/ruoyi/project/system/user/controller/UserController.java

@@ -1,18 +1,5 @@
 package com.ruoyi.project.system.user.controller;
 
-import java.util.List;
-import org.apache.shiro.authz.annotation.RequiresPermissions;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Controller;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
-
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.web.controller.BaseController;
@@ -25,6 +12,17 @@ import com.ruoyi.project.system.role.service.IRoleService;
 import com.ruoyi.project.system.user.domain.User;
 import com.ruoyi.project.system.user.domain.UserStatus;
 import com.ruoyi.project.system.user.service.IUserService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 /**
  * 用户信息
@@ -35,7 +33,7 @@ import com.ruoyi.project.system.user.service.IUserService;
 @RequestMapping("/system/user")
 public class UserController extends BaseController
 {
-
+    private static final Logger log = LoggerFactory.getLogger(UserController.class);
     private String prefix = "system/user";
 
     @Autowired
@@ -173,6 +171,28 @@ public class UserController extends BaseController
     }
 
     /**
+     * 批量新增用户
+     */
+    @RequiresPermissions("system:user:batchAdd")
+    @Log(title = "系统管理", action = "用户管理-批量新增用户")
+    @PostMapping("/batchAdd")
+    @Transactional(rollbackFor = Exception.class)
+    @ResponseBody
+    public Message batchAdd( @RequestParam("uploadfile") MultipartFile file)
+    {
+        try {
+            if(!file.isEmpty()){
+               int rows=userService.batchImportUsers(file);
+                return Message.success(String.valueOf(rows));
+            }
+            return Message.error();
+        }catch (Exception e){
+            log.error("批量添加用户失败 !", e);
+            return Message.error(e.getMessage());
+        }
+    }
+
+    /**
      * 校验用户名
      */
     @PostMapping("/checkLoginNameUnique")

+ 10 - 1
src/main/java/com/ruoyi/project/system/user/mapper/UserMapper.java

@@ -1,8 +1,9 @@
 package com.ruoyi.project.system.user.mapper;
 
-import java.util.List;
 import com.ruoyi.project.system.user.domain.User;
 
+import java.util.List;
+
 /**
  * 用户表 数据层
  * 
@@ -108,4 +109,12 @@ public interface UserMapper
      * @return 结果
      */
     public User checkEmailUnique(String email);
+
+
+    /**
+     * 批量添加用户
+     * @param userList
+     * @return
+     */
+   public int batchAddUser(List<User> userList);
 }

+ 10 - 1
src/main/java/com/ruoyi/project/system/user/service/IUserService.java

@@ -1,7 +1,9 @@
 package com.ruoyi.project.system.user.service;
 
-import java.util.List;
 import com.ruoyi.project.system.user.domain.User;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 /**
  * 用户 业务层
@@ -133,4 +135,11 @@ public interface IUserService
      */
     public String selectUserPostGroup(Long userId);
 
+    /**
+     *   Excel批量导入用户
+     * @param myFile
+     * @return
+     */
+    public  int batchImportUsers(MultipartFile myFile);
+
 }

+ 261 - 5
src/main/java/com/ruoyi/project/system/user/service/UserServiceImpl.java

@@ -1,13 +1,13 @@
 package com.ruoyi.project.system.user.service;
 
-import java.util.ArrayList;
-import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
 import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.exception.user.UserException;
+import com.ruoyi.common.utils.ExcelImportUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.security.ShiroUtils;
 import com.ruoyi.framework.shiro.service.PasswordService;
+import com.ruoyi.project.system.dept.domain.Dept;
+import com.ruoyi.project.system.dept.service.IDeptService;
 import com.ruoyi.project.system.post.domain.Post;
 import com.ruoyi.project.system.post.mapper.PostMapper;
 import com.ruoyi.project.system.role.domain.Role;
@@ -18,6 +18,19 @@ import com.ruoyi.project.system.user.domain.UserRole;
 import com.ruoyi.project.system.user.mapper.UserMapper;
 import com.ruoyi.project.system.user.mapper.UserPostMapper;
 import com.ruoyi.project.system.user.mapper.UserRoleMapper;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
 
 /**
  * 用户 业务层处理
@@ -27,7 +40,7 @@ import com.ruoyi.project.system.user.mapper.UserRoleMapper;
 @Service("userService")
 public class UserServiceImpl implements IUserService
 {
-
+    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
     @Autowired
     private UserMapper userMapper;
 
@@ -46,6 +59,10 @@ public class UserServiceImpl implements IUserService
     @Autowired
     private PasswordService passwordService;
 
+    @Autowired
+    private IDeptService deptService;
+
+
     /**
      * 根据条件分页查询用户对象
      * 
@@ -179,6 +196,245 @@ public class UserServiceImpl implements IUserService
     }
 
     /**
+     * 根据Execl 批量保存用户
+     *  1.  使用HSSFWorkbook 打开或者创建 “Excel对象”
+     *  2.  用HSSFWorkbook返回对象或者创建sheet对象
+     *  3.  用sheet返回行对象,用行对象得到Cell对象
+     *  4.  对Cell对象进行读写
+     * @param myFile
+     * @return
+     */
+    @Override
+    public int batchImportUsers(MultipartFile myFile) {
+        //Excel工作簿
+        Workbook workbook=null;
+        //获取文件名
+        String filename=myFile.getOriginalFilename();
+        log.info("【ExeclfileName】{}",filename);
+         //根据文件名判断文件是2003版本还是2007版本
+        if(ExcelImportUtils.isExcel2003(filename)){
+            try {
+                workbook=new HSSFWorkbook(myFile.getInputStream());//2003版本
+            }catch (IOException e){
+              log.error("获取Excel2003流错误"+e.getMessage());
+            }
+        }else  if(ExcelImportUtils.isExcel2007(filename)){
+            try {
+                workbook=new XSSFWorkbook(myFile.getInputStream());//2007以上版本
+            }catch (IOException e){
+                log.error("获取Excel2007以上版本流错误"+e.getMessage());
+            }
+        }else{
+
+            throw new UserException("1000",new Object[]{"文件不是Excel格式"});
+        }
+        //得到第一个sheet
+        Sheet sheet = workbook.getSheetAt(0);
+        //得到Excel的行数
+        int totalRows = sheet.getLastRowNum();
+        log.info("【rows】{}",totalRows);
+        //新建用户list
+        List<User> users=new ArrayList<User>();
+
+        List<Dept> depts;
+        List<Role> roles;
+        List<Post> posts;
+
+        //如果行数为空
+        /**
+         * getPhysicalNumberOfRows
+         *
+         *     获取有记录的行数,即:最后有数据的行是第n行,前面有m行是空行没数据,则返回n-m;
+         */
+        if((totalRows==0)&&(sheet.getPhysicalNumberOfRows()==0)){
+            throw new UserException("1001",new Object[]{"数据为空 请填写数据"});
+        }else{
+            //获取全部部门信息
+              depts=deptService.selectDeptAll();
+            //获取全部角色信息
+              roles=roleMapper.selectRolesAll();
+            //获取全部岗位信息
+              posts=postMapper.selectPostAll();
+        }
+
+       for(int i=1;i<=totalRows;i++){
+           Row row = sheet.getRow(i);
+           if(row!=null){
+               User user=new User();
+               //登录名(用户名)
+               String userName=ExcelImportUtils.getCellValue(row.getCell(0));
+               if(userName.isEmpty()){
+                   continue;
+               }else{
+                   //判断用户名是否唯一
+                 if(checkLoginNameUnique(userName).equals(UserConstants.USER_NAME_UNIQUE)){
+                     user.setLoginName(userName);
+                 }else {
+                     log.error("【rows】{}行用户名已经存在",i+1);
+                     continue;
+                 }
+               }
+               //姓名
+               String userRealName=ExcelImportUtils.getCellValue(row.getCell(1));
+               user.setUserName(userRealName);
+               //性别
+               String  userSex=ExcelImportUtils.getCellValue(row.getCell(2));
+               if(StringUtils.isNotEmpty(userSex)){
+                   if(userSex.equals("男")){
+                       user.setSex("0");
+                   }else if(userSex.equals("女")){
+                       user.setSex("1");
+                   }else {
+                       user.setSex("2");
+                   }
+               }
+               //密码
+               String passWord=ExcelImportUtils.getCellValue(row.getCell(3));
+               user.randomSalt();
+               user.setPassword(passwordService.encryptPassword(userName, passWord, user.getSalt()));
+               //部门
+               String dept=ExcelImportUtils.getCellValue(row.getCell(4));
+               if(StringUtils.isNotEmpty(dept)){
+                  for (int k=0;k<depts.size();k++){
+                       if(dept.equals(depts.get(k).getDeptName())){
+                           user.setDeptId(depts.get(k).getDeptId());
+                           break;
+                       }
+                  }
+               }
+               user.setCreateBy(ShiroUtils.getLoginName());
+               //角色--多个角色以","分割
+               String userRolesExcel=ExcelImportUtils.getCellValue(row.getCell(5));
+               if(StringUtils.isNotEmpty(userRolesExcel)){
+                   //Set可以去掉重复的值,
+                   Set<Long> sets=new HashSet<Long>();
+                   //判断是否有"," 号
+                   if(userRolesExcel.contains(",")){
+                       List<String> results= Arrays.asList(userRolesExcel.split(","));
+                       for(String s:results){
+                           for(int l=0;l<roles.size();l++){
+                               if(s.equals(roles.get(l).getRoleName())){
+                                   sets.add(roles.get(l).getRoleId());
+                                     break;
+                               }
+                           }
+                       }
+
+                   }else {
+                      for(int j=0;j<roles.size();j++){
+                          if(userRolesExcel.equals(roles.get(j).getRoleName())){
+                              sets.add(roles.get(j).getRoleId());
+                                break;
+                          }
+                      }
+
+                   }
+                   for(Long longTes:sets){
+                       log.info("username={},longTes={}",userName,longTes);
+                   }
+                   user.setRoleIds((Long[]) sets.toArray(new Long[sets.size()]));
+               }
+
+               //岗位--多个岗位以","分割
+               String userPostExcel=ExcelImportUtils.getCellValue(row.getCell(6));
+               if(StringUtils.isNotEmpty(userPostExcel)){
+                   //去掉重复的值,
+                   Set<Long> setPosts=new HashSet<Long>();
+                   //判断是否有"," 号
+                   if(userPostExcel.contains(",")){
+                       List<String> resultsp= Arrays.asList(userPostExcel.split(","));
+                       for(String p:resultsp){
+                           for(int h=0;h<posts.size();h++){
+                               if(p.equals(posts.get(h).getPostName())){
+                                   setPosts.add(posts.get(h).getPostId());
+                                   break;
+                               }
+                           }
+                       }
+
+                   }else {
+                       for(int m=0;m<posts.size();m++){
+                           if(userPostExcel.equals(posts.get(m).getPostName())){
+                               setPosts.add(posts.get(m).getPostId());
+                               break;
+                           }
+                       }
+
+                   }
+
+                   for(Long longTest:setPosts){
+                       log.info("username={},longTest={}",userName,longTest);
+                   }
+                   user.setPostIds((Long[]) setPosts.toArray(new Long[setPosts.size()]));
+               }
+
+               //手机号
+               String phoneNumber=ExcelImportUtils.getCellValue(row.getCell(7));
+               if(StringUtils.isNotEmpty(phoneNumber)){
+                   //验证是否是手机号
+                   if(phoneNumber.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN)){
+                       user.setPhonenumber(phoneNumber);
+                   }
+               }
+               //邮箱
+               String userEmail=ExcelImportUtils.getCellValue(row.getCell(8));
+               if(StringUtils.isNotEmpty(userEmail)){
+                   //验证是否是邮箱
+                   if(userEmail.matches(UserConstants.EMAIL_PATTERN)){
+                       user.setEmail(userEmail);
+                   }
+               }
+            users.add(user);
+           }
+       }
+       //实际添加行数
+       int realRow=0;
+        //如果添加的列表不为空
+       if(users.size()>0){
+           //批量插入用户
+            realRow=userMapper.batchAddUser(users);
+       }
+       System.out.println(realRow);
+        if(realRow>0){
+            //用户和角色关联
+            List<UserRole> userRoles=new ArrayList<UserRole>();
+            //用户和岗位关联
+            List<UserPost> userPosts=new ArrayList<UserPost>();
+          for(User test:users){
+              System.out.println("userID="+test.getUserId());
+              System.out.println("username="+test.getUserName());
+              System.out.println("useroleids"+Arrays.toString(test.getRoleIds()));
+              System.out.println("userpostids"+Arrays.toString(test.getPostIds()));
+
+              //添加用户-角色关联表
+              for(int q=0;q<test.getRoleIds().length;q++){
+                    UserRole userRole=new UserRole();
+                    userRole.setUserId(test.getUserId());
+                    userRole.setRoleId(test.getRoleIds()[q]);
+                    userRoles.add(userRole);
+
+              }
+
+              for(int r=0;r<test.getPostIds().length;r++){
+                  UserPost userPost=new UserPost();
+                  userPost.setUserId(test.getUserId());
+                  userPost.setPostId(test.getPostIds()[r]);
+                  userPosts.add(userPost);
+              }
+
+          }
+            //批量添加用户-角色关联数据
+            userRoleMapper.batchUserRole(userRoles);
+
+          //批量添加用户-岗位关联数据
+            userPostMapper.batchUserPost(userPosts);
+;
+        }
+
+        return  realRow;
+    }
+
+    /**
      * 修改用户信息
      * 
      * @param user 用户信息

+ 1 - 1
src/main/resources/application.yml

@@ -1,7 +1,7 @@
 # 项目名称、版本、版权年份
 ruoyi:
   name: RuoYi
-  version: 1.1.5
+  version: 1.1.6
   copyrightYear: 2018
   profile: D:/profile/
 

+ 33 - 0
src/main/resources/mybatis/system/UserMapper.xml

@@ -159,5 +159,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			sysdate()
  		)
 	</insert>
+
+	<insert id="batchAddUser" useGeneratedKeys="true" keyProperty="userId" >
+		insert into sys_user(
+		dept_id,
+	    login_name,
+	    user_name,
+		email,
+		phonenumber,
+		sex,
+		password,
+		salt,
+		status,
+		create_by,
+		remark,
+		create_time
+		)values
+		<foreach item="item" index="index" collection="list" separator=",">
+		(
+		#{item.deptId},
+		#{item.loginName},
+		#{item.userName},
+		#{item.email},
+		#{item.phonenumber},
+		#{item.sex},
+		#{item.password},
+		#{item.salt},
+	    #{item.status},
+		#{item.createBy},
+		#{item.remark},
+		sysdate()
+		)
+		</foreach>
+	</insert>
 	
 </mapper> 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 554 - 0
src/main/resources/static/css/plugins/fileinput/fileinput.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 12 - 0
src/main/resources/static/css/plugins/fileinput/fileinput.min.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4463 - 0
src/main/resources/static/js/plugins/fileinput/fileinput.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 12 - 0
src/main/resources/static/js/plugins/fileinput/fileinput.min.js


+ 100 - 0
src/main/resources/static/js/plugins/fileinput/locales/zh.js

@@ -0,0 +1,100 @@
+/*!
+ * FileInput Chinese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author kangqf <kangqingfei@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['zh'] = {
+        fileSingle: '文件',
+        filePlural: '个文件',
+        browseLabel: '选择 &hellip;',
+        removeLabel: '移除',
+        removeTitle: '清除选中文件',
+        cancelLabel: '取消',
+        cancelTitle: '取消进行中的上传',
+        uploadLabel: '上传',
+        uploadTitle: '上传选中文件',
+        msgNo: '没有',
+        msgNoFilesSelected: '未选择文件',
+        msgCancelled: '取消',
+        msgPlaceholder: '选择 {files}...',
+        msgZoomModalHeading: '详细预览',
+        msgFileRequired: '必须选择一个文件上传.',
+        msgSizeTooSmall: '文件 "{name}" (<b>{size} KB</b>) 必须大于限定大小 <b>{minSize} KB</b>.',
+        msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.',
+        msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. ',
+        msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.',
+        msgFileNotFound: '文件 "{name}" 未找到!',
+        msgFileSecured: '安全限制,为了防止读取文件 "{name}".',
+        msgFileNotReadable: '文件 "{name}" 不可读.',
+        msgFilePreviewAborted: '取消 "{name}" 的预览.',
+        msgFilePreviewError: '读取 "{name}" 时出现了一个错误.',
+        msgInvalidFileName: '文件名 "{name}" 包含非法字符.',
+        msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',
+        msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',
+        msgFileTypes: {
+            'image': 'image',
+            'html': 'HTML',
+            'text': 'text',
+            'video': 'video',
+            'audio': 'audio',
+            'flash': 'flash',
+            'pdf': 'PDF',
+            'object': 'object'
+        },
+        msgUploadAborted: '该文件上传被中止',
+        msgUploadThreshold: '处理中...',
+        msgUploadBegin: '正在初始化...',
+        msgUploadEnd: '完成',
+        msgUploadEmpty: '无效的文件上传.',
+        msgUploadError: '上传出错',
+        msgValidationError: '验证错误',
+        msgLoading: '加载第 {index} 文件 共 {files} &hellip;',
+        msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',
+        msgSelected: '{n} {files} 选中',
+        msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',
+        msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.',
+        msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.',
+        msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.',
+        msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.',
+        msgImageResizeError: '无法获取的图像尺寸调整。',
+        msgImageResizeException: '调整图像大小时发生错误。<pre>{errors}</pre>',
+        msgAjaxError: '{operation} 发生错误. 请重试!',
+        msgAjaxProgressError: '{operation} 失败',
+        ajaxOperations: {
+            deleteThumb: '删除文件',
+            uploadThumb: '上传文件',
+            uploadBatch: '批量上传',
+            uploadExtra: '表单数据上传'
+        },
+        dropZoneTitle: '拖拽文件到这里 &hellip;<br>支持多文件同时上传',
+        dropZoneClickTitle: '<br>(或点击{files}按钮选择文件)',
+        fileActionSettings: {
+            removeTitle: '删除文件',
+            uploadTitle: '上传文件',
+            uploadRetryTitle: '重试',
+            zoomTitle: '查看详情',
+            dragTitle: '移动 / 重置',
+            indicatorNewTitle: '没有上传',
+            indicatorSuccessTitle: '上传',
+            indicatorErrorTitle: '上传错误',
+            indicatorLoadingTitle: '上传 ...'
+        },
+        previewZoomButtonTitles: {
+            prev: '预览上一个文件',
+            next: '预览下一个文件',
+            toggleheader: '缩放',
+            fullscreen: '全屏',
+            borderless: '无边界模式',
+            close: '关闭当前预览'
+        }
+    };
+})(window.jQuery);

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2471 - 0
src/main/resources/static/js/plugins/fileinput/plugins/piexif.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/main/resources/static/js/plugins/fileinput/plugins/piexif.min.js


+ 812 - 0
src/main/resources/static/js/plugins/fileinput/plugins/purify.js

@@ -0,0 +1,812 @@
+;(function(factory) {
+    'use strict';
+    /* global window: false, define: false, module: false */
+    var root = typeof window === 'undefined' ? null : window;
+
+    if (typeof define === 'function' && define.amd) {
+        define(function(){ return factory(root); });
+    } else if (typeof module !== 'undefined') {
+        module.exports = factory(root);
+    } else {
+        root.DOMPurify = factory(root);
+    }
+}(function factory(window) {
+    'use strict';
+
+    var DOMPurify = function(window) {
+        return factory(window);
+    };
+
+    /**
+     * Version label, exposed for easier checks
+     * if DOMPurify is up to date or not
+     */
+    DOMPurify.version = '0.7.4';
+
+    if (!window || !window.document || window.document.nodeType !== 9) {
+        // not running in a browser, provide a factory function
+        // so that you can pass your own Window
+        DOMPurify.isSupported = false;
+        return DOMPurify;
+    }
+
+    var document = window.document;
+    var originalDocument = document;
+    var DocumentFragment = window.DocumentFragment;
+    var HTMLTemplateElement = window.HTMLTemplateElement;
+    var NodeFilter = window.NodeFilter;
+    var NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap;
+    var Text = window.Text;
+    var Comment = window.Comment;
+    var DOMParser = window.DOMParser;
+
+    // As per issue #47, the web-components registry is inherited by a
+    // new document created via createHTMLDocument. As per the spec
+    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
+    // a new empty registry is used when creating a template contents owner
+    // document, so we use that as our parent document to ensure nothing
+    // is inherited.
+    if (typeof HTMLTemplateElement === 'function') {
+        var template = document.createElement('template');
+        if (template.content && template.content.ownerDocument) {
+            document = template.content.ownerDocument;
+        }
+    }
+    var implementation = document.implementation;
+    var createNodeIterator = document.createNodeIterator;
+    var getElementsByTagName = document.getElementsByTagName;
+    var createDocumentFragment = document.createDocumentFragment;
+    var importNode = originalDocument.importNode;
+
+    var hooks = {};
+
+    /**
+     * Expose whether this browser supports running the full DOMPurify.
+     */
+    DOMPurify.isSupported =
+        typeof implementation.createHTMLDocument !== 'undefined' &&
+        document.documentMode !== 9;
+
+    /* Add properties to a lookup table */
+    var _addToSet = function(set, array) {
+        var l = array.length;
+        while (l--) {
+            if (typeof array[l] === 'string') {
+                array[l] = array[l].toLowerCase();
+            }
+            set[array[l]] = true;
+        }
+        return set;
+    };
+
+    /* Shallow clone an object */
+    var _cloneObj = function(object) {
+        var newObject = {};
+        var property;
+        for (property in object) {
+            if (object.hasOwnProperty(property)) {
+                newObject[property] = object[property];
+            }
+        }
+        return newObject;
+    };
+
+    /**
+     * We consider the elements and attributes below to be safe. Ideally
+     * don't add any new ones but feel free to remove unwanted ones.
+     */
+
+    /* allowed element names */
+    var ALLOWED_TAGS = null;
+    var DEFAULT_ALLOWED_TAGS = _addToSet({}, [
+
+        // HTML
+        'a','abbr','acronym','address','area','article','aside','audio','b',
+        'bdi','bdo','big','blink','blockquote','body','br','button','canvas',
+        'caption','center','cite','code','col','colgroup','content','data',
+        'datalist','dd','decorator','del','details','dfn','dir','div','dl','dt',
+        'element','em','fieldset','figcaption','figure','font','footer','form',
+        'h1','h2','h3','h4','h5','h6','head','header','hgroup','hr','html','i',
+        'img','input','ins','kbd','label','legend','li','main','map','mark',
+        'marquee','menu','menuitem','meter','nav','nobr','ol','optgroup',
+        'option','output','p','pre','progress','q','rp','rt','ruby','s','samp',
+        'section','select','shadow','small','source','spacer','span','strike',
+        'strong','style','sub','summary','sup','table','tbody','td','template',
+        'textarea','tfoot','th','thead','time','tr','track','tt','u','ul','var',
+        'video','wbr',
+
+        // SVG
+        'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
+        'animatemotion','animatetransform','circle','clippath','defs','desc',
+        'ellipse','filter','font','g','glyph','glyphref','hkern','image','line',
+        'lineargradient','marker','mask','metadata','mpath','path','pattern',
+        'polygon','polyline','radialgradient','rect','stop','switch','symbol',
+        'text','textpath','title','tref','tspan','view','vkern',
+
+        // SVG Filters
+        'feBlend','feColorMatrix','feComponentTransfer','feComposite',
+        'feConvolveMatrix','feDiffuseLighting','feDisplacementMap',
+        'feFlood','feFuncA','feFuncB','feFuncG','feFuncR','feGaussianBlur',
+        'feMerge','feMergeNode','feMorphology','feOffset',
+        'feSpecularLighting','feTile','feTurbulence',
+
+        //MathML
+        'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr',
+        'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow',
+        'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd',
+        'mtext','mtr','munder','munderover',
+
+        //Text
+        '#text'
+    ]);
+
+    /* Allowed attribute names */
+    var ALLOWED_ATTR = null;
+    var DEFAULT_ALLOWED_ATTR = _addToSet({}, [
+
+        // HTML
+        'accept','action','align','alt','autocomplete','background','bgcolor',
+        'border','cellpadding','cellspacing','checked','cite','class','clear','color',
+        'cols','colspan','coords','datetime','default','dir','disabled',
+        'download','enctype','face','for','headers','height','hidden','high','href',
+        'hreflang','id','ismap','label','lang','list','loop', 'low','max',
+        'maxlength','media','method','min','multiple','name','noshade','novalidate',
+        'nowrap','open','optimum','pattern','placeholder','poster','preload','pubdate',
+        'radiogroup','readonly','rel','required','rev','reversed','rows',
+        'rowspan','spellcheck','scope','selected','shape','size','span',
+        'srclang','start','src','step','style','summary','tabindex','title',
+        'type','usemap','valign','value','width','xmlns',
+
+        // SVG
+        'accent-height','accumulate','additivive','alignment-baseline',
+        'ascent','attributename','attributetype','azimuth','basefrequency',
+        'baseline-shift','begin','bias','by','clip','clip-path','clip-rule',
+        'color','color-interpolation','color-interpolation-filters','color-profile',
+        'color-rendering','cx','cy','d','dx','dy','diffuseconstant','direction',
+        'display','divisor','dur','edgemode','elevation','end','fill','fill-opacity',
+        'fill-rule','filter','flood-color','flood-opacity','font-family','font-size',
+        'font-size-adjust','font-stretch','font-style','font-variant','font-weight',
+        'fx', 'fy','g1','g2','glyph-name','glyphref','gradientunits','gradienttransform',
+        'image-rendering','in','in2','k','k1','k2','k3','k4','kerning','keypoints',
+        'keysplines','keytimes','lengthadjust','letter-spacing','kernelmatrix',
+        'kernelunitlength','lighting-color','local','marker-end','marker-mid',
+        'marker-start','markerheight','markerunits','markerwidth','maskcontentunits',
+        'maskunits','max','mask','mode','min','numoctaves','offset','operator',
+        'opacity','order','orient','orientation','origin','overflow','paint-order',
+        'path','pathlength','patterncontentunits','patterntransform','patternunits',
+        'points','preservealpha','r','rx','ry','radius','refx','refy','repeatcount',
+        'repeatdur','restart','result','rotate','scale','seed','shape-rendering',
+        'specularconstant','specularexponent','spreadmethod','stddeviation','stitchtiles',
+        'stop-color','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-linecap',
+        'stroke-linejoin','stroke-miterlimit','stroke-opacity','stroke','stroke-width',
+        'surfacescale','targetx','targety','transform','text-anchor','text-decoration',
+        'text-rendering','textlength','u1','u2','unicode','values','viewbox',
+        'visibility','vert-adv-y','vert-origin-x','vert-origin-y','word-spacing',
+        'wrap','writing-mode','xchannelselector','ychannelselector','x','x1','x2',
+        'y','y1','y2','z','zoomandpan',
+
+        // MathML
+        'accent','accentunder','bevelled','close','columnsalign','columnlines',
+        'columnspan','denomalign','depth','display','displaystyle','fence',
+        'frame','largeop','length','linethickness','lspace','lquote',
+        'mathbackground','mathcolor','mathsize','mathvariant','maxsize',
+        'minsize','movablelimits','notation','numalign','open','rowalign',
+        'rowlines','rowspacing','rowspan','rspace','rquote','scriptlevel',
+        'scriptminsize','scriptsizemultiplier','selection','separator',
+        'separators','stretchy','subscriptshift','supscriptshift','symmetric',
+        'voffset',
+
+        // XML
+        'xlink:href','xml:id','xlink:title','xml:space','xmlns:xlink'
+    ]);
+
+    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
+    var FORBID_TAGS = null;
+
+    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
+    var FORBID_ATTR = null;
+
+    /* Decide if custom data attributes are okay */
+    var ALLOW_DATA_ATTR = true;
+
+    /* Decide if unknown protocols are okay */
+    var ALLOW_UNKNOWN_PROTOCOLS = false;
+
+    /* Output should be safe for jQuery's $() factory? */
+    var SAFE_FOR_JQUERY = false;
+
+    /* Output should be safe for common template engines.
+     * This means, DOMPurify removes data attributes, mustaches and ERB
+     */
+    var SAFE_FOR_TEMPLATES = false;
+
+    /* Specify template detection regex for SAFE_FOR_TEMPLATES mode */
+    var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm;
+    var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm;
+
+    /* Decide if document with <html>... should be returned */
+    var WHOLE_DOCUMENT = false;
+
+    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string.
+     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
+     */
+    var RETURN_DOM = false;
+
+    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */
+    var RETURN_DOM_FRAGMENT = false;
+
+    /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
+     * `Node` is imported into the current `Document`. If this flag is not enabled the
+     * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
+     * DOMPurify. */
+    var RETURN_DOM_IMPORT = false;
+
+    /* Output should be free from DOM clobbering attacks? */
+    var SANITIZE_DOM = true;
+
+    /* Keep element content when removing element? */
+    var KEEP_CONTENT = true;
+
+    /* Tags to ignore content of when KEEP_CONTENT is true */
+    var FORBID_CONTENTS = _addToSet({}, [
+        'audio', 'head', 'math', 'script', 'style', 'svg', 'video'
+    ]);
+
+    /* Tags that are safe for data: URIs */
+    var DATA_URI_TAGS = _addToSet({}, [
+        'audio', 'video', 'img', 'source'
+    ]);
+
+    /* Attributes safe for values like "javascript:" */
+    var URI_SAFE_ATTRIBUTES = _addToSet({}, [
+        'alt','class','for','id','label','name','pattern','placeholder',
+        'summary','title','value','style','xmlns'
+    ]);
+
+    /* Keep a reference to config to pass to hooks */
+    var CONFIG = null;
+
+    /* Ideally, do not touch anything below this line */
+    /* ______________________________________________ */
+
+    var formElement = document.createElement('form');
+
+    /**
+     * _parseConfig
+     *
+     * @param  optional config literal
+     */
+    var _parseConfig = function(cfg) {
+        /* Shield configuration object from tampering */
+        if (typeof cfg !== 'object') {
+            cfg = {};
+        }
+
+        /* Set configuration parameters */
+        ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ?
+            _addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
+        ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ?
+            _addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
+        FORBID_TAGS = 'FORBID_TAGS' in cfg ?
+            _addToSet({}, cfg.FORBID_TAGS) : {};
+        FORBID_ATTR = 'FORBID_ATTR' in cfg ?
+            _addToSet({}, cfg.FORBID_ATTR) : {};
+        ALLOW_DATA_ATTR     = cfg.ALLOW_DATA_ATTR     !== false; // Default true
+        ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
+        SAFE_FOR_JQUERY     = cfg.SAFE_FOR_JQUERY     ||  false; // Default false
+        SAFE_FOR_TEMPLATES  = cfg.SAFE_FOR_TEMPLATES  ||  false; // Default false
+        WHOLE_DOCUMENT      = cfg.WHOLE_DOCUMENT      ||  false; // Default false
+        RETURN_DOM          = cfg.RETURN_DOM          ||  false; // Default false
+        RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT ||  false; // Default false
+        RETURN_DOM_IMPORT   = cfg.RETURN_DOM_IMPORT   ||  false; // Default false
+        SANITIZE_DOM        = cfg.SANITIZE_DOM        !== false; // Default true
+        KEEP_CONTENT        = cfg.KEEP_CONTENT        !== false; // Default true
+
+        if (SAFE_FOR_TEMPLATES) {
+            ALLOW_DATA_ATTR = false;
+        }
+
+        if (RETURN_DOM_FRAGMENT) {
+            RETURN_DOM = true;
+        }
+
+        /* Merge configuration parameters */
+        if (cfg.ADD_TAGS) {
+            if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
+                ALLOWED_TAGS = _cloneObj(ALLOWED_TAGS);
+            }
+            _addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
+        }
+        if (cfg.ADD_ATTR) {
+            if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
+                ALLOWED_ATTR = _cloneObj(ALLOWED_ATTR);
+            }
+            _addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
+        }
+
+        /* Add #text in case KEEP_CONTENT is set to true */
+        if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; }
+
+        // Prevent further manipulation of configuration.
+        // Not available in IE8, Safari 5, etc.
+        if (Object && 'freeze' in Object) { Object.freeze(cfg); }
+
+        CONFIG = cfg;
+    };
+
+   /**
+     * _forceRemove
+     *
+     * @param  a DOM node
+     */
+    var _forceRemove = function(node) {
+        try {
+            node.parentNode.removeChild(node);
+        } catch (e) {
+            node.outerHTML = '';
+        }
+    };
+
+   /**
+     * _initDocument
+     *
+     * @param  a string of dirty markup
+     * @return a DOM, filled with the dirty markup
+     */
+    var _initDocument = function(dirty) {
+        /* Create a HTML document using DOMParser */
+        var doc, body;
+        try {
+            doc = new DOMParser().parseFromString(dirty, 'text/html');
+        } catch (e) {}
+
+        /* Some browsers throw, some browsers return null for the code above
+           DOMParser with text/html support is only in very recent browsers. */
+        if (!doc) {
+            doc = implementation.createHTMLDocument('');
+            body = doc.body;
+            body.parentNode.removeChild(body.parentNode.firstElementChild);
+            body.outerHTML = dirty;
+        }
+
+        /* Work on whole document or just its body */
+        if (typeof doc.getElementsByTagName === 'function') {
+            return doc.getElementsByTagName(
+                WHOLE_DOCUMENT ? 'html' : 'body')[0];
+        }
+        return getElementsByTagName.call(doc,
+            WHOLE_DOCUMENT ? 'html' : 'body')[0];
+    };
+
+    /**
+     * _createIterator
+     *
+     * @param  document/fragment to create iterator for
+     * @return iterator instance
+     */
+    var _createIterator = function(root) {
+        return createNodeIterator.call(root.ownerDocument || root,
+            root,
+            NodeFilter.SHOW_ELEMENT
+            | NodeFilter.SHOW_COMMENT
+            | NodeFilter.SHOW_TEXT,
+            function() { return NodeFilter.FILTER_ACCEPT; },
+            false
+        );
+    };
+
+    /**
+     * _isClobbered
+     *
+     * @param  element to check for clobbering attacks
+     * @return true if clobbered, false if safe
+     */
+    var _isClobbered = function(elm) {
+        if (elm instanceof Text || elm instanceof Comment) {
+            return false;
+        }
+        if (  typeof elm.nodeName !== 'string'
+           || typeof elm.textContent !== 'string'
+           || typeof elm.removeChild !== 'function'
+           || !(elm.attributes instanceof NamedNodeMap)
+           || typeof elm.removeAttribute !== 'function'
+           || typeof elm.setAttribute !== 'function'
+        ) {
+            return true;
+        }
+        return false;
+    };
+
+    /**
+     * _sanitizeElements
+     *
+     * @protect nodeName
+     * @protect textContent
+     * @protect removeChild
+     *
+     * @param   node to check for permission to exist
+     * @return  true if node was killed, false if left alive
+     */
+    var _sanitizeElements = function(currentNode) {
+        var tagName, content;
+        /* Execute a hook if present */
+        _executeHook('beforeSanitizeElements', currentNode, null);
+
+        /* Check if element is clobbered or can clobber */
+        if (_isClobbered(currentNode)) {
+            _forceRemove(currentNode);
+            return true;
+        }
+
+        /* Now let's check the element's type and name */
+        tagName = currentNode.nodeName.toLowerCase();
+
+        /* Execute a hook if present */
+        _executeHook('uponSanitizeElement', currentNode, {
+            tagName: tagName
+        });
+
+        /* Remove element if anything forbids its presence */
+        if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
+            /* Keep content except for black-listed elements */
+            if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]
+                    && typeof currentNode.insertAdjacentHTML === 'function') {
+                try {
+                    currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
+                } catch (e) {}
+            }
+            _forceRemove(currentNode);
+            return true;
+        }
+
+        /* Convert markup to cover jQuery behavior */
+        if (SAFE_FOR_JQUERY && !currentNode.firstElementChild &&
+                (!currentNode.content || !currentNode.content.firstElementChild)) {
+            currentNode.innerHTML = currentNode.textContent.replace(/</g, '&lt;');
+        }
+
+        /* Sanitize element content to be template-safe */
+        if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
+            /* Get the element's text content */
+            content = currentNode.textContent;
+            content = content.replace(MUSTACHE_EXPR, ' ');
+            content = content.replace(ERB_EXPR, ' ');
+            currentNode.textContent = content;
+        }
+
+        /* Execute a hook if present */
+        _executeHook('afterSanitizeElements', currentNode, null);
+
+        return false;
+    };
+
+    var DATA_ATTR = /^data-[\w.\u00B7-\uFFFF-]/;
+    var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
+    var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i;
+    /* This needs to be extensive thanks to Webkit/Blink's behavior */
+    var ATTR_WHITESPACE = /[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g;
+
+    /**
+     * _sanitizeAttributes
+     *
+     * @protect attributes
+     * @protect nodeName
+     * @protect removeAttribute
+     * @protect setAttribute
+     *
+     * @param   node to sanitize
+     * @return  void
+     */
+    var _sanitizeAttributes = function(currentNode) {
+        var attr, name, value, lcName, idAttr, attributes, hookEvent, l;
+        /* Execute a hook if present */
+        _executeHook('beforeSanitizeAttributes', currentNode, null);
+
+        attributes = currentNode.attributes;
+
+        /* Check if we have attributes; if not we might have a text node */
+        if (!attributes) { return; }
+
+        hookEvent = {
+            attrName: '',
+            attrValue: '',
+            keepAttr: true
+        };
+        l = attributes.length;
+
+        /* Go backwards over all attributes; safely remove bad ones */
+        while (l--) {
+            attr = attributes[l];
+            name = attr.name;
+            value = attr.value;
+            lcName = name.toLowerCase();
+
+            /* Execute a hook if present */
+            hookEvent.attrName = lcName;
+            hookEvent.attrValue = value;
+            hookEvent.keepAttr = true;
+            _executeHook('uponSanitizeAttribute', currentNode, hookEvent );
+            value = hookEvent.attrValue;
+
+            /* Remove attribute */
+            // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
+            // remove a "name" attribute from an <img> tag that has an "id"
+            // attribute at the time.
+            if (lcName === 'name'  &&
+                    currentNode.nodeName === 'IMG' && attributes.id) {
+                idAttr = attributes.id;
+                attributes = Array.prototype.slice.apply(attributes);
+                currentNode.removeAttribute('id');
+                currentNode.removeAttribute(name);
+                if (attributes.indexOf(idAttr) > l) {
+                    currentNode.setAttribute('id', idAttr.value);
+                }
+            } else {
+                // This avoids a crash in Safari v9.0 with double-ids.
+                // The trick is to first set the id to be empty and then to
+                // remove the attriubute
+                if (name === 'id') {
+                    currentNode.setAttribute(name, '');
+                }
+                currentNode.removeAttribute(name);
+            }
+
+            /* Did the hooks approve of the attribute? */
+            if (!hookEvent.keepAttr) {
+                continue;
+            }
+
+            /* Make sure attribute cannot clobber */
+            if (SANITIZE_DOM &&
+                    (lcName === 'id' || lcName === 'name') &&
+                    (value in window || value in document || value in formElement)) {
+                continue;
+            }
+
+            /* Sanitize attribute content to be template-safe */
+            if (SAFE_FOR_TEMPLATES) {
+                value = value.replace(MUSTACHE_EXPR, ' ');
+                value = value.replace(ERB_EXPR, ' ');
+            }
+
+            if (
+                /* Check the name is permitted */
+                (ALLOWED_ATTR[lcName] && !FORBID_ATTR[lcName] && (
+                  /* Check no script, data or unknown possibly unsafe URI
+                     unless we know URI values are safe for that attribute */
+                  URI_SAFE_ATTRIBUTES[lcName] ||
+                  IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,'')) ||
+                  /* Keep image data URIs alive if src is allowed */
+                  (lcName === 'src' && value.indexOf('data:') === 0 &&
+                   DATA_URI_TAGS[currentNode.nodeName.toLowerCase()])
+                )) ||
+                /* Allow potentially valid data-* attributes:
+                 * At least one character after "-" (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
+                 * XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
+                 * We don't need to check the value; it's always URI safe.
+                 */
+                 (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName)) ||
+                 /* Allow unknown protocols:
+                  * This provides support for links that are handled by protocol handlers which may be unknown
+                  * ahead of time, e.g. fb:, spotify:
+                  */
+                 (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE,'')))
+            ) {
+                /* Handle invalid data-* attribute set by try-catching it */
+                try {
+                    currentNode.setAttribute(name, value);
+                } catch (e) {}
+            }
+        }
+
+        /* Execute a hook if present */
+        _executeHook('afterSanitizeAttributes', currentNode, null);
+    };
+
+    /**
+     * _sanitizeShadowDOM
+     *
+     * @param  fragment to iterate over recursively
+     * @return void
+     */
+    var _sanitizeShadowDOM = function(fragment) {
+        var shadowNode;
+        var shadowIterator = _createIterator(fragment);
+
+        /* Execute a hook if present */
+        _executeHook('beforeSanitizeShadowDOM', fragment, null);
+
+        while ( (shadowNode = shadowIterator.nextNode()) ) {
+            /* Execute a hook if present */
+            _executeHook('uponSanitizeShadowNode', shadowNode, null);
+
+            /* Sanitize tags and elements */
+            if (_sanitizeElements(shadowNode)) {
+                continue;
+            }
+
+            /* Deep shadow DOM detected */
+            if (shadowNode.content instanceof DocumentFragment) {
+                _sanitizeShadowDOM(shadowNode.content);
+            }
+
+            /* Check attributes, sanitize if necessary */
+            _sanitizeAttributes(shadowNode);
+        }
+
+        /* Execute a hook if present */
+        _executeHook('afterSanitizeShadowDOM', fragment, null);
+    };
+
+    /**
+     * _executeHook
+     * Execute user configurable hooks
+     *
+     * @param  {String} entryPoint  Name of the hook's entry point
+     * @param  {Node} currentNode
+     */
+    var _executeHook = function(entryPoint, currentNode, data) {
+        if (!hooks[entryPoint]) { return; }
+
+        hooks[entryPoint].forEach(function(hook) {
+            hook.call(DOMPurify, currentNode, data, CONFIG);
+        });
+    };
+
+    /**
+     * sanitize
+     * Public method providing core sanitation functionality
+     *
+     * @param {String} dirty string
+     * @param {Object} configuration object
+     */
+    DOMPurify.sanitize = function(dirty, cfg) {
+        var body, currentNode, oldNode, nodeIterator, returnNode;
+        /* Make sure we have a string to sanitize.
+           DO NOT return early, as this will return the wrong type if
+           the user has requested a DOM object rather than a string */
+        if (!dirty) {
+            dirty = '';
+        }
+
+        /* Stringify, in case dirty is an object */
+        if (typeof dirty !== 'string') {
+            if (typeof dirty.toString !== 'function') {
+                throw new TypeError('toString is not a function');
+            } else {
+                dirty = dirty.toString();
+            }
+        }
+
+        /* Check we can run. Otherwise fall back or ignore */
+        if (!DOMPurify.isSupported) {
+            if (typeof window.toStaticHTML === 'object'
+                || typeof window.toStaticHTML === 'function') {
+                return window.toStaticHTML(dirty);
+            }
+            return dirty;
+        }
+
+        /* Assign config vars */
+        _parseConfig(cfg);
+
+        /* Exit directly if we have nothing to do */
+        if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
+            return dirty;
+        }
+
+        /* Initialize the document to work on */
+        body = _initDocument(dirty);
+
+        /* Check we have a DOM node from the data */
+        if (!body) {
+            return RETURN_DOM ? null : '';
+        }
+
+        /* Get node iterator */
+        nodeIterator = _createIterator(body);
+
+        /* Now start iterating over the created document */
+        while ( (currentNode = nodeIterator.nextNode()) ) {
+
+            /* Fix IE's strange behavior with manipulated textNodes #89 */
+            if (currentNode.nodeType === 3 && currentNode === oldNode) {
+                continue;
+            }
+
+            /* Sanitize tags and elements */
+            if (_sanitizeElements(currentNode)) {
+                continue;
+            }
+
+            /* Shadow DOM detected, sanitize it */
+            if (currentNode.content instanceof DocumentFragment) {
+                _sanitizeShadowDOM(currentNode.content);
+            }
+
+            /* Check attributes, sanitize if necessary */
+            _sanitizeAttributes(currentNode);
+
+            oldNode = currentNode;
+        }
+
+        /* Return sanitized string or DOM */
+        if (RETURN_DOM) {
+
+            if (RETURN_DOM_FRAGMENT) {
+                returnNode = createDocumentFragment.call(body.ownerDocument);
+
+                while (body.firstChild) {
+                    returnNode.appendChild(body.firstChild);
+                }
+            } else {
+                returnNode = body;
+            }
+
+            if (RETURN_DOM_IMPORT) {
+                /* adoptNode() is not used because internal state is not reset
+                   (e.g. the past names map of a HTMLFormElement), this is safe
+                   in theory but we would rather not risk another attack vector.
+                   The state that is cloned by importNode() is explicitly defined
+                   by the specs. */
+                returnNode = importNode.call(originalDocument, returnNode, true);
+            }
+
+            return returnNode;
+        }
+
+        return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
+    };
+
+    /**
+     * addHook
+     * Public method to add DOMPurify hooks
+     *
+     * @param {String} entryPoint
+     * @param {Function} hookFunction
+     */
+    DOMPurify.addHook = function(entryPoint, hookFunction) {
+        if (typeof hookFunction !== 'function') { return; }
+        hooks[entryPoint] = hooks[entryPoint] || [];
+        hooks[entryPoint].push(hookFunction);
+    };
+
+    /**
+     * removeHook
+     * Public method to remove a DOMPurify hook at a given entryPoint
+     * (pops it from the stack of hooks if more are present)
+     *
+     * @param {String} entryPoint
+     * @return void
+     */
+    DOMPurify.removeHook = function(entryPoint) {
+        if (hooks[entryPoint]) {
+            hooks[entryPoint].pop();
+        }
+    };
+
+    /**
+     * removeHooks
+     * Public method to remove all DOMPurify hooks at a given entryPoint
+     *
+     * @param  {String} entryPoint
+     * @return void
+     */
+    DOMPurify.removeHooks = function(entryPoint) {
+        if (hooks[entryPoint]) {
+            hooks[entryPoint] = [];
+        }
+    };
+
+    /**
+     * removeAllHooks
+     * Public method to remove all DOMPurify hooks
+     *
+     * @return void
+     */
+    DOMPurify.removeAllHooks = function() {
+        hooks = [];
+    };
+
+    return DOMPurify;
+}));

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/main/resources/static/js/plugins/fileinput/plugins/purify.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1590 - 0
src/main/resources/static/js/plugins/fileinput/plugins/sortable.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/main/resources/static/js/plugins/fileinput/plugins/sortable.min.js


+ 58 - 1
src/main/resources/static/ruoyi/system/user/user.js

@@ -5,8 +5,11 @@ $(document).ready(function(){
 	$('body').layout({ west__size: 185 });
 	queryUserList();
 	queryDeptTreeDaTa();
+	//加载文件输入框
+    fileinputlist();
 });
 
+
 function queryUserList() {
 	var columns = [{
             checkbox: true
@@ -119,7 +122,25 @@ function queryDeptTreeDaTa()
 	    loadTree();
 	});
 }
-
+/** 初始化文件框*/
+function fileinputlist(){
+    $('#uploadfile').fileinput({
+        language: 'zh', //设置语言
+        showPreview: false,//是否显示预览,不写默认为true
+        showUpload: false, //是否显示上传按钮,跟随文本框的那个
+        showRemove : true, //显示移除按钮,跟随文本框的那个
+        showCaption: true,//是否显示标题,就是那个文本框
+        uploadAsync : false, //默认异步上传
+        elErrorContainer: '#upload-file-errors',
+        maxFileCount: 1, //表示允许同时上传的最大文件个数
+        maxFileSize: 102400,      //单位为kb,如果为0表示不限制文件大小
+        enctype: 'multipart/form-data',
+        dropZoneTitle: '拖拽文件到这里 &hellip;<br>仅仅支持单个文件同时上传',//重新定义提示
+        msgFilesTooMany: '选择上传的文件数量({n}) 超过允许的最大数值{m}!',
+        allowedFileExtensions: ["xls", "xlsx"],
+        uploadUrl: prefix+'/batchAdd'
+    });
+}
 /*用户管理-部门*/
 function dept() {
 	var url = ctx + "system/dept";
@@ -144,6 +165,42 @@ function add() {
     var url = prefix + '/add';
     layer_showAuto("新增用户", url);
 }
+/*用户管理-批量新增*/
+function batchAdd() {
+    //文件默认上传方法,传ID:uploadfile
+    $('#uploadfile').fileinput("upload");
+    //同步上传错误处理
+    $('#uploadfile').on('filebatchuploaderror', function(event, data, msg) {
+        // get message
+        $.modalAlert(msg, "error");
+        //重置
+        $('#uploadfile').fileinput("clear");
+        $('#uploadfile').fileinput("reset");
+        $('#uploadfile').fileinput('refresh');
+        $('#uploadfile').fileinput('enable');
+    });
+    //同步上传后从后台返回结果
+    $('#uploadfile').on('filebatchuploadsuccess', function(event, data, previewId, index) {
+        var result = data.response;
+        if (result.code == 0) {
+            //刷新数据表格
+            $('.bootstrap-table').bootstrapTable('refresh');
+            var count = result.msg;
+            $.modalAlert("成功导入"  +count+ "条数据","success");
+            $('#uploadfile').fileinput('reset');
+            $('#exampleModal').modal('hide');
+
+        } else {
+            $.modalAlert(result.msg, "error");
+            //重置
+            $('#uploadfile').fileinput("clear");
+            $('#uploadfile').fileinput("reset");
+            $('#uploadfile').fileinput('refresh');
+            $('#uploadfile').fileinput('enable');
+        }
+    });
+
+}
 
 /*用户管理-重置密码*/
 function resetPwd(userId) {

BIN
src/main/resources/static/template/用户导入模板.xlsx


+ 1 - 1
src/main/resources/templates/system/user/profile/avatar.html

@@ -39,7 +39,7 @@ $(window).load(function() {
             options.imgSrc = e.target.result;
             //根据MIME判断上传的文件是不是图片类型
             if((options.imgSrc).indexOf("image/")==-1){
-                alert("错误提示\n" + " 请上传图片类型!");
+                parent.layer.alert("文件格式错误,请上传图片类型,如:JPG,JEPG,PNG后缀的文件。", {icon: 2,title:"系统提示"});
             } else {
                 cropper = $('.imageBox').cropbox(options);
             }

+ 43 - 0
src/main/resources/templates/system/user/user.html

@@ -6,6 +6,9 @@
 <link href="/ruoyi/css/RuoYi.css" th:href="@{/ruoyi/css/RuoYi.css}" rel="stylesheet"/>
 <link href="/ajax/libs/jquery-layout/jquery.layout-latest.css" th:href="@{/ajax/libs/jquery-layout/jquery.layout-latest.css}" rel="stylesheet"/>
 <link href="/ajax/libs/jquery-ztree/3.5/css/metro/zTreeStyle.css" th:href="@{/ajax/libs/jquery-ztree/3.5/css/metro/zTreeStyle.css}" rel="stylesheet"/>
+<!--  文件输入框-->
+<link href="/css/plugins/fileinput/fileinput.min.css" th:href="@{/css/plugins/fileinput/fileinput.min.css}" rel="stylesheet"/>
+
 <body class="white-bg">
 	<div class="ui-layout-west">
 	<div class="main-content">
@@ -33,6 +36,10 @@
         	<button class="btn btn-outline btn-default" onclick="javascript:add()" shiro:hasPermission="system:user:add">
                 <i class="fa fa-plus"></i> 新增
             </button>
+			<button class="btn btn-outline btn-default" data-toggle="modal" data-target="#exampleModal" shiro:hasPermission="system:user:batchAdd">
+				<i class="fa fa-plus-square"></i> 批量新增
+			</button>
+
             <button class="btn btn-outline btn-default" onclick="javascript:batchRemove()" shiro:hasPermission="system:user:batchRemove">
                 <i class="fa fa-trash-o"></i> 删除
             </button>
@@ -43,10 +50,46 @@
 			   data-sort-name="create_time" data-sort-order="desc">
 		</table>
 	</div>
+	<!-- Modal -->
+
+	<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
+		<div class="modal-dialog modal-lg" role="document">
+			<div class="modal-content">
+				<div class="modal-header">
+					<h3 class="modal-title" id="exampleModalLabel">导入Execl表</h3>
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+						<span aria-hidden="true">&times;</span>
+					</button>
+					<a  class="btn" style="font-size:15px" href="../static/template/用户导入模板.xlsx" th:href="@{/template/用户导入模板.xlsx}">
+						用户导入模板.xlsx
+					</a>
+				</div>
+					<div class="modal-body">
+						<div class="file-loading">
+							<input id="uploadfile" name="uploadfile" multiple type="file" accept=".xls,.xlsx">
+						</div>
+						<div id="upload-file-errors"></div>
+						<div class="modal-footer">
+							<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+							<button type="button" class="btn btn-primary" onclick="javascript:batchAdd()">保存</button>
+						</div>
+					</div>
+			</div>
+		</div>
+	</div>
+
 	<div th:include="include :: footer"></div>
 	<script src="/ruoyi/system/user/user.js" th:src="@{/ruoyi/system/user/user.js}"></script>
 	<script src="/ajax/libs/jquery-layout/jquery.layout-latest.js" th:src="@{/ajax/libs/jquery-layout/jquery.layout-latest.js}"></script>
 	<script src="/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js" th:src="@{/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js}"></script>
+
+	<!--  文件输入框-->
+	<script src="/js/plugins/fileinput/fileinput.min.js" th:src="@{/js/plugins/fileinput/fileinput.min.js}"></script>
+	<script src="/js/plugins/fileinput/locales/zh.js" th:src="@{/js/plugins/fileinput/locales/zh.js}"></script>
+	<script src="/js/plugins/fileinput/plugins/piexif.js" th:src="@{/js/plugins/fileinput/plugins/piexif.js}"></script>
+	<script src="/js/plugins/fileinput/plugins/purify.js" th:src="@{/js/plugins/fileinput/plugins/purify.js}"></script>
+	<script src="/js/plugins/fileinput/plugins/sortable.js" th:src="@{/js/plugins/fileinput/plugins/sortable.js}"></script>
+
 	<script th:inline="javascript">
 		var editFlag = [[${@permissionService.hasPermi('system:user:edit')}]];
 		var removeFlag = [[${@permissionService.hasPermi('system:user:remove')}]];