drew il y a 5 ans
Parent
commit
e8430a94f8

+ 434 - 0
awesome_admin_v2.sql

@@ -0,0 +1,434 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server         : 微球
+ Source Server Type    : MySQL
+ Source Server Version : 50616
+ Source Host           : rdsave1o67m1ido6gwp6public.mysql.rds.aliyuncs.com:3306
+ Source Schema         : awesome_admin_v2
+
+ Target Server Type    : MySQL
+ Target Server Version : 50616
+ File Encoding         : 65001
+
+ Date: 09/07/2020 15:07:37
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for authority
+-- ----------------------------
+DROP TABLE IF EXISTS `authority`;
+CREATE TABLE `authority` (
+  `name` varchar(50) NOT NULL,
+  `description` varchar(50) NOT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of authority
+-- ----------------------------
+BEGIN;
+INSERT INTO `authority` VALUES ('ROLE_ADMIN', '管理员');
+INSERT INTO `authority` VALUES ('ROLE_DEV', '开发者');
+INSERT INTO `authority` VALUES ('ROLE_USER', '普通用户');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for child
+-- ----------------------------
+DROP TABLE IF EXISTS `child`;
+CREATE TABLE `child` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `child_name` varchar(255) DEFAULT NULL,
+  `parent_id` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK7dag1cncltpyhoc2mbwka356h` (`parent_id`),
+  CONSTRAINT `FK7dag1cncltpyhoc2mbwka356h` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of child
+-- ----------------------------
+BEGIN;
+INSERT INTO `child` VALUES (3, '2019-12-25 11:17:02', 'system', '2019-12-25 11:17:02', 'system', '222', 2);
+INSERT INTO `child` VALUES (5, '2019-12-25 11:18:19', 'system', '2019-12-25 11:18:19', 'system', '222', 4);
+INSERT INTO `child` VALUES (7, '2019-12-25 14:50:50', 'system', '2019-12-25 14:50:50', 'system', '222', 6);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for city
+-- ----------------------------
+DROP TABLE IF EXISTS `city`;
+CREATE TABLE `city` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for district
+-- ----------------------------
+DROP TABLE IF EXISTS `district`;
+CREATE TABLE `district` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `full_name` varchar(255) DEFAULT NULL,
+  `lat` double NOT NULL,
+  `leaf` bit(1) NOT NULL,
+  `level` int(11) NOT NULL,
+  `lng` double NOT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `parent` bigint(20) DEFAULT NULL,
+  `pinyin` varchar(255) DEFAULT NULL,
+  `child_count` int(11) NOT NULL,
+  `city_code` varchar(10) DEFAULT NULL,
+  `city_count` int(11) NOT NULL,
+  `district_count` int(11) NOT NULL,
+  `street_count` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for guid_package
+-- ----------------------------
+DROP TABLE IF EXISTS `guid_package`;
+CREATE TABLE `guid_package` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `price` decimal(10,2) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for guide
+-- ----------------------------
+DROP TABLE IF EXISTS `guide`;
+CREATE TABLE `guide` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `pic` varchar(255) DEFAULT NULL,
+  `sort` int(11) NOT NULL,
+  `text` text,
+  `type` varchar(255) DEFAULT NULL,
+  `video` varchar(255) DEFAULT NULL,
+  `voice` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for hibernate_sequence
+-- ----------------------------
+DROP TABLE IF EXISTS `hibernate_sequence`;
+CREATE TABLE `hibernate_sequence` (
+  `next_val` bigint(20) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of hibernate_sequence
+-- ----------------------------
+BEGIN;
+INSERT INTO `hibernate_sequence` VALUES (9);
+INSERT INTO `hibernate_sequence` VALUES (9);
+INSERT INTO `hibernate_sequence` VALUES (9);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for menu
+-- ----------------------------
+DROP TABLE IF EXISTS `menu`;
+CREATE TABLE `menu` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `active` bit(1) DEFAULT NULL,
+  `enabled` bit(1) DEFAULT NULL,
+  `icon` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `parent` bigint(20) DEFAULT NULL,
+  `path` varchar(255) DEFAULT NULL,
+  `root` bit(1) DEFAULT NULL,
+  `sort` int(11) DEFAULT NULL,
+  `category` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of menu
+-- ----------------------------
+BEGIN;
+INSERT INTO `menu` VALUES (3, NULL, '', NULL, '', b'1', b'1', 'fas fa-desktop', '开发', 2, '', b'1', 4, '开发');
+INSERT INTO `menu` VALUES (4, NULL, '', NULL, '', b'1', b'1', 'fas fa-code', '代码生成', 3, '/genCodeList', b'0', 5, '开发');
+INSERT INTO `menu` VALUES (5, NULL, '', NULL, '', b'1', b'1', 'fas fa-bug', '接口调试', 3, '/api', b'0', 6, '开发');
+INSERT INTO `menu` VALUES (6, NULL, NULL, '2020-07-09 15:01:05', '管理员(1)', b'1', b'1', 'fas fa-user', '用户管理', 2, '/userList', b'1', 2, '用户');
+INSERT INTO `menu` VALUES (8, '2020-07-09 15:01:40', NULL, '2020-07-09 15:01:45', '管理员(1)', b'1', b'1', '', '菜单权限', 9, '/menuAuthority', b'0', 8, NULL);
+INSERT INTO `menu` VALUES (9, NULL, NULL, '2020-07-09 15:01:10', '管理员(1)', b'1', b'1', 'fas fa-cog', '配置', 2, '', b'1', 3, '系统');
+INSERT INTO `menu` VALUES (10, NULL, '', NULL, '', b'1', b'1', '', '菜单配置', 9, '/menus', b'0', 7, '系统');
+INSERT INTO `menu` VALUES (14, NULL, NULL, '2020-07-09 15:01:45', '管理员(1)', b'1', b'1', '', '参数配置', 9, '/sysConfigList', b'0', 9, '系统');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for menu_authority
+-- ----------------------------
+DROP TABLE IF EXISTS `menu_authority`;
+CREATE TABLE `menu_authority` (
+  `menu_id` bigint(20) NOT NULL,
+  `authority` varchar(50) NOT NULL,
+  PRIMARY KEY (`menu_id`,`authority`),
+  KEY `FKlj7sftrck7uk1kcjsy0doo56` (`authority`),
+  CONSTRAINT `FK4hopjqfvkhdagmk110y1jk17q` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`),
+  CONSTRAINT `FKlj7sftrck7uk1kcjsy0doo56` FOREIGN KEY (`authority`) REFERENCES `authority` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of menu_authority
+-- ----------------------------
+BEGIN;
+INSERT INTO `menu_authority` VALUES (6, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (8, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (9, 'ROLE_ADMIN');
+INSERT INTO `menu_authority` VALUES (14, 'ROLE_ADMIN');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for parent
+-- ----------------------------
+DROP TABLE IF EXISTS `parent`;
+CREATE TABLE `parent` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `parent_name` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of parent
+-- ----------------------------
+BEGIN;
+INSERT INTO `parent` VALUES (2, '2019-12-25 11:17:02', 'system', '2019-12-25 11:17:02', 'system', '111');
+INSERT INTO `parent` VALUES (4, '2019-12-25 11:18:19', 'system', '2019-12-25 11:18:19', 'system', '111');
+INSERT INTO `parent` VALUES (6, '2019-12-25 14:50:49', 'system', '2019-12-25 14:50:49', 'system', '111');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for scenic
+-- ----------------------------
+DROP TABLE IF EXISTS `scenic`;
+CREATE TABLE `scenic` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `city_id` bigint(20) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `pic` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for scenic_spot
+-- ----------------------------
+DROP TABLE IF EXISTS `scenic_spot`;
+CREATE TABLE `scenic_spot` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `city_id` bigint(20) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  `pic` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  `scenic_id` bigint(20) DEFAULT NULL,
+  `x` int(11) NOT NULL,
+  `y` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for sms_record
+-- ----------------------------
+DROP TABLE IF EXISTS `sms_record`;
+CREATE TABLE `sms_record` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `code` varchar(255) DEFAULT NULL,
+  `expired` bit(1) DEFAULT NULL,
+  `expires_at` datetime DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `scope` varchar(255) DEFAULT NULL,
+  `session_id` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for spot
+-- ----------------------------
+DROP TABLE IF EXISTS `spot`;
+CREATE TABLE `spot` (
+  `user_id` bigint(20) NOT NULL,
+  `authority_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`user_id`,`authority_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for spot_guid
+-- ----------------------------
+DROP TABLE IF EXISTS `spot_guid`;
+CREATE TABLE `spot_guid` (
+  `spot_id` bigint(20) NOT NULL,
+  `guid_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`spot_id`,`guid_id`),
+  KEY `FKbftxye4brw7vfq05d7rv0mtca` (`guid_id`),
+  CONSTRAINT `FK5xnrr1sk76u0xy3j15p6r96q9` FOREIGN KEY (`spot_id`) REFERENCES `scenic_spot` (`id`),
+  CONSTRAINT `FKbftxye4brw7vfq05d7rv0mtca` FOREIGN KEY (`guid_id`) REFERENCES `guide` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for super_user
+-- ----------------------------
+DROP TABLE IF EXISTS `super_user`;
+CREATE TABLE `super_user` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `avatar` varchar(255) DEFAULT NULL,
+  `enabled` bit(1) NOT NULL,
+  `nickname` varchar(255) DEFAULT NULL,
+  `password` varchar(255) DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `username` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UK_jfokkpxg19r117eil158ooo9d` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for super_user_authority
+-- ----------------------------
+DROP TABLE IF EXISTS `super_user_authority`;
+CREATE TABLE `super_user_authority` (
+  `user_id` bigint(20) NOT NULL,
+  `authority_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`user_id`,`authority_name`),
+  KEY `FKxjxyodse6n0n00ewvoq8xv8` (`authority_name`),
+  CONSTRAINT `FK7p0vq0b1vy9f0sqvgrdqw1q70` FOREIGN KEY (`user_id`) REFERENCES `super_user` (`id`),
+  CONSTRAINT `FKxjxyodse6n0n00ewvoq8xv8` FOREIGN KEY (`authority_name`) REFERENCES `authority` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for sys_config
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_config`;
+CREATE TABLE `sys_config` (
+  `name` varchar(25) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `description` varchar(255) DEFAULT NULL,
+  `type` varchar(255) DEFAULT NULL,
+  `value` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for test_model
+-- ----------------------------
+DROP TABLE IF EXISTS `test_model`;
+CREATE TABLE `test_model` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `level` varchar(255) DEFAULT NULL,
+  `name` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for user
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+  `id` bigint(20) NOT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `created_by` varchar(255) DEFAULT NULL,
+  `modified_at` datetime DEFAULT NULL,
+  `modified_by` varchar(255) DEFAULT NULL,
+  `avatar` varchar(255) DEFAULT NULL,
+  `city` varchar(255) DEFAULT NULL,
+  `country` varchar(255) DEFAULT NULL,
+  `email` varchar(255) DEFAULT NULL,
+  `enabled` bit(1) NOT NULL,
+  `language` varchar(255) DEFAULT NULL,
+  `nickname` varchar(255) DEFAULT NULL,
+  `open_id` varchar(255) DEFAULT NULL,
+  `password` varchar(255) DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `province` varchar(255) DEFAULT NULL,
+  `sex` varchar(255) DEFAULT NULL,
+  `username` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of user
+-- ----------------------------
+BEGIN;
+INSERT INTO `user` VALUES (1, '2020-03-07 18:34:36', 'system', '2020-03-07 18:34:36', 'system', 'https://zhumj.oss-cn-hangzhou.aliyuncs.com/image/user.jpg', NULL, NULL, NULL, b'1', NULL, '管理员', NULL, '$2a$10$F1djCtmdF3T0qsviWw50/utajZSe4EJMMUD.Ey5BhEIpoDhanBMuC', NULL, NULL, NULL, 'root');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for user_authority
+-- ----------------------------
+DROP TABLE IF EXISTS `user_authority`;
+CREATE TABLE `user_authority` (
+  `user_id` bigint(20) NOT NULL,
+  `authority_name` varchar(50) NOT NULL,
+  PRIMARY KEY (`user_id`,`authority_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of user_authority
+-- ----------------------------
+BEGIN;
+INSERT INTO `user_authority` VALUES (1, 'ROLE_ADMIN');
+INSERT INTO `user_authority` VALUES (1, 'ROLE_USER');
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 0 - 18
db.sql

@@ -1,18 +0,0 @@
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (2, NULL, '', NULL, '', b'1', b'1', '', '系统菜单', NULL, '', b'1', 1);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (6, NULL, '', NULL, '', b'1', b'1', 'fas fa-user', '用户管理', 2, '/userList', b'0', 2);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (4, NULL, '', NULL, '', b'1', b'1', 'fas fa-code', '代码生成', 3, '/genCodeList', b'0', 5);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (5, NULL, '', NULL, '', b'1', b'1', 'fas fa-bug', '接口调试', 3, '/api', b'0', 6);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (10, NULL, '', NULL, '', b'1', b'1', '', '菜单配置', 9, '/menus', b'0', 7);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (14, NULL, '', NULL, '', b'1', b'1', '', '参数配置', 9, '/sysConfigList', b'0', 8);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (3, NULL, '', NULL, '', b'1', b'1', 'fas fa-desktop', '开发', 2, '', b'0', 4);
-INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (9, NULL, '', NULL, '', b'1', b'1', 'fas fa-cog', '配置', 2, '', b'0', 3);
-
-INSERT INTO authority (name) VALUES ('ROLE_ADMIN');
-INSERT INTO authority (name) VALUES ('ROLE_USER');
-
-INSERT INTO `user_authority`(`user_id`, `authority_name`) VALUES (1, 'ROLE_ADMIN');
-INSERT INTO `user_authority`(`user_id`, `authority_name`) VALUES (1, 'ROLE_USER');
-
-INSERT INTO `user`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `avatar`, `city`, `country`, `email`, `enabled`, `language`, `nickname`, `open_id`, `password`, `phone`, `province`, `sex`, `username`) VALUES (1, '2020-03-07 18:34:36', 'system', '2020-03-07 18:34:36', 'system', 'https://zhumj.oss-cn-hangzhou.aliyuncs.com/image/user.jpg', NULL, NULL, NULL, b'1', NULL, '管理员', NULL, '$2a$10$F1djCtmdF3T0qsviWw50/utajZSe4EJMMUD.Ey5BhEIpoDhanBMuC', NULL, NULL, NULL, 'root');
-
-update hibernate_sequence set next_val = 1000

+ 36 - 0
src/main/java/com/izouma/awesomeAdmin/domain/Menu.java

@@ -1,14 +1,25 @@
 package com.izouma.awesomeAdmin.domain;
 
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.izouma.awesomeAdmin.dto.MenuDTO;
+import com.izouma.awesomeAdmin.security.Authority;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.hibernate.annotations.Where;
 
 import javax.persistence.*;
 import java.io.Serializable;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 @Data
 @Entity
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
 @Where(clause = "active = 1")
 public class Menu extends BaseEntity implements Serializable {
     private String name;
@@ -32,4 +43,29 @@ public class Menu extends BaseEntity implements Serializable {
     @OneToMany
     @JoinColumn(name = "parent", insertable = false, updatable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
     List<Menu> children;
+
+    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH})
+    @JoinTable(
+            name = "menu_authority",
+            joinColumns = {@JoinColumn(name = "menu_id", referencedColumnName = "id")},
+            inverseJoinColumns = {@JoinColumn(name = "authority", referencedColumnName = "name")})
+    @ExcelIgnore
+    private Set<Authority> authorities = new HashSet<>();
+
+    public static Menu from(MenuDTO menuDTO) {
+        Menu menu = Menu.builder()
+                .name(menuDTO.getName())
+                .path(menuDTO.getPath())
+                .icon(menuDTO.getIcon())
+                .sort(menuDTO.getSort())
+                .parent(menuDTO.getParent())
+                .root(menuDTO.getRoot())
+                .enabled(menuDTO.getEnabled())
+                .active(menuDTO.getActive())
+                .category(menuDTO.getCategory())
+                .children(null)
+                .build();
+        menu.setId(menuDTO.getId());
+        return menu;
+    }
 }

+ 23 - 0
src/main/java/com/izouma/awesomeAdmin/dto/MenuDTO.java

@@ -0,0 +1,23 @@
+package com.izouma.awesomeAdmin.dto;
+
+public interface MenuDTO {
+    Long getId();
+
+    String getName();
+
+    String getPath();
+
+    String getIcon();
+
+    Integer getSort();
+
+    Long getParent();
+
+    Boolean getRoot();
+
+    Boolean getEnabled();
+
+    Boolean getActive();
+
+    String getCategory();
+}

+ 24 - 6
src/main/java/com/izouma/awesomeAdmin/repo/MenuRepo.java

@@ -1,18 +1,36 @@
 package com.izouma.awesomeAdmin.repo;
 
 import com.izouma.awesomeAdmin.domain.Menu;
+import com.izouma.awesomeAdmin.dto.MenuDTO;
 import org.hibernate.annotations.Where;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 
+import javax.transaction.Transactional;
 import java.util.List;
 
-public interface MenuRepo extends JpaRepository<Menu, Long> {
-    List<Menu> findByRootTrue();
-
-    @Where(clause = "enabled = 1")
-    List<Menu> findByNameAndRootTrue(String name);
-
+public interface MenuRepo extends JpaRepository<Menu, Long>, JpaSpecificationExecutor<Menu> {
     @Query(nativeQuery = true, value = "SELECT ifnull(max(sort + 1),1) FROM menu")
     int nextSort();
+
+    @Query("select m.category from Menu m group by m.category")
+    List<String> categories();
+
+    @Transactional
+    @Modifying
+    @Query(value = "delete from menu_authority where authority = ?1", nativeQuery = true)
+    int clearAuthority(String name);
+
+    @Transactional
+    @Modifying
+    @Query(value = "insert into menu_authority value (?2, ?1)", nativeQuery = true)
+    int saveAuthority(String name, Long id);
+
+    @Query(value = "select * " +
+            "from menu " +
+            "    join menu_authority on menu.id = menu_authority.menu_id " +
+            "where authority in ?1 and active = 1 group by id", nativeQuery = true)
+    List<MenuDTO> authorityMenus(Iterable<String> authority);
 }

+ 78 - 17
src/main/java/com/izouma/awesomeAdmin/web/MenuController.java

@@ -1,14 +1,21 @@
 package com.izouma.awesomeAdmin.web;
 
+import com.izouma.awesomeAdmin.domain.BaseEntity;
 import com.izouma.awesomeAdmin.domain.Menu;
+import com.izouma.awesomeAdmin.enums.AuthorityName;
 import com.izouma.awesomeAdmin.repo.MenuRepo;
+import com.izouma.awesomeAdmin.security.Authority;
 import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
 
+import javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -18,26 +25,72 @@ import java.util.stream.Collectors;
 @RequestMapping("/menu")
 @AllArgsConstructor
 public class MenuController extends BaseController {
-    private MenuRepo menuRepo;
+    private final MenuRepo menuRepo;
 
     @GetMapping("/userMenu")
-    public List<Menu> userMenu(String name) {
-        List<Menu> menuList = menuRepo.findByNameAndRootTrue(name);
-        menuList.forEach(this::sortMenu);
-        return menuList;
+    public List<Menu> userMenu() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        List<Menu> menuList = menuRepo.authorityMenus(authentication.getAuthorities()
+                .stream()
+                .map(GrantedAuthority::getAuthority)
+                .collect(Collectors.toList()))
+                .stream()
+                .map(Menu::from)
+                .collect(Collectors.toList());
+
+        List<Menu> root = new ArrayList<>();
+
+        for (Menu menu : menuList) {
+            if (menu.getRoot()) {
+                root.add(menu);
+            } else {
+                Menu parent = menuList.stream().filter(m -> m.getId().equals(menu.getParent())).findAny().orElse(null);
+                if (parent != null) {
+                    if (parent.getChildren() == null) {
+                        parent.setChildren(new ArrayList<>());
+                    }
+                    parent.getChildren().add(menu);
+                }
+            }
+        }
+
+        sortMenu(root);
+        return root;
+    }
+
+    @GetMapping("/authority/{name}/get")
+    public List<Long> getAuthorityMenus(@PathVariable String name) {
+        return menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(criteriaBuilder.isMember(Authority.get(AuthorityName.valueOf(name)), root.get("authorities")));
+            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
+        }).stream().map(BaseEntity::getId).collect(Collectors.toList());
+    }
+
+    @PostMapping("/authority/{name}/save")
+    public void saveAuthorityMenus(@PathVariable String name, @RequestBody List<Long> ids) {
+        menuRepo.clearAuthority(name);
+        ids.stream().parallel().forEach(id -> menuRepo.saveAuthority(name, id));
     }
 
     @PreAuthorize("hasRole('ADMIN')")
     @GetMapping("/all")
-    public List<Menu> all() {
-        List<Menu> menuList = menuRepo.findByRootTrue();
-        menuList.forEach(this::sortMenu);
+    public List<Menu> all(String category) {
+        List<Menu> menuList = menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(criteriaBuilder.equal(root.get("root"), true));
+            if (StringUtils.isNotBlank(category)) {
+                predicates.add(criteriaBuilder.equal(root.get("category"), category));
+            }
+            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
+        });
+        sortMenu(menuList);
         return menuList;
     }
 
     @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
-    public Menu save(Menu menu) {
+    public Menu save(@RequestBody Menu menu) {
         if (menu.getSort() == null) {
             menu.setSort(menuRepo.nextSort());
         }
@@ -53,10 +106,18 @@ public class MenuController extends BaseController {
                 .collect(Collectors.groupingBy(Menu::getCategory));
     }
 
-    private void sortMenu(Menu menu) {
-        if (menu.getChildren() != null) {
-            menu.getChildren().sort(Comparator.comparingInt(Menu::getSort));
-            menu.getChildren().forEach(this::sortMenu);
+    @PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/categories")
+    public List<String> categories() {
+        return menuRepo.categories().stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
+    }
+
+    private void sortMenu(List<Menu> menus) {
+        menus.sort(Comparator.comparingInt(Menu::getSort));
+        for (Menu menu : menus) {
+            if (menu.getChildren() != null) {
+                sortMenu(menu.getChildren());
+            }
         }
     }
 }

+ 68 - 68
src/main/vue/src/components/DistrictChoose.vue

@@ -11,76 +11,76 @@
     </div>
 </template>
 <script>
-    export default {
-        props: {
-            value: {
-                type: Array
-            },
-            checkStrictly: {
-                type: Boolean,
-                default: false
-            },
-            maxLevel: {}
+export default {
+    props: {
+        value: {
+            type: Array
         },
-        data() {
-            return {
-                show: true,
-                chooseValue: [],
-                props: {
-                    lazy: true,
-                    lazyLoad: (node, resolve) => {
-                        const { level } = node;
-                        let params = { size: 10000, query: { level: level } };
-                        if (level === 0) {
-                            params.level = 'PROVINCE';
-                        } else {
-                            params.parent = node.data.id;
-                        }
-                        if (this.maxLevel) {
-                            params.maxLevel = this.maxLevel.toUpperCase();
+        checkStrictly: {
+            type: Boolean,
+            default: false
+        },
+        maxLevel: {}
+    },
+    data() {
+        return {
+            show: true,
+            chooseValue: [],
+            props: {
+                lazy: true,
+                lazyLoad: (node, resolve) => {
+                    const { level } = node;
+                    let params = { size: 10000, query: { level: level } };
+                    if (level === 0) {
+                        params.level = 'PROVINCE';
+                    } else {
+                        params.parent = node.data.id;
+                    }
+                    if (this.maxLevel) {
+                        params.maxLevel = this.maxLevel.toUpperCase();
+                    }
+                    this.$http.get('/district', params).then(res => {
+                        if (res.length === 0) {
+                            this.$set(node, 'isLeaf', true);
+                            node.hasChildren = false;
+                            resolve(null);
+                            return;
                         }
-                        this.$http.get('/district', params).then(res => {
-                            if (res.length === 0) {
-                                this.$set(node, 'isLeaf', true);
-                                node.hasChildren = false;
-                                resolve(null);
-                                return;
-                            }
-                            resolve(res);
-                        });
-                    },
-                    value: 'id',
-                    label: 'name',
-                    leaf: 'leaf',
-                    checkStrictly: this.checkStrictly
+                        resolve(res);
+                    });
                 },
-                emiting: false
-            };
-        },
-        created() {
-            if (this.value) {
-                this.chooseValue = this.value;
-            }
-        },
-        methods: {
-            onChange(e) {
-                this.emiting = true;
-                this.$emit('input', [...e]);
-                this.$emit('select', this.$refs.cascader.getCheckedNodes());
-                this.$nextTick(() => {
-                    this.emiting = false;
-                });
-            }
-        },
-        watch: {
-            value(val) {
-                if (this.emiting) return;
-                this.show = false;
-                this.$nextTick(() => {
-                    this.chooseValue = val;
-                    this.show = true;
-                });
-            }
+                value: 'id',
+                label: 'name',
+                leaf: 'leaf',
+                checkStrictly: this.checkStrictly
+            },
+            emiting: false
+        };
+    },
+    created() {
+        if (this.value) {
+            this.chooseValue = this.value;
+        }
+    },
+    methods: {
+        onChange(e) {
+            this.emiting = true;
+            this.$emit('input', [...e]);
+            this.$emit('select', this.$refs.cascader.getCheckedNodes());
+            this.$nextTick(() => {
+                this.emiting = false;
+            });
+        }
+    },
+    watch: {
+        value(val) {
+            if (this.emiting) return;
+            this.show = false;
+            this.$nextTick(() => {
+                this.chooseValue = val;
+                this.show = true;
+            });
         }
-    };
+    }
+};
 </script>

+ 175 - 175
src/main/vue/src/components/FileUpload.vue

@@ -43,201 +43,201 @@
 </template>
 
 <script>
-    import resolveUrl from 'resolve-url';
-    import axios from 'axios';
-    export default {
-        name: 'FileUpload',
-        props: {
-            single: {
-                type: Boolean,
-                default() {
-                    return false;
-                }
-            },
-            limit: {
-                type: Number,
-                default() {
-                    return 10000;
-                }
-            },
-            value: {},
-            format: {
-                type: String,
-                default: 'string'
+import resolveUrl from 'resolve-url';
+import axios from 'axios';
+export default {
+    name: 'FileUpload',
+    props: {
+        single: {
+            type: Boolean,
+            default() {
+                return false;
             }
         },
-        data() {
+        limit: {
+            type: Number,
+            default() {
+                return 10000;
+            }
+        },
+        value: {},
+        format: {
+            type: String,
+            default: 'string'
+        }
+    },
+    data() {
+        return {
+            fileList: [],
+            emitting: false,
+            uploadUrl: '',
+            previewUrl: null
+        };
+    },
+    computed: {
+        headers() {
             return {
-                fileList: [],
-                emitting: false,
-                uploadUrl: '',
-                previewUrl: null
+                Authorization: 'Bearer ' + localStorage.getItem('token')
             };
         },
-        computed: {
-            headers() {
-                return {
-                    Authorization: 'Bearer ' + localStorage.getItem('token')
-                };
-            },
-            filesLimit() {
-                if (this.single) {
-                    return 1;
-                }
-                return this.limit;
-            },
-            disabled() {
-                return this.fileList.length >= this.limit;
+        filesLimit() {
+            if (this.single) {
+                return 1;
             }
+            return this.limit;
         },
-        created() {
-            this.uploadUrl = resolveUrl(this.$baseUrl, 'upload/file');
-            this.update(this.value);
+        disabled() {
+            return this.fileList.length >= this.limit;
+        }
+    },
+    created() {
+        this.uploadUrl = resolveUrl(this.$baseUrl, 'upload/file');
+        this.update(this.value);
+    },
+    methods: {
+        onSuccess(res, file, fileList) {
+            file.url = res;
+            this.fileList = fileList;
+            this.emit();
         },
-        methods: {
-            onSuccess(res, file, fileList) {
-                file.url = res;
-                this.fileList = fileList;
-                this.emit();
-            },
-            update(value) {
-                if (this.filesLimit === 1) {
-                    if (this.format === 'json') {
-                        this.fileList = value ? [{ name: value.name, url: value.url }] : [];
-                    } else {
-                        this.fileList = value ? [{ name: value.split('/').pop(), url: value }] : [];
-                    }
+        update(value) {
+            if (this.filesLimit === 1) {
+                if (this.format === 'json') {
+                    this.fileList = value ? [{ name: value.name, url: value.url }] : [];
                 } else {
-                    if (!value) {
-                        this.fileList = [];
-                    } else {
-                        this.fileList = value.map(i => {
-                            return { name: i.name, url: i.url };
-                        });
-                    }
+                    this.fileList = value ? [{ name: value.split('/').pop(), url: value }] : [];
                 }
-            },
-            onExceed(files, fileList) {
-                console.log(files, fileList);
-                this.$message.error(`最多上传${this.filesLimit}个文件`);
-            },
-            onPreview(file) {
-                console.log(file);
-            },
-            removeFile(file) {
-                if (file.status === 'uploading') {
-                    this.$refs.upload.abort(file);
-                } else if (file.status === 'success') {
-                    let index = this.fileList.findIndex(i => i.url === file.url);
-                    if (index > -1) {
-                        this.fileList.splice(index, 1);
-                    }
-                }
-                this.emit();
-            },
-            download(file) {
-                window.open(file.url, '_blank');
-            },
-            preview(file) {
-                this.previewUrl = file.url;
-                this.$nextTick(() => {
-                    this.$refs.preview.clickHandler();
-                });
-            },
-            isImage(file) {
-                return /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.url);
-            },
-            emit() {
-                this.emitting = true;
-                if (this.filesLimit === 1) {
-                    if (this.format === 'json') {
-                        this.$emit(
-                            'input',
-                            this.fileList[0]
-                                ? {
-                                    name: this.fileList[0].name,
-                                    url: this.fileList[0].url
-                                }
-                                : null
-                        );
-                    } else {
-                        this.$emit('input', this.fileList[0] ? this.fileList[0].url : null);
-                    }
+            } else {
+                if (!value) {
+                    this.fileList = [];
                 } else {
-                    if (this.format === 'json') {
-                        this.$emit(
-                            'input',
-                            this.fileList.map(i => {
-                                return {
-                                    name: i.name,
-                                    url: i.url
-                                };
-                            })
-                        );
-                    } else {
-                        this.$emit(
-                            'input',
-                            this.fileList.map(i => i.url)
-                        );
-                    }
+                    this.fileList = value.map(i => {
+                        return { name: i.name, url: i.url };
+                    });
                 }
-                this.$nextTick(() => {
-                    this.emitting = false;
-                });
             }
         },
-        watch: {
-            value(value) {
-                if (this.emitting) return;
-                this.update(value);
+        onExceed(files, fileList) {
+            console.log(files, fileList);
+            this.$message.error(`最多上传${this.filesLimit}个文件`);
+        },
+        onPreview(file) {
+            console.log(file);
+        },
+        removeFile(file) {
+            if (file.status === 'uploading') {
+                this.$refs.upload.abort(file);
+            } else if (file.status === 'success') {
+                let index = this.fileList.findIndex(i => i.url === file.url);
+                if (index > -1) {
+                    this.fileList.splice(index, 1);
+                }
             }
+            this.emit();
+        },
+        download(file) {
+            window.open(file.url, '_blank');
+        },
+        preview(file) {
+            this.previewUrl = file.url;
+            this.$nextTick(() => {
+                this.$refs.preview.clickHandler();
+            });
+        },
+        isImage(file) {
+            return /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.url);
+        },
+        emit() {
+            this.emitting = true;
+            if (this.filesLimit === 1) {
+                if (this.format === 'json') {
+                    this.$emit(
+                        'input',
+                        this.fileList[0]
+                            ? {
+                                  name: this.fileList[0].name,
+                                  url: this.fileList[0].url
+                              }
+                            : null
+                    );
+                } else {
+                    this.$emit('input', this.fileList[0] ? this.fileList[0].url : null);
+                }
+            } else {
+                if (this.format === 'json') {
+                    this.$emit(
+                        'input',
+                        this.fileList.map(i => {
+                            return {
+                                name: i.name,
+                                url: i.url
+                            };
+                        })
+                    );
+                } else {
+                    this.$emit(
+                        'input',
+                        this.fileList.map(i => i.url)
+                    );
+                }
+            }
+            this.$nextTick(() => {
+                this.emitting = false;
+            });
+        }
+    },
+    watch: {
+        value(value) {
+            if (this.emitting) return;
+            this.update(value);
         }
-    };
+    }
+};
 </script>
 
 <style lang="less" scoped>
-    .file-list-item {
-        line-height: 1.8;
-        margin-top: 5px;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        overflow: hidden;
-        cursor: pointer;
-        .file-name {
-            padding: 0 90px 0 20px;
-        }
-        .upload-progress {
-            margin-top: 2px;
-            position: absolute;
-            bottom: 0;
-            left: 20px;
-            right: 0;
-            width: auto;
-        }
-        .danger {
-            color: #f56c6c;
-        }
-        .success {
-            color: #67c23a;
-        }
-        .status-icon {
-            position: absolute;
-            left: 0;
-            top: 0;
-            line-height: inherit;
-        }
-        .opt {
-            position: absolute;
-            right: 0;
-            top: 0;
-            line-height: inherit;
-            .opt-icon {
-                margin-left: 15px;
-                transition: color 0.3s;
-                &:hover {
-                    color: #409eff;
-                }
+.file-list-item {
+    line-height: 1.8;
+    margin-top: 5px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+    cursor: pointer;
+    .file-name {
+        padding: 0 90px 0 20px;
+    }
+    .upload-progress {
+        margin-top: 2px;
+        position: absolute;
+        bottom: 0;
+        left: 20px;
+        right: 0;
+        width: auto;
+    }
+    .danger {
+        color: #f56c6c;
+    }
+    .success {
+        color: #67c23a;
+    }
+    .status-icon {
+        position: absolute;
+        left: 0;
+        top: 0;
+        line-height: inherit;
+    }
+    .opt {
+        position: absolute;
+        right: 0;
+        top: 0;
+        line-height: inherit;
+        .opt-icon {
+            margin-left: 15px;
+            transition: color 0.3s;
+            &:hover {
+                color: #409eff;
             }
         }
     }
+}
 </style>

+ 8 - 0
src/main/vue/src/router.js

@@ -63,6 +63,14 @@ const router = new Router({
                         title: '菜单配置'
                     }
                 },
+                {
+                    path: '/menuAuthority',
+                    name: 'menuAuthority',
+                    component: () => import(/* webpackChunkName: "menuAuthority" */ '@/views/MenuAuthority.vue'),
+                    meta: {
+                        title: '菜单权限'
+                    }
+                },
                 {
                     path: '/userEdit',
                     name: 'userEdit',

+ 5 - 9
src/main/vue/src/views/Admin.vue

@@ -111,15 +111,11 @@ export default {
             findActiveMenu([], this.rawMenus);
         },
         getMenus() {
-            this.$http
-                .get('/menu/userMenu', {
-                    name: '系统菜单'
-                })
-                .then(([{ children: menus }]) => {
-                    this.rawMenus = menus;
-                    this.menus = menus;
-                    this.findActiveMenu();
-                });
+            this.$http.get('/menu/userMenu').then(res => {
+                this.rawMenus = res;
+                this.menus = res;
+                this.findActiveMenu();
+            });
         },
         toggleFullScreen() {
             this.isFullscreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;

+ 201 - 0
src/main/vue/src/views/MenuAuthority.vue

@@ -0,0 +1,201 @@
+<template>
+    <el-container class="view-menu-authority">
+        <el-main style="padding:0 20px 0 0">
+            <div class="opts">
+                <el-select v-model="authority">
+                    <el-option
+                        v-for="item in authorities"
+                        :label="item.description"
+                        :value="item.name"
+                        :key="item.name"
+                    ></el-option>
+                </el-select>
+            </div>
+            <div class="menu-tree" v-loading="loading || saving">
+                <el-tree
+                    ref="menuTree"
+                    :data="menus"
+                    show-checkbox
+                    node-key="id"
+                    :props="defaultProps"
+                    @check="preview"
+                >
+                </el-tree>
+            </div>
+            <el-button
+                type="primary"
+                @click="save"
+                style="margin-top:15px;margin-left:20px"
+                :loading="saving"
+                :disabled="loading"
+            >
+                保存
+            </el-button>
+        </el-main>
+        <el-aside>
+            <div class="title">预览</div>
+            <el-menu
+                :collapse="false"
+                background-color="#324157"
+                text-color="#BFCBD9"
+                active-text-color="#20A0FF"
+                :unique-opened="true"
+                :router="true"
+                style="border-right: 1px solid #545c64;"
+                class="el-menu-vertical-demo"
+            >
+                <sys-menu v-for="item in previewMenus" :menu="item" :key="item.id" noRoute> </sys-menu>
+            </el-menu>
+        </el-aside>
+    </el-container>
+</template>
+<script>
+import SysMenu from '../components/SysMenu';
+export default {
+    components: { SysMenu },
+    data() {
+        return {
+            defaultProps: {
+                children: 'children',
+                label: 'name'
+            },
+            menus: [],
+            authorities: [],
+            authority: null,
+            previewMenus: [],
+            loading: false,
+            saving: false
+        };
+    },
+    created() {
+        this.getData();
+    },
+    methods: {
+        getData() {
+            this.loading = true;
+            this.$http
+                .get('/menu/all', { category: this.category })
+                .then(res => {
+                    this.menus = res;
+                    return this.$http.get('/authority/all');
+                })
+                .then(res => {
+                    this.authorities = res;
+                    if (!this.authority && res[0]) {
+                        this.authority = res[0].name;
+                    }
+                    return this.$http.get(`/menu/authority/${this.authority}/get`);
+                })
+                .then(res => {
+                    this.loading = false;
+                    let checkedKeys = [];
+                    const isChecked = menu => {
+                        if (res.includes(menu.id)) {
+                            if (menu.children && menu.children.length) {
+                                let fullChecked = false;
+                                menu.children.forEach(i => {
+                                    let c = isChecked(i);
+                                    if (c) {
+                                        checkedKeys.push(i.id);
+                                    }
+                                    fullChecked = fullChecked && c;
+                                });
+                                return fullChecked;
+                            } else {
+                                return true;
+                            }
+                        }
+                        return false;
+                    };
+                    this.menus.forEach(i => {
+                        if (isChecked(i)) {
+                            checkedKeys.push(i.id);
+                        }
+                    });
+                    this.$refs.menuTree.setCheckedKeys(checkedKeys);
+                    this.$nextTick(() => {
+                        this.preview();
+                    });
+                })
+                .catch(e => {
+                    this.loading = false;
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        },
+        preview() {
+            const checkedNodes = this.$refs.menuTree.getCheckedNodes(false, true);
+
+            const checkedIds = checkedNodes.map(node => node.id);
+
+            const filter = menus => {
+                let filtered = menus
+                    .filter(m => checkedIds.includes(m.id))
+                    .map(i => {
+                        return { ...i };
+                    });
+                filtered.forEach(m => {
+                    if (m.children) {
+                        m.children = [...filter(m.children)];
+                    }
+                });
+                return filtered;
+            };
+            this.previewMenus = filter(this.menus);
+        },
+        save() {
+            this.saving = true;
+            this.$http
+                .post(
+                    `/menu/authority/${this.authority}/save`,
+                    this.$refs.menuTree.getCheckedNodes(false, true).map(node => node.id),
+                    { body: 'json' }
+                )
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('保存成功');
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error || '保存失败');
+                });
+        }
+    },
+    watch: {
+        authority() {
+            this.getData();
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.title {
+    line-height: 32px;
+    color: #303133;
+    font-size: 14px;
+    font-weight: bold;
+    margin-top: 10px;
+}
+.opts {
+    margin-bottom: 10px;
+    > * {
+        margin-right: 10px;
+    }
+}
+</style>
+<style lang="less">
+.view-menu-authority {
+    padding-bottom: 20px;
+    .menu-tree {
+        border-radius: 4px;
+        background: white;
+        padding: 10px;
+    }
+    .el-tree-node__content {
+        height: 40px !important;
+    }
+    .el-aside {
+        width: 200px !important;
+    }
+}
+</style>

+ 196 - 109
src/main/vue/src/views/Menus.vue

@@ -1,56 +1,96 @@
 <template>
-    <div class="edit-view">
-        <el-tree
-            :data="menus"
-            :render-content="renderContent"
-            :highlight-current="true"
-            :expand-on-click-node="true"
-            node-key="id"
-            default-expand-all
-            v-loading="loading"
-        >
-        </el-tree>
-        <el-button type="text" @click="addRootMenu" style="margin-left: 24px;">添加 </el-button>
-        <el-dialog :visible.sync="dialogVisible" title="添加菜单">
-            <el-form :model="menu" ref="form" label-position="top">
-                <el-form-item
-                    label="菜单名"
-                    prop="name"
-                    :rules="[{ required: true, message: '请填写菜单名', trigger: 'blur' }]"
-                >
-                    <el-input v-model="menu.name"></el-input>
-                </el-form-item>
-                <el-form-item label="菜单地址" prop="path">
-                    <el-input v-model="menu.path"></el-input>
-                </el-form-item>
-                <el-form-item prop="icon">
-                    <template slot="label"
-                        >图标
-                        <a
-                            href="https://fontawesome.com/icons?d=gallery&s=brands,solid&m=free"
-                            target="_blank"
-                            class="available-icons"
-                            >查看所有可用图标</a
-                        ></template
+    <div>
+        <el-select v-model="category" placeholder="分类" clearable>
+            <el-option v-for="item in categories" :label="item" :key="item" :value="item"></el-option>
+        </el-select>
+        <el-row :gutter="20">
+            <el-col :span="12">
+                <div class="menu-tree">
+                    <el-tree
+                        :data="menus"
+                        :render-content="renderContent"
+                        :highlight-current="true"
+                        :expand-on-click-node="true"
+                        node-key="id"
+                        v-loading="loading"
+                        accordion
+                        @node-click="nodeClick"
+                        :default-expanded-keys="expandKeys"
+                        :default-checked-keys="expandKeys"
                     >
-                    <el-input v-model="icon">
-                        <template slot="append"
-                            ><span ref="iconContainer" style="font-size: 18px;"><i class="fas fa-"></i></span
-                        ></template>
-                    </el-input>
-                </el-form-item>
-            </el-form>
-            <div slot="footer">
-                <el-button @click="dialogVisible = false">取消 </el-button>
-                <el-button type="primary" @click="addMenu" :loading="loading">保存 </el-button>
-            </div>
-        </el-dialog>
+                    </el-tree>
+                    <el-button type="text" @click="addRootMenu" style="margin-left: 24px;">添加 </el-button>
+                </div>
+            </el-col>
+            <transition name="el-fade-in">
+                <el-col :span="12" v-if="dialogVisible">
+                    <div class="menu-tree">
+                        <div style="font-weight:bold;padding:10px 0">{{ menu.id ? '编辑菜单' : '新增菜单' }}</div>
+                        <el-form :model="menu" ref="form" label-position="top">
+                            <el-form-item
+                                label="菜单名"
+                                prop="name"
+                                :rules="[{ required: true, message: '请填写菜单名', trigger: 'blur' }]"
+                            >
+                                <el-input v-model="menu.name"></el-input>
+                            </el-form-item>
+                            <el-form-item label="菜单地址" prop="path">
+                                <el-input v-model="menu.path"></el-input>
+                            </el-form-item>
+                            <el-form-item prop="icon">
+                                <template slot="label"
+                                    >图标
+                                    <a
+                                        href="https://fontawesome.com/icons?d=gallery&s=brands,solid&m=free"
+                                        target="_blank"
+                                        class="available-icons"
+                                        >查看所有可用图标</a
+                                    ></template
+                                >
+                                <el-input v-model="icon">
+                                    <template slot="append"
+                                        ><span ref="iconContainer" style="font-size: 18px;"
+                                            ><i class="fas fa-"></i></span
+                                    ></template>
+                                </el-input>
+                            </el-form-item>
+                            <el-form-item prop="category" label="分类" v-if="menu.root">
+                                <el-input v-model="menu.category"></el-input>
+                            </el-form-item>
+                            <el-form-item prop="authorities" label="权限">
+                                <el-select
+                                    v-model="menu.authorities"
+                                    clearable
+                                    multiple
+                                    value-key="name"
+                                    style="width:100%"
+                                >
+                                    <el-option
+                                        v-for="item in authorities"
+                                        :label="item.description"
+                                        :value="item"
+                                        :key="item.name"
+                                    ></el-option>
+                                </el-select>
+                            </el-form-item>
+                        </el-form>
+                        <div slot="footer">
+                            <el-button @click="dialogVisible = false">取消 </el-button>
+                            <el-button type="primary" @click="addMenu" :loading="loading">保存 </el-button>
+                        </div>
+                    </div>
+                </el-col>
+            </transition>
+        </el-row>
     </div>
 </template>
 <script>
 export default {
     created() {
         this.getData();
+        this.$http.get('/authority/all').then(res => {
+            this.authorities = res;
+        });
     },
     data() {
         return {
@@ -68,7 +108,11 @@ export default {
             parent: null,
             currentRef: null,
             edit: false,
-            icon: ''
+            icon: '',
+            categories: [],
+            category: null,
+            expandKeys: [],
+            authorities: []
         };
     },
     methods: {
@@ -78,7 +122,9 @@ export default {
                 path: '',
                 active: true,
                 root: true,
-                icon: 'bars'
+                icon: 'bars',
+                category: this.category || null,
+                authorities: [{ name: 'ROLE_ADMIN' }]
             };
             this.parent = null;
             this.icon = 'bars';
@@ -96,7 +142,8 @@ export default {
                 path: '',
                 active: true,
                 root: false,
-                icon: null
+                icon: null,
+                authorities: [{ name: 'ROLE_ADMIN' }]
             };
             this.icon = '';
             this.dialogVisible = true;
@@ -134,22 +181,12 @@ export default {
                     let menu = { ...this.menu };
                     delete menu.children;
                     this.$http
-                        .post('/menu/save', menu)
+                        .post('/menu/save', menu, { body: 'json' })
                         .then(res => {
                             this.loading = false;
                             this.$message.success('添加成功');
                             this.dialogVisible = false;
-                            if (this.edit) {
-                                for (let [key, value] of Object.entries(res)) {
-                                    console.log(`${key}: ${value}`);
-                                    this.$set(this.currentRef, key, value);
-                                }
-                            } else if (this.parent) {
-                                this.parent.children = this.parent.children || [];
-                                this.parent.children.push(res);
-                            } else {
-                                this.menus.push(res);
-                            }
+                            this.getData();
                         })
                         .catch(e => {
                             console.log(e);
@@ -167,28 +204,27 @@ export default {
                 type: 'error'
             })
                 .then(() => {
-                    this.$http
-                        .post('/menu/save', {
+                    return this.$http.post(
+                        '/menu/save',
+                        {
                             ...data,
                             active: false,
                             children: null
-                        })
-                        .then(res => {
-                            this.$message.success('删除成功');
-                            let index = node.parent.data.children.findIndex(i => {
-                                return i.id === data.id;
-                            });
-                            if (index > -1) {
-                                node.parent.data.children.splice(index, 1);
-                            }
-                        })
-                        .catch(e => {
-                            console.log(e);
-                            this.loading = false;
-                            this.$message.error(e.error);
-                        });
+                        },
+                        { body: 'json' }
+                    );
+                })
+                .then(res => {
+                    this.$message.success('删除成功');
+                    this.getData();
                 })
-                .catch(() => {});
+                .catch(e => {
+                    this.loading = false;
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    }
+                });
         },
         moveUp(node, data) {
             if (node.previousSibling) {
@@ -196,25 +232,28 @@ export default {
                 let sort0 = node.previousSibling.data.sort,
                     sort1 = node.data.sort;
                 Promise.all([
-                    this.$http.post('/menu/save', {
-                        ...node.data,
-                        children: null,
-                        sort: sort0
-                    }),
-                    this.$http.post('/menu/save', {
-                        ...node.previousSibling.data,
-                        children: null,
-                        sort: sort1
-                    })
+                    this.$http.post(
+                        '/menu/save',
+                        {
+                            ...node.data,
+                            children: null,
+                            sort: sort0
+                        },
+                        { body: 'json' }
+                    ),
+                    this.$http.post(
+                        '/menu/save',
+                        {
+                            ...node.previousSibling.data,
+                            children: null,
+                            sort: sort1
+                        },
+                        { body: 'json' }
+                    )
                 ])
                     .then(_ => {
                         this.loading = false;
-                        let tmp = { ...node.previousSibling.data, sort: sort1 };
-                        node.previousSibling.data = {
-                            ...node.data,
-                            sort: sort0
-                        };
-                        node.data = tmp;
+                        this.getData();
                     })
                     .catch(e => {
                         console.log(e);
@@ -229,22 +268,28 @@ export default {
                 let sort0 = node.data.sort,
                     sort1 = node.nextSibling.data.sort;
                 Promise.all([
-                    this.$http.post('/menu/save', {
-                        ...node.data,
-                        children: null,
-                        sort: sort1
-                    }),
-                    this.$http.post('/menu/save', {
-                        ...node.nextSibling.data,
-                        children: null,
-                        sort: sort0
-                    })
+                    this.$http.post(
+                        '/menu/save',
+                        {
+                            ...node.data,
+                            children: null,
+                            sort: sort1
+                        },
+                        { body: 'json' }
+                    ),
+                    this.$http.post(
+                        '/menu/save',
+                        {
+                            ...node.nextSibling.data,
+                            children: null,
+                            sort: sort0
+                        },
+                        { body: 'json' }
+                    )
                 ])
                     .then(_ => {
                         this.loading = false;
-                        let tmp = { ...node.nextSibling.data, sort: sort0 };
-                        node.nextSibling.data = { ...node.data, sort: sort1 };
-                        node.data = tmp;
+                        this.getData();
                     })
                     .catch(e => {
                         console.log(e);
@@ -255,7 +300,7 @@ export default {
         },
         getData() {
             this.$http
-                .get('/menu/all')
+                .get('/menu/all', { category: this.category })
                 .then(res => {
                     this.menus = res;
                 })
@@ -263,10 +308,19 @@ export default {
                     console.log(e);
                     this.$message.error(e.error);
                 });
+            this.$http.get('/menu/categories').then(res => {
+                this.categories = res;
+            });
         },
         renderContent(h, { node, data, store }) {
             return (
-                <span class="custom-tree-node">
+                <span
+                    class={
+                        this.menu.id == data.id || (this.menu.parent == data.id && !this.menu.id)
+                            ? 'custom-tree-node selected'
+                            : 'custom-tree-node'
+                    }
+                >
                     <span>{data.name}</span>
                     <span class="url">{data.path}</span>
                     <span class="opt">
@@ -344,16 +398,38 @@ export default {
                 FontAwesome.dom.i2svg();
                 this.menu.icon = '';
             }
+        },
+        nodeClick(data, node, el) {
+            if (this.expandKeys[0] != data.id) {
+                this.expandKeys = [data.id];
+            }
         }
     },
     watch: {
         icon(val) {
             this.showIcon(val);
+        },
+        category() {
+            this.getData();
         }
     }
 };
 </script>
+<style lang="less" scoped>
+.menu-tree {
+    border-radius: 4px;
+    background: white;
+    margin-top: 20px;
+    padding: 10px;
+}
+</style>
 <style lang="less">
+.menu-tree {
+    .el-tree-node__content {
+        height: 40px;
+        line-height: 40px;
+    }
+}
 .custom-tree-node {
     flex: 1;
     display: flex;
@@ -361,6 +437,8 @@ export default {
     justify-content: space-between;
     font-size: 14px;
     padding-right: 8px;
+    line-height: 40px;
+    height: 40px;
     .url {
         flex-grow: 1;
         text-align: right;
@@ -370,6 +448,15 @@ export default {
     .opt {
         opacity: 0;
     }
+    &.selected {
+        border: 2px solid #409eff;
+        border-radius: 4px;
+        padding: 0 10px;
+        box-sizing: border-box;
+        .opt {
+            opacity: 1;
+        }
+    }
 }
 
 .custom-tree-node:hover {