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

更新 Menu 相关页面和接口

reghao 1 éve
szülő
commit
9db2258046

+ 12 - 11
web/src/main/java/cn/reghao/devops/web/account/controller/MenuController.java

@@ -1,5 +1,6 @@
 package cn.reghao.devops.web.account.controller;
 
+import cn.reghao.devops.web.account.model.vo.MenuTree;
 import cn.reghao.devops.web.account.service.MenuService;
 import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.devops.web.account.model.dto.MenuDto;
@@ -12,8 +13,6 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * @author reghao
@@ -58,13 +57,15 @@ public class MenuController {
     }
 
     @ApiOperation(value = "获取指定状态的菜单")
-    @GetMapping(value = "/{enabled}", produces = MediaType.APPLICATION_JSON_VALUE)
+    @GetMapping(value = "/ztree/{enabled}", produces = MediaType.APPLICATION_JSON_VALUE)
     public String list(@PathVariable(value = "enabled") Boolean enabled) {
-        List<Menu> list = menuService.getSortedMenusByStatus(enabled).stream()
-                .peek(menu -> menu.getRoles().forEach(role -> {
-                    role.setMenus(null);
-                }))
-                .collect(Collectors.toList());
+        List<MenuTree> list = menuService.getMenusByStatus(enabled);
+        return WebResult.success(list);
+    }
+
+    @GetMapping(value = "/ztree/parent", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getParentMenus() {
+        List<MenuTree> list = menuService.getParentMenus();
         return WebResult.success(list);
     }
 
@@ -72,11 +73,11 @@ public class MenuController {
     @GetMapping(value = "/sorted/{pid}/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
     public String sortList(@PathVariable(value = "pid") int pid,
                            @PathVariable(value = "id", required = false) Menu menu) {
-        Map<Integer, String> map = menuService.getSortedChildGroupByPid(pid);
+        /*Map<Integer, String> map = menuService.getSortedChildGroupByPid(pid);
         // 排除当前 menu
         if (menu != null) {
             map.remove(menu.getPos());
-        }
-        return WebResult.success(map);
+        }*/
+        return WebResult.failWithMsg("not implement");
     }
 }

+ 2 - 13
web/src/main/java/cn/reghao/devops/web/account/controller/RoleController.java

@@ -3,6 +3,7 @@ package cn.reghao.devops.web.account.controller;
 import cn.reghao.devops.web.account.model.dto.RoleDto;
 import cn.reghao.devops.web.account.model.po.Menu;
 import cn.reghao.devops.web.account.model.po.Role;
+import cn.reghao.devops.web.account.model.vo.MenuTree;
 import cn.reghao.devops.web.account.service.MenuService;
 import cn.reghao.devops.web.account.service.RoleService;
 import cn.reghao.jutil.jdk.result.WebResult;
@@ -15,7 +16,6 @@ import org.springframework.web.bind.annotation.*;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * @author reghao
@@ -57,18 +57,7 @@ public class RoleController {
     @ApiOperation("获取角色可访问的资源")
     @GetMapping(value = "/menus/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
     public String getRoleMenus(@PathVariable("id") Role role) {
-        List<Menu> allMenus = menuService.getAllMenus();
-        allMenus.forEach(menu -> {
-            // TODO 序列化时会递归 roles 中的 Role 导致 StackOverflow, 因此在序列化时应该把 Role 的 menus 设置为 null
-            Set<Role> roles = menu.getRoles().stream()
-                    .peek(role1 -> role1.setMenus(null))
-                    .collect(Collectors.toSet());
-            if (roles.contains(role)) {
-                // 对应前端的 checked 复选框
-                menu.setRemark("auth:true");
-            }
-        });
-
+        List<MenuTree> allMenus = menuService.getMenusByRole(role);
         return WebResult.success(allMenus);
     }
 

+ 1 - 32
web/src/main/java/cn/reghao/devops/web/account/controller/page/MenuPageController.java

@@ -1,11 +1,7 @@
 package cn.reghao.devops.web.account.controller.page;
 
-import cn.reghao.devops.web.account.model.dto.MenuDto;
-import cn.reghao.devops.web.account.model.po.Role;
 import cn.reghao.devops.web.account.model.po.Menu;
 import cn.reghao.devops.web.account.model.vo.RoleVO;
-import cn.reghao.devops.web.account.service.MenuService;
-import cn.reghao.devops.web.account.service.RoleService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.stereotype.Controller;
@@ -23,14 +19,6 @@ import java.util.stream.Collectors;
 @RequestMapping("/rbac/menu")
 @Controller
 public class MenuPageController {
-    private final MenuService menuService;
-    private final RoleService roleService;
-
-    public MenuPageController(MenuService menuService, RoleService roleService) {
-        this.menuService = menuService;
-        this.roleService = roleService;
-    }
-
     @ApiOperation(value = "资源列表页面")
     @GetMapping
     public String menuPage(@RequestParam(value = "enabled", required = false) Boolean enabled, Model model) {
@@ -45,32 +33,13 @@ public class MenuPageController {
     @ApiOperation(value = "资源添加页面")
     @GetMapping(value = "/add")
     public String addMenuPage(Model model) {
-        Set<Role> allRoles = new HashSet<>(roleService.getAllRoles());
-        Set<Role> menuRoles = Collections.emptySet();
-
-        model.addAttribute("allRoles", allRoles);
-        model.addAttribute("menuRoles", menuRoles);
         return "/rbac/menu/add";
     }
 
     @ApiOperation(value = "资源编辑页面")
     @GetMapping("/edit/{id}")
     public String toEdit(@PathVariable("id") Menu menu, Model model) {
-        int pid = menu.getPid();
-        Menu pMenu;
-        if (pid == 0) {
-            pMenu = new Menu(pid, "根菜单");
-        } else {
-            pMenu = menuService.getById(pid);
-        }
-
-        Set<Role> allRoles = new HashSet<>(roleService.getAllRoles());
-        Set<Role> menuRoles = Collections.emptySet();
-
-        model.addAttribute("allRoles", allRoles);
-        model.addAttribute("menuRoles", menuRoles);
-        model.addAttribute("menu", new MenuDto(menu));
-        model.addAttribute("pMenu", pMenu);
+        model.addAttribute("menu", menu);
         return "/rbac/menu/edit";
     }
 

+ 3 - 0
web/src/main/java/cn/reghao/devops/web/account/db/repository/MenuRepository.java

@@ -10,6 +10,9 @@ import java.util.List;
  * @date 2019-08-29 10:52:14
  */
 public interface MenuRepository extends JpaRepository<Menu, Integer> {
+    int countByPid(int pid);
     List<Menu> findByPid(int pid);
+    Menu findByUrl(String url);
     List<Menu> findByEnabled(boolean isEnabled);
+    List<Menu> findByTypeAndEnabledIsTrue(String type);
 }

+ 7 - 26
web/src/main/java/cn/reghao/devops/web/account/model/dto/MenuDto.java

@@ -1,47 +1,28 @@
 package cn.reghao.devops.web.account.model.dto;
 
-import cn.reghao.devops.web.account.model.po.Menu;
-import cn.reghao.devops.web.account.model.po.Role;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import lombok.Data;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
-import java.io.Serializable;
-import java.util.Set;
 
 /**
  * @author reghao
  * @date 2021-07-15 14:06:13
  */
 @NoArgsConstructor
-@Data
-public class MenuDto implements Serializable {
-    private static final long serialVersionUID = 1L;
-
+@Setter
+@Getter
+public class MenuDto {
     @NotNull(message = "菜单 ID 不能为 NULL")
     private Integer menuId;
-    // 父级菜单 ID
-    @NotNull(message = "父级菜单不能为 NULL")
-    private Integer pid;
     // 在同一个 pid 组内的位置,作为排序使用
     private Integer pos;
     @NotBlank(message = "菜单名不能为空白字符串")
     private String name;
-    @NotBlank(message = "URL 地址不能为空白字符串,目录类型使用可使用 # 字符")
+    @NotBlank(message = "URL 地址不能为空白字符串")
     private String url;
+    @NotBlank(message = "icon 不能为空白字符串")
     private String icon;
-    @NotNull(message = "角色不能为 NULL")
-    @JsonIgnore
-    private Set<Role> roles;
-
-    public MenuDto(Menu menu) {
-        this.menuId = menu.getId();
-        this.pid = menu.getPid();
-        this.pos = menu.getPos();
-        this.name = menu.getName();
-        this.url = menu.getUrl();
-        this.icon = menu.getIcon();
-    }
 }

+ 8 - 9
web/src/main/java/cn/reghao/devops/web/account/model/po/Menu.java

@@ -14,7 +14,6 @@ import java.util.*;
  * @author reghao
  * @date 2021-04-04 22:42:14
  */
-@NoArgsConstructor
 @Setter
 @Getter
 @Table(name = "sys_menu")
@@ -25,28 +24,28 @@ public class Menu extends BaseEntity {
     private String type;
     @NotBlank(message = "菜单名不能为空白字符串")
     private String name;
-    @NotBlank(message = "URL 地址不能为空白字符串,目录类型使用可使用 # 字符")
+    @NotBlank(message = "URL 地址不能为空白字符串")
     private String url;
-    @NotNull(message = "菜单图标不能为 NULL")
     private String icon;
     // 父级菜单 ID
     @NotNull(message = "父级菜单不能为 NULL")
     private Integer pid;
     // 在同一个 pid 组内的位置,作为排序使用
-    @NotNull
     private Integer pos;
     private Boolean enabled;
-    // Menu 拥有的所有子 Menu(按排序顺序, 不持久化)
-    @Transient
-    private Map<Integer, Menu> children;
     @ManyToMany(mappedBy = "menus")
-    @NotNull(message = "角色不能为 NULL")
     private Set<Role> roles;
+    // Menu 拥有的所有子 Menu(按排序顺序, 不持久化)
     @Transient
-    private String remark;
+    private Map<Integer, Menu> children;
 
     public Menu(int id, String name) {
         this.id = id;
         this.name = name;
     }
+
+    public Menu() {
+        this.enabled = true;
+        this.icon = "layui-icon layui-icon-app";
+    }
 }

+ 9 - 5
web/src/main/java/cn/reghao/devops/web/account/model/vo/MenuVO.java → web/src/main/java/cn/reghao/devops/web/account/model/vo/MenuTree.java

@@ -10,17 +10,21 @@ import java.io.Serializable;
  * @date 2021-07-15 14:06:13
  */
 @Data
-public class MenuVO implements Serializable {
-    private static final long serialVersionUID = 1L;
-    private Integer menuId;
+public class MenuTree {
+    private int id;
+    private int pid;
     private String name;
     private String url;
     private String type;
+    // 授予了访问权限
+    private boolean granted;
 
-    public MenuVO(Menu menu) {
-        this.menuId = menu.getId();
+    public MenuTree(Menu menu) {
+        this.id = menu.getId();
+        this.pid = menu.getPid();
         this.name = menu.getName();
         this.url = menu.getUrl();
         this.type = menu.getType();
+        this.granted = false;
     }
 }

+ 5 - 5
web/src/main/java/cn/reghao/devops/web/account/service/MenuService.java

@@ -1,11 +1,12 @@
 package cn.reghao.devops.web.account.service;
 
+import cn.reghao.devops.web.account.model.po.Role;
+import cn.reghao.devops.web.account.model.vo.MenuTree;
 import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.devops.web.account.model.dto.MenuDto;
 import cn.reghao.devops.web.account.model.po.Menu;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * @author reghao
@@ -16,8 +17,7 @@ public interface MenuService {
     Result updateMenu(MenuDto menuDto);
     void updateMenusStatus(boolean status, List<Integer> menuIds);
     Result deleteMenu(Integer menuId);
-    Menu getById(Integer menuId);
-    List<Menu> getAllMenus();
-    List<Menu> getSortedMenusByStatus(Boolean isEnabled);
-    Map<Integer, String> getSortedChildGroupByPid(int pid);
+    List<MenuTree> getMenusByRole(Role role);
+    List<MenuTree> getMenusByStatus(Boolean isEnabled);
+    List<MenuTree> getParentMenus();
 }

+ 94 - 153
web/src/main/java/cn/reghao/devops/web/account/service/impl/MenuServiceImpl.java

@@ -3,6 +3,7 @@ package cn.reghao.devops.web.account.service.impl;
 import cn.reghao.devops.web.account.db.repository.MenuRepository;
 import cn.reghao.devops.web.account.db.repository.RoleRepository;
 import cn.reghao.devops.web.account.model.constant.MenuType;
+import cn.reghao.devops.web.account.model.vo.MenuTree;
 import cn.reghao.devops.web.account.service.MenuService;
 import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.jutil.jdk.result.ResultStatus;
@@ -11,8 +12,8 @@ import cn.reghao.devops.web.account.model.po.Menu;
 import cn.reghao.devops.web.account.model.po.Role;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
-import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -33,23 +34,50 @@ public class MenuServiceImpl implements MenuService {
 
     @Override
     public synchronized Result addMenu(Menu menu) {
-        Result result = moreThanTwoParents(menu.getPid());
+        int pid = menu.getPid();
+        int total = menuRepository.countByPid(pid);
+        if (total >= 100) {
+            return Result.result(ResultStatus.FAIL, "每个 dir 最多可包含 100 个 page");
+        }
+
+        Result result = moreThanTwoParents(pid);
         if (result.getCode() != ResultStatus.SUCCESS.getCode()) {
             return result;
         }
 
-        // 调整 menu 组内元素的位置
-        insertNewMenu(menu);
-        menu.setEnabled(true);
-        Menu menuEntity = menuRepository.save(menu);
+        String type = menu.getType();
+        if (type.equals(MenuType.dir.name())) {
+            menu.setUrl("#");
+        } else {
+            String url = menu.getUrl();
+            Result result1 = checkUrl(url);
+            if (result1.getCode() != ResultStatus.SUCCESS.getCode()) {
+                return result1;
+            }
+        }
+
+        List<Menu> siblings = menuRepository.findByPid(menu.getPid());
+        if (siblings.isEmpty()) {
+            menu.setPos(1);
+        } else {
+            siblings.sort(Comparator.comparingInt(Menu::getPos));
+            int max = siblings.get(siblings.size()-1).getPos();
+            menu.setPos(max+1);
+        }
 
-        //Set<Role> roles = menu.getRoles();
-        Set<Role> roles = Collections.emptySet();
-        roles.forEach(role -> role.getMenus().add(menuEntity));
-        roleRepository.saveAll(new ArrayList<>(roles));
+        menuRepository.save(menu);
         return Result.result(ResultStatus.SUCCESS);
     }
 
+    private Result checkUrl(String url) {
+        Menu menu1 = menuRepository.findByUrl(url);
+        if (menu1 != null) {
+            return Result.result(ResultStatus.FAIL, String.format("url 为 %s 的 Menu 已存在", url));
+        }
+
+        return Result.success();
+    }
+
     /**
      * 检查菜单的层级, Menu 最多只能有两个 parent, 即最多只能有三级菜单
      *
@@ -73,151 +101,46 @@ public class MenuServiceImpl implements MenuService {
         return Result.result(ResultStatus.SUCCESS);
     }
 
-    /**
-     * 向 menu 组插入元素时调整组内 menu 的位置
-     *
-     * @date 2021-05-18 上午9:54
-     */
-    private void insertNewMenu(Menu menu) {
-        int pid = menu.getPid();
-        if (pid != 0) {
-            Menu pMenu = menuRepository.getOne(pid);
-            if (pMenu.getType().equals(MenuType.page.name())) {
-                log.error("父级菜单的类型不能是 PAGE");
-                return;
-            }
-        }
-
-        List<Menu> menus = menuRepository.findByPid(pid);
-        menus.sort(Comparator.comparingInt(Menu::getPos));
-        // 组内没有 menu 或新增的 menu 在组内排在最后一位
-        if (menus.isEmpty()) {
-            menu.setPos(1);
-            return;
-        }
-
-        // menu 的新位置
-        int pos = menu.getPos()+1;
-        int size = menus.size();
-        if (pos == 1) {
-            // menu 的位置在 menu 组的首位, 组内的其他元素位置向后移一位
-            menu.setPos(1);
-            moveBackward(menus, 1);
-        } else if (pos >= size+1) {
-            // menu 的位置在 menu 组的末尾, 组内的其他元素位置不变
-            menu.setPos(size+1);
-        } else {
-            // menu 的位置在 menu 组的中间, pos 开始的元素位置向后移一位
-            menu.setPos(pos);
-            moveBackward(menus, pos);
-        }
-    }
-
-    /**
-     * 向后移动元素(向 menu 组新增元素时调用)
-     *
-     * @return
-     * @date 2021-07-15 下午5:07
-     */
-    private void moveBackward(List<Menu> menus, int startPos) {
-        for (int i = startPos-1; i < menus.size(); i++) {
-            Menu tmpMenu = menus.get(i);
-            int newPos = tmpMenu.getPos()+1;
-            tmpMenu.setPos(newPos);
-        }
-        menuRepository.saveAll(menus);
-    }
-
-    /**
-     * 向前移动元素(从 menu 组删除元素时调用)
-     *
-     * @param
-     * @return
-     * @date 2021-07-15 下午5:41
-     */
-    private void moveForward(List<Menu> menus, int startPos) {
-        for (int i = startPos; i < menus.size(); i++) {
-            Menu tmpMenu = menus.get(i);
-            int newPos = tmpMenu.getPos()-1;
-            tmpMenu.setPos(newPos);
-        }
-        menuRepository.saveAll(menus);
-    }
-
     @Override
     public synchronized Result updateMenu(MenuDto menuDto) {
         int menuId = menuDto.getMenuId();
-        Menu menuEntity = menuRepository.getOne(menuId);
+        Menu menuEntity = menuRepository.findById(menuId).orElse(null);
         if (menuEntity == null) {
             return Result.result(ResultStatus.FAIL, String.format("ID 为 %s 的 Menu 不存在", menuId));
         }
 
-        int oldPos = menuEntity.getPos();
-        // menu 的新位置
-        int newPos = menuDto.getPos()+1;
-        int pid = menuDto.getPid();
-        if (pid != menuEntity.getPid()) {
-            Result result = moreThanTwoParents(pid);
-            if (result.getCode() != ResultStatus.SUCCESS.getCode()) {
-                return result;
-            }
-
-            // menu 更换到了新的 menu 组
-            menuEntity.setPid(pid);
-            menuEntity.setPos(menuDto.getPos());
-            insertNewMenu(menuEntity);
-        } else if (newPos != oldPos) {
-            reOrderMenus(pid, oldPos, newPos);
-            menuEntity.setPos(newPos);
+        String url = menuDto.getUrl();
+        Result result1 = checkUrl(url);
+        if (result1.getCode() != ResultStatus.SUCCESS.getCode()) {
+            return result1;
         }
 
         menuEntity.setName(menuDto.getName());
         menuEntity.setUrl(menuDto.getUrl());
         menuEntity.setIcon(menuDto.getIcon());
-        menuEntity.setUpdateTime(LocalDateTime.now());
         menuRepository.save(menuEntity);
-
-        Set<Role> roles = menuDto.getRoles();
-        roles.forEach(role -> role.getMenus().add(menuEntity));
-        roleRepository.saveAll(new ArrayList<>(roles));
         return Result.result(ResultStatus.SUCCESS);
     }
 
-    public void updateMenusStatus(boolean status, List<Integer> menuIds) {
-        menuRepository.findAllById(menuIds).forEach(menu -> {
-            menu.setEnabled(status);
-            menuRepository.save(menu);
-        });
-    }
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public synchronized Result deleteMenu(Integer menuId) {
+        Menu menu = menuRepository.findById(menuId).orElse(null);
+        if (menu == null) {
+            return Result.result(ResultStatus.FAIL, String.format("ID 为 %s 的 Menu 不存在", menuId));
+        }
 
-    /**
-     * 对组内的菜单进行重排序,只需调整 oldPos~newPos 之间的元素位置
-     *
-     * @param
-     * @return
-     * @date 2021-07-21 上午11:07
-     */
-    private void reOrderMenus(int pid, int oldPos, int newPos) {
-        Map<Integer, Menu> map = menuRepository.findByPid(pid).stream()
-                .collect(Collectors.toMap(Menu::getPos, menu -> menu));
-        map.remove(oldPos);
-        if (newPos < oldPos) {
-            // 向前移动
-            for (int i = newPos, j = 1; i < oldPos; i++, j++) {
-                map.get(i).setPos(newPos+j);
-            }
-        } else {
-            // 向后移动
-            for (int i = newPos-1, j = 1; i > oldPos; i--, j++) {
-                map.get(i).setPos(newPos-j);
+        String type = menu.getType();
+        if (type.equals(MenuType.dir.name())) {
+            List<Menu> list1 = menuRepository.findByPid(menu.getId());
+            Map<String, List<Menu>> map =  list1.stream().collect(Collectors.groupingBy(Menu::getType));
+            List<Menu> dirMenus = map.get(MenuType.dir.name());
+            if (!dirMenus.isEmpty()) {
+                for (Menu menu1 : dirMenus) {
+                    List<Menu> list2 = menuRepository.findByPid(menu1.getId());
+                }
             }
         }
-        menuRepository.saveAll(new ArrayList<>(map.values()));
-    }
-
-    @Override
-    public synchronized Result deleteMenu(Integer menuId) {
-        Menu menu = menuRepository.getOne(menuId);
 
         // 删除 Role 关联的 Menu
         for (Role role : menu.getRoles()) {
@@ -225,41 +148,59 @@ public class MenuServiceImpl implements MenuService {
             roleRepository.save(role);
         }
 
-        // 重新调整组内排序
-        int pid = menu.getPid();
-        List<Menu> menus = menuRepository.findByPid(pid);
-        menus.sort(Comparator.comparingInt(Menu::getPos));
-        int pos = menu.getPos();
-        moveForward(menus, pos);
         menuRepository.delete(menu);
         return Result.result(ResultStatus.SUCCESS);
     }
 
+    private void deleteRoleMenus(Menu menu) {
+    }
+
     @Override
-    public Menu getById(Integer menuId) {
-        return menuRepository.findById(menuId).orElse(null);
+    public List<MenuTree> getMenusByStatus(Boolean isEnabled) {
+        List<Menu> menuList = menuRepository.findByEnabled(isEnabled);
+        return getSortedZTree(menuList, null);
     }
 
     @Override
-    public List<Menu> getAllMenus() {
-        return menuRepository.findAll();
+    public List<MenuTree> getMenusByRole(Role role) {
+        List<Menu> allMenus = menuRepository.findAll();
+        return getSortedZTree(allMenus, role);
     }
 
     @Override
-    public List<Menu> getSortedMenusByStatus(Boolean isEnabled) {
-        List<Menu> menuList = menuRepository.findByEnabled(isEnabled);
-        Map<Integer, List<Menu>> map =  menuList.stream().collect(Collectors.groupingBy(Menu::getPid));
-        List<Menu> list = new ArrayList<>();
+    public List<MenuTree> getParentMenus() {
+        List<Menu> allMenus = menuRepository.findByTypeAndEnabledIsTrue(MenuType.dir.name());
+        return getSortedZTree(allMenus, null);
+    }
+
+    private List<MenuTree> getSortedZTree(List<Menu> list, Role role) {
+        Map<Integer, List<Menu>> map =  list.stream().collect(Collectors.groupingBy(Menu::getPid));
+        List<MenuTree> menuTrees = new ArrayList<>();
         map.forEach((pid, menus) -> {
-            list.addAll(menus.stream()
+            menuTrees.addAll(menus.stream()
                     .sorted(Comparator.comparing(Menu::getPos))
+                    .map(menu -> {
+                        MenuTree menuTree = new MenuTree(menu);
+                        Set<Role> roles = menu.getRoles();
+                        if (roles.contains(role)) {
+                            // 对应前端的 checked 复选框
+                            menuTree.setGranted(true);
+                        }
+                        return menuTree;
+                    })
                     .collect(Collectors.toList()));
         });
 
-        return list;
+        return menuTrees;
+    }
+
+    public void updateMenusStatus(boolean status, List<Integer> menuIds) {
+        menuRepository.findAllById(menuIds).forEach(menu -> {
+            menu.setEnabled(status);
+            menuRepository.save(menu);
+        });
     }
 
-    @Override
     public Map<Integer, String> getSortedChildGroupByPid(int pid) {
         List<Menu> menus = menuRepository.findByPid(pid);
         menus.sort(Comparator.comparingInt(Menu::getPos));

+ 1 - 1
web/src/main/java/cn/reghao/devops/web/config/SwaggerConfig.java

@@ -27,7 +27,7 @@ public class SwaggerConfig {
         return new Docket(DocumentationType.SWAGGER_2)
                 .apiInfo(apiInfo())
                 .select()
-                .apis(RequestHandlerSelectors.basePackage("cn.reghao.devops.web.devops"))
+                .apis(RequestHandlerSelectors.basePackage("cn.reghao.devops.web"))
                 //.apis(RequestHandlerSelectors.any())
                 .paths(PathSelectors.any())
                 .build();

+ 21 - 25
web/src/main/resources/templates/rbac/menu/add.html

@@ -8,55 +8,55 @@
 <div class="layui-form timo-compile">
     <form th:action="@{/api/rbac/menu}">
         <div class="layui-form-item">
-            <label class="layui-form-label required">名字</label>
+            <label class="layui-form-label required">菜单类型</label>
             <div class="layui-input-inline">
                 <label>
-                    <input class="layui-input" type="text" name="name"  placeholder="请输入名字">
+                    <select name="type" mo:dict="MENU_TYPE" mo-selected="${menu?.type}" mo-empty="" lay-verify="type"></select>
                 </label>
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label required">URL 地址</label>
+            <label class="layui-form-label required">父级菜单</label>
             <div class="layui-input-inline">
                 <label>
-                    <input class="layui-input url-input" type="text" name="url"  placeholder="请输入URL地址">
+                    <input class="layui-input select-tree" th:attr="data-url=@{/api/rbac/menu/ztree/parent}"
+                           type="text" name="pid"  placeholder="请选择父级菜单">
                 </label>
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label required">父级菜单</label>
+            <label class="layui-form-label required">名字</label>
             <div class="layui-input-inline">
                 <label>
-                    <input class="layui-input select-tree" th:attr="data-url=@{/api/rbac/menu/1}, data-value=${pMenu?.id}"
-                           type="text" name="pid"  placeholder="请选择父级菜单(必须是目录类型)">
+                    <input class="layui-input" type="text" name="name"  placeholder="请输入名字">
                 </label>
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label required">菜单类型</label>
+            <label class="layui-form-label required">URL 地址</label>
             <div class="layui-input-inline">
                 <label>
-                    <select name="type" mo:dict="MENU_TYPE" mo-selected="${menu?.type}" mo-empty="" lay-verify="type"></select>
+                    <input class="layui-input url-input" type="text" name="url"  placeholder="请输入URL地址">
                 </label>
             </div>
         </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label">排序</label>
+        <!--<div class="layui-form-item">
+            <label class="layui-form-label required">排序</label>
             <div class="layui-input-inline">
                 <label>
                     <select class="select-pos" name="pos" lay-verify="pos"
-                            th:attr="data-url=@{/api/rbac/menu/sorted}, data-id=${menu?.id}, data-pos=${menu?.pos}"></select>
+                            th:attr="data-url=@{/api/rbac/menu/sorted}"></select>
                 </label>
             </div>
             <div class="layui-input-info">(之后)</div>
-        </div>
-        <div class="layui-form-item">
+        </div>-->
+        <!--<div class="layui-form-item">
             <label class="layui-form-label required">分配角色</label>
             <div class="layui-input-block">
                 <input th:each="item:${allRoles}" type="checkbox" name="roles" th:title="${item.name}"
                        th:value="${item.id}" th:checked="${#sets.contains(menuRoles, item)}" lay-skin="primary">
             </div>
-        </div>
+        </div>-->
         <div class="layui-form-item timo-finally">
             <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
             <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
@@ -73,34 +73,30 @@
     layui.use(['form'], function () {
         window.form = layui.form;
         // 初始化排序字段下拉选项
-        var pid = $(".select-tree").data('value');
-        if (pid !== undefined){
-            sortRender({id: pid});
-        }
+        // var pid = $(".select-tree").data('value');
+        // if (pid !== undefined){
+        //     sortRender({id: pid});
+        // }
     });
 
     // 初始化下拉树
     $.fn.selectTree({
         rootTree: '根',
         // 选中后事件
-        onSelected: sortRender
+        // onSelected: sortRender
     });
 
     // 重新渲染排序字段的下拉选项
     function sortRender(treeNode) {
         var pid = treeNode.id;
         var pos = $(".select-pos");
-        var id = pos.data('id') ? pos.data('id') : 0;
+        var id = 0;
         var url = pos.data('url') + "/" + pid + "/" + id;
         $.get(url, function (result) {
             result = result.data
             var options = '';
             var posNum = Object.keys(result).length;
-            if(pid === $(".select-tree").data('value') && pos.data('pos')){
-                posNum = pos.data('pos') - 1;
-            }
             result[0] = "首位";
-            // TODO key 应该是 int 类型才对
             for(var key in result){
                 var selected = posNum === parseInt(key) ? "selected=''" : "";
                 options += "<option value='"+ key +"' " + selected + ">"+ result[key] +"</option>";

+ 12 - 30
web/src/main/resources/templates/rbac/menu/edit.html

@@ -6,7 +6,7 @@
 <body>
 <div class="layui-form timo-compile">
     <form th:action="@{/api/rbac/menu/edit}">
-        <input type="hidden" name="menuId" th:value="${menu.menuId}"/>
+        <input type="hidden" name="menuId" th:value="${menu.id}"/>
         <div class="layui-form-item">
             <label class="layui-form-label required">名字</label>
             <div class="layui-input-inline">
@@ -19,12 +19,13 @@
             <label class="layui-form-label required">URL地址</label>
             <div class="layui-input-inline">
                 <label>
-                    <input class="layui-input url-input" type="text" name="url"  placeholder="请输入URL地址" th:value="${menu.url}">
+                    <input class="layui-input url-input" type="text" name="url"  placeholder="请输入URL地址" th:if="${menu.url} eq '#'" th:value="${menu.url}" readonly>
+                    <input class="layui-input url-input" type="text" name="url"  placeholder="请输入URL地址" th:if="${menu.url} ne '#'" th:value="${menu.url}">
                 </label>
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label">菜单图标</label>
+            <label class="layui-form-label required">菜单图标</label>
             <div class="layui-input-inline">
                 <label>
                     <input class="layui-input icon-input" type="text" name="icon"  placeholder="请输入菜单图标" th:value="${menu.icon}">
@@ -32,32 +33,16 @@
             </div>
             <i th:class="'icon-show '+${menu.icon}" style="line-height: 38px;"></i>
         </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label required">父级菜单</label>
-            <div class="layui-input-inline">
-                <label>
-                    <input class="layui-input select-tree" th:attr="data-url=@{/api/rbac/menu/1}, data-value=${pMenu.id}"
-                           type="text" name="pid"  placeholder="请选择父级菜单(必须是目录类型)" th:value="${pMenu?.name}">
-                </label>
-            </div>
-        </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label">排序</label>
+        <!--<div class="layui-form-item">
+            <label class="layui-form-label required">排序</label>
             <div class="layui-input-inline">
                 <label>
                     <select class="select-pos" name="pos" lay-verify="pos"
-                            th:attr="data-url=@{/api/rbac/menu/sorted}, data-id=${menu.menuId}, data-pos=${menu.pos}"></select>
+                            th:attr="data-url=@{/api/rbac/menu/sorted}, data-id=${menu.id}, data-pos=${menu.pos}"></select>
                 </label>
             </div>
             <div class="layui-input-info">(之后)</div>
-        </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label required">分配角色</label>
-            <div class="layui-input-block">
-                <input th:each="item:${allRoles}" type="checkbox" name="roles" th:title="${item.name}"
-                       th:value="${item.id}" th:checked="${#sets.contains(menuRoles, item)}" lay-skin="primary">
-            </div>
-        </div>
+        </div>-->
         <div class="layui-form-item timo-finally">
             <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
             <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
@@ -70,21 +55,19 @@
 <script type="text/javascript" th:src="@{/lib/zTree_v3/js/jquery.ztree.core.min.js}"></script>
 <script type="text/javascript" th:src="@{/js/timoTree.js}"></script>
 
-<script type="text/javascript">
+<script type="text/javascript" th:inline="javascript">
     layui.use(['form'], function () {
         window.form = layui.form;
         // 初始化排序字段下拉选项
-        var pid = $(".select-tree").data('value');
-        if (pid !== undefined){
-            sortRender({id: pid});
-        }
+        // var pid = [[${menu.pid}]]
+        // sortRender({id: pid});
     });
 
     // 初始化下拉树
     $.fn.selectTree({
         rootTree: '根',
         // 选中后事件
-        onSelected: sortRender
+        // onSelected: sortRender
     });
 
     // 重新渲染排序字段的下拉选项
@@ -101,7 +84,6 @@
                 posNum = pos.data('pos') - 1;
             }
             result[0] = "首位";
-            // TODO key 应该是 int 类型才对
             for(var key in result){
                 var selected = posNum === parseInt(key) ? "selected=''" : "";
                 options += "<option value='"+ key +"' " + selected + ">"+ result[key] +"</option>";

+ 2 - 2
web/src/main/resources/templates/rbac/menu/index.html

@@ -5,7 +5,7 @@
         <link rel="stylesheet" th:href="@{/lib/zTree_v3/css/zTreeStyle/zTreeStyle.css}" type="text/css">
     </head>
     <body class="timo-layout-page">
-        <div class="layui-card timo-tree" th:attr="data-url=@{'/api/rbac/menu/' + ${enabled}}">
+        <div class="layui-card timo-tree" th:attr="data-url=@{'/api/rbac/menu/ztree/' + ${enabled}}">
             <div class="layui-card-header timo-card-header">
                 <span>
                     <i class="fa fa-bars"></i>
@@ -65,7 +65,7 @@
                                         </label>
                                     </th>
                                     <th>名称</th>
-                                    <th>URL 地址</th>
+                                    <th>URL</th>
                                     <th>菜单类型</th>
                                     <th>授予的角色</th>
                                     <th>操作</th>

+ 2 - 2
web/src/main/resources/templates/rbac/role/menus.html

@@ -71,12 +71,12 @@
                         pId: item.id,
                         name: "列表"
                     };
-                    if(item.remark === "auth:true"){
+                    if(item.granted){
                         index.checked = true;
                     }
                     zNodes.push(index);
                 }
-                if(item.remark === "auth:true"){
+                if(item.granted){
                     menu.checked = true;
                 }
                 zNodes.push(menu);