diff --git a/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java b/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java index 8745ff7f..107b7abb 100644 --- a/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java +++ b/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java @@ -9,6 +9,7 @@ import lombok.Getter; * * C:目录 * M:菜单 + * E:外链 * B:按钮 */ @Getter @@ -16,6 +17,7 @@ public enum MenuTypeEnum implements IBaseEnum { CATALOG("C", "目录"), MENU("M", "菜单"), + EXTERNAL("E", "外链"), BUTTON("B", "按钮"); /** diff --git a/src/main/java/com/youlai/boot/system/model/entity/Menu.java b/src/main/java/com/youlai/boot/system/model/entity/Menu.java index 4839f40b..f7c0fab4 100644 --- a/src/main/java/com/youlai/boot/system/model/entity/Menu.java +++ b/src/main/java/com/youlai/boot/system/model/entity/Menu.java @@ -29,7 +29,7 @@ public class Menu extends BaseEntity { private String name; /** - * 菜单类型(C-目录 M-菜单 B-按钮) + * 菜单类型(C-目录 M-菜单 E-外链 B-按钮) */ private String type; diff --git a/src/main/java/com/youlai/boot/system/model/form/MenuForm.java b/src/main/java/com/youlai/boot/system/model/form/MenuForm.java index 41cda185..459ab708 100644 --- a/src/main/java/com/youlai/boot/system/model/form/MenuForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/MenuForm.java @@ -26,7 +26,7 @@ public class MenuForm { @Schema(description = "菜单名称") private String name; - @Schema(description = "菜单类型(C-目录 M-菜单 B-按钮)") + @Schema(description = "菜单类型(C-目录 M-菜单 E-外链 B-按钮)") private String type; @Schema(description = "路由名称") diff --git a/src/main/java/com/youlai/boot/system/model/vo/MenuVO.java b/src/main/java/com/youlai/boot/system/model/vo/MenuVO.java index 2353a18e..001865c6 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/MenuVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/MenuVO.java @@ -19,7 +19,7 @@ public class MenuVO { @Schema(description = "菜单名称") private String name; - @Schema(description="菜单类型(C-目录 M-菜单 B-按钮)") + @Schema(description="菜单类型(C-目录 M-菜单 E-外链 B-按钮)") private String type; @Schema(description = "路由名称") diff --git a/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java b/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java index a07ed419..121ca5df 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/RouteVO.java @@ -54,6 +54,10 @@ public class RouteVO { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean alwaysShow; + @Schema(description = "外链地址(内嵌模式用)", example = "https://www.youlai.tech") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String externalUrl; + @Schema(description = "路由参数") private Map params; } diff --git a/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java index 088ae04a..28c581dd 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java @@ -227,36 +227,50 @@ public class MenuServiceImpl extends ServiceImpl implements Me private RouteVO toRouteVo(Menu menu) { RouteVO routeVo = new RouteVO(); String routePath = menu.getRoutePath(); - boolean externalLink = StrUtil.startWithAny(routePath, "http://", "https://"); + String externalUrl = menu.getExternalUrl(); - // 获取路由名称 + // 外链(type=E):component 为空是新标签页,component=iframe 是系统内嵌 + boolean isExternal = MenuTypeEnum.EXTERNAL.getValue().equals(menu.getType()); + boolean isEmbedded = isExternal && "iframe".equals(menu.getComponent()); + + // 新标签页:path 用外链地址;内嵌:path 用内部路由路径 + String path = isExternal && !isEmbedded && StrUtil.isNotBlank(externalUrl) + ? externalUrl + : routePath; + routeVo.setPath(path); + + // 路由名称:外链不设 name(前端 filterRoutes 会过滤出路由注册表) String routeName = menu.getRouteName(); - if (StrUtil.isBlank(routeName)) { - // 外链不做驼峰转换,使用唯一占位,避免 http:// 被解析异常 - routeName = externalLink - ? "ext-" + menu.getId() - : StringUtils.capitalize(StrUtil.toCamelCase(routePath, '-')); + if (StrUtil.isBlank(routeName) && !isExternal) { + routeName = StringUtils.capitalize(StrUtil.toCamelCase(path, '-')); } - // 根据name路由跳转 this.$router.push({name:xxx}) routeVo.setName(routeName); - // 根据path路由跳转 this.$router.push({path:xxx}) - routeVo.setPath(routePath); routeVo.setRedirect(menu.getRedirect()); - // 外链无组件 - routeVo.setComponent(externalLink ? null : menu.getComponent()); + + if (isEmbedded) { + routeVo.setComponent("iframe"); + } else if (isExternal) { + routeVo.setComponent(null); + } else { + routeVo.setComponent(menu.getComponent()); + } RouteVO.Meta meta = new RouteVO.Meta(); meta.setTitle(menu.getName()); meta.setIcon(menu.getIcon()); meta.setHidden(StatusEnum.DISABLE.getValue().equals(menu.getVisible())); - // 【菜单】是否开启页面缓存 - if (MenuTypeEnum.MENU.getValue().equals(menu.getType()) + // 【菜单/内嵌外链】是否开启页面缓存 + if ((MenuTypeEnum.MENU.getValue().equals(menu.getType()) || isEmbedded) && ObjectUtil.equals(menu.getKeepAlive(), 1)) { meta.setKeepAlive(true); } meta.setAlwaysShow(ObjectUtil.equals(menu.getAlwaysShow(), 1)); + if (isEmbedded && StrUtil.isNotBlank(externalUrl)) { + meta.setExternalUrl(externalUrl); + } + Map paramsMap = menu.getParams(); if (paramsMap != null && !paramsMap.isEmpty()) { Map paramMap = paramsMap.entrySet().stream() @@ -275,8 +289,8 @@ public class MenuServiceImpl extends ServiceImpl implements Me public boolean saveMenu(MenuForm menuForm) { String menuType = menuForm.getType(); - boolean isExternalLink = MenuTypeEnum.MENU.getValue().equals(menuType) - && StrUtil.startWithAny(menuForm.getRoutePath(), "http://", "https://"); + boolean isExternal = MenuTypeEnum.EXTERNAL.getValue().equals(menuType); + boolean isEmbedded = isExternal && "iframe".equals(menuForm.getComponent()); if (MenuTypeEnum.CATALOG.getValue().equals(menuType)) { // 如果是目录 String path = menuForm.getRoutePath(); @@ -284,8 +298,8 @@ public class MenuServiceImpl extends ServiceImpl implements Me menuForm.setRoutePath("/" + path); // 一级目录需以 / 开头 } menuForm.setComponent("Layout"); - } else if (isExternalLink) { - // 外链菜单组件设置为 null,通过 routePath 判断外链 + } else if (isExternal && !isEmbedded) { + // 外链新标签页:component 留空 menuForm.setComponent(null); } if (Objects.equals(menuForm.getParentId(), menuForm.getId())) { @@ -302,8 +316,9 @@ public class MenuServiceImpl extends ServiceImpl implements Me } else { entity.setParams(null); } - // 新增类型为菜单时候 路由名称唯一 - if (MenuTypeEnum.MENU.getValue().equals(menuType) && !isExternalLink) { + // 菜单(M)和内嵌外链(E+iframe)需要路由名称唯一 + boolean needsRouteName = MenuTypeEnum.MENU.getValue().equals(menuType) || isEmbedded; + if (needsRouteName) { Assert.isFalse(this.exists(new LambdaQueryWrapper() .eq(Menu::getRouteName, entity.getRouteName()) .ne(menuForm.getId() != null, Menu::getId, menuForm.getId())