diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java index 14a2f985..de1c6f58 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/permission/service/PermissionMenuService.java @@ -85,7 +85,7 @@ public class PermissionMenuService { entity.setParentId(parentId); entity.setMenuType(menuType); entity.setRoutePath(routePath); - entity.setSortOrder(resolveSortOrder(request.getSortOrder(), null)); + entity.setSortOrder(resolveSortOrder(menuType, request.getSortOrder(), null)); permissionMenuMapper.insert(entity); return toItemVo(getMenuById(entity.getId())); } @@ -118,7 +118,7 @@ public class PermissionMenuService { entity.setParentId(parentId); entity.setMenuType(menuType); entity.setRoutePath(routePath); - entity.setSortOrder(resolveSortOrder(request.getSortOrder(), id)); + entity.setSortOrder(resolveSortOrder(menuType, request.getSortOrder(), id)); permissionMenuMapper.updateById(entity); return toItemVo(getMenuById(id)); } @@ -410,12 +410,7 @@ public class PermissionMenuService { } private List loadMenus(String menuType) { - String normalizedType = normalizeMenuType(menuType); - LambdaQueryWrapper query = new LambdaQueryWrapper() - .eq(normalizedType != null, PermissionMenuEntity::getMenuType, normalizedType) - .orderByAsc(PermissionMenuEntity::getSortOrder) - .orderByAsc(PermissionMenuEntity::getId); - List rows = permissionMenuMapper.selectList(query); + List rows = loadRawMenus(menuType); if (rows == null || rows.isEmpty()) { return List.of(); } @@ -425,6 +420,20 @@ public class PermissionMenuService { .toList(); } + /** + * Fetches every menu (optionally scoped to one type) in insertion order. + * Sorting happens in {@link #loadMenus(String)} and only for hierarchies + * that actually need one, keeping raw SQL and comparator semantics in sync. + */ + private List loadRawMenus(String menuType) { + String normalizedType = normalizeMenuType(menuType); + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(normalizedType != null, PermissionMenuEntity::getMenuType, normalizedType) + .orderByAsc(PermissionMenuEntity::getSortOrder) + .orderByAsc(PermissionMenuEntity::getId); + return permissionMenuMapper.selectList(query); + } + private List loadDirectColumnIds(Long userId) { List rows = userColumnPermissionMapper.selectByUserId(userId); if (rows == null || rows.isEmpty()) { @@ -751,12 +760,30 @@ public class PermissionMenuService { return new ArrayList<>(uniqueIds); } + /** app 分组排在 admin 分组之前;组内按 sort_order, id 排。 */ private Comparator menuComparator() { return Comparator - .comparing(PermissionMenuEntity::getSortOrder, Comparator.nullsLast(Integer::compareTo)) + .comparing(PermissionMenuEntity::getMenuType, Comparator.nullsLast(menuTypeRank())) + .thenComparing(PermissionMenuEntity::getSortOrder, Comparator.nullsLast(Integer::compareTo)) .thenComparing(PermissionMenuEntity::getId, Comparator.nullsLast(Long::compareTo)); } + private Comparator menuTypeRank() { + return Comparator.comparing(this::menuTypePriority); + } + + /** App 菜单优先展示在 admin 菜单之前。 */ + private int menuTypePriority(String menuType) { + if (MENU_TYPE_APP.equals(menuType)) { + return 0; + } + if (MENU_TYPE_ADMIN.equals(menuType)) { + return 1; + } + // Internal/legacy rows stay out of the way of both user-facing groups. + return 2; + } + private List toItemVos(List visibleMenus, List hierarchyMenus) { Map menuMap = hierarchyMenus.stream() @@ -800,11 +827,18 @@ public class PermissionMenuService { return root.getColumnKey(); } - private Integer resolveSortOrder(Integer sortOrder, Long excludeId) { + /** + * Resolves the default sort order when none is supplied, scoped to the + * menu's own type (parent takes precedence, then hierarchy ancestors). + * The DB query mirrors {@link #loadMenus(String)} ordering so the last row + * is the true tail of the type's ordering. + */ + private Integer resolveSortOrder(String menuType, Integer sortOrder, Long excludeId) { if (sortOrder != null) { return sortOrder; } PermissionMenuEntity tail = permissionMenuMapper.selectOne(new LambdaQueryWrapper() + .eq(PermissionMenuEntity::getMenuType, normalizeMenuType(menuType)) .ne(excludeId != null, PermissionMenuEntity::getId, excludeId) .orderByDesc(PermissionMenuEntity::getSortOrder) .orderByDesc(PermissionMenuEntity::getId)