From adf09f47539b3e6776792b6f17b1f4eca7c49208 Mon Sep 17 00:00:00 2001 From: dubai Date: Sun, 11 Jan 2026 20:49:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(menu):=20=E6=B7=BB=E5=8A=A0=20Vben5=20?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=9E=84=E5=BB=BA=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=8F=9C=E5=8D=95=E8=BD=AC=E6=8D=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 MenuAggregateRoot 添加 Vben5RouterBuild 扩展方法,支持 Vben5 框架的路由构建 - 在 Vben5RouterBuild 中实现完整的 URL 类型检测和内嵌 iframe 处理逻辑 - 添加对内嵌链接、外部链接和普通路由的不同处理策略 - 优化路由名称生成规则,支持开头大写处理 - 在种子数据中添加示例并注释说明 --- .../Services/AccountService.cs | 4 +- .../Entities/MenuAggregateRoot.cs | 194 +++++++++++++++++- .../DataSeeds/MenuVben5DataSeed.cs | 6 +- 3 files changed, 196 insertions(+), 8 deletions(-) diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/AccountService.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/AccountService.cs index 1350c1eb..a045d6e3 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/AccountService.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/AccountService.cs @@ -417,7 +417,7 @@ namespace Yi.Framework.Rbac.Application.Services { //将后端菜单转换成前端路由,组件级别需要过滤 output = - ObjectMapper.Map, List>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Ruoyi).ToList()).Vue3RuoYiRouterBuild(MenuSourceEnum.Ruoyi); + ObjectMapper.Map, List>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Ruoyi).ToList()).Vue3RuoYiRouterBuild(); } else if (routerType == "pure") { @@ -429,7 +429,7 @@ namespace Yi.Framework.Rbac.Application.Services { //将后端菜单转换成前端路由,组件级别需要过滤 output = - ObjectMapper.Map, List>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Vben5).ToList()).Vue3RuoYiRouterBuild(MenuSourceEnum.Vben5); + ObjectMapper.Map, List>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Vben5).ToList()).Vben5RouterBuild(); } return output; diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/MenuAggregateRoot.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/MenuAggregateRoot.cs index 56f540ee..9ede281b 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/MenuAggregateRoot.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain/Entities/MenuAggregateRoot.cs @@ -1,4 +1,5 @@ -using System.Web; +using System.Text.RegularExpressions; +using System.Web; using NUglify.Helpers; using SqlSugar; using Volo.Abp; @@ -168,12 +169,12 @@ namespace Yi.Framework.Rbac.Domain.Entities /// /// /// - public static List Vue3RuoYiRouterBuild(this List menus,MenuSourceEnum menuSource) + public static List Vue3RuoYiRouterBuild(this List menus) { menus = menus .Where(m => m.State == true) .Where(m => m.MenuType != MenuTypeEnum.Component) - .Where(m => m.MenuSource == menuSource) + .Where(m => m.MenuSource == MenuSourceEnum.Ruoyi) .ToList(); List routers = new(); foreach (var m in menus) @@ -231,7 +232,194 @@ namespace Yi.Framework.Rbac.Domain.Entities return TreeHelper.SetTree(routers); } + /// + /// 构建vue3路由 + /// + /// + /// + public static List Vben5RouterBuild(this List menus) + { + menus = menus + .Where(m => m.State == true) + .Where(m => m.MenuType != MenuTypeEnum.Component) + .Where(m => m.MenuSource == MenuSourceEnum.Vben5) + .ToList(); + List routers = new(); + foreach (var m in menus) + { + var r = new Vue3RouterDto(); + r.OrderNum = m.OrderNum; + r.Id = m.Id; + r.ParentId = m.ParentId; + r.Hidden = !m.IsShow; + // 检测是否为 URL 链接(http:// 或 https:// 开头) + bool isUrl = !string.IsNullOrEmpty(m.Router) && + (m.Router.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + m.Router.StartsWith("https://", StringComparison.OrdinalIgnoreCase)); + + // 判断是否为内嵌 iframe: + // 1. Component 明确设置为 "InnerLink"(优先级最高) + // 2. 或者检测到是 URL 且 isLink = false(自动识别为内嵌) + bool isInnerLink = (!string.IsNullOrEmpty(m.Component) && + m.Component.Equals("InnerLink", StringComparison.OrdinalIgnoreCase)) || + (isUrl && !m.IsLink); + + // 判断是否为外链(新标签页打开): + // 检测到是 URL 且 isLink = true,且不是内嵌 iframe + bool isExternalLink = isUrl && m.IsLink && !isInnerLink; + + // 生成路由名称 + string routerName; + if (isInnerLink) + { + // 内嵌 iframe:从 path 或 router 中提取名称 + routerName = m.Router?.Split("/").LastOrDefault() ?? "InnerLink"; + } + else if (isExternalLink) + { + // 外链:从 URL 中提取名称 + try + { + var uri = new Uri(m.Router!); + routerName = uri.Host.Replace(".", "").Replace("-", ""); + } + catch + { + // 如果 URL 格式不正确,使用默认名称 + routerName = "ExternalLink"; + } + } + else + { + // 普通路由:从 router 中提取名称 + routerName = m.Router?.Split("/").LastOrDefault() ?? string.Empty; + } + + // 开头大写处理 + if (string.IsNullOrEmpty(routerName)) + { + r.Name = routerName; + } + else if (routerName.Length == 1) + { + r.Name = routerName.ToUpper(); + } + else + { + r.Name = routerName.First().ToString().ToUpper() + routerName.Substring(1); + } + + // 设置路径 + r.Path = m.Router ?? string.Empty; + + // 处理内嵌 iframe 场景(优先级最高) + // 触发条件:Component = "InnerLink" 或 (检测到 URL 且 isLink = false) + if (isInnerLink) + { + // 内嵌 iframe:component 为 InnerLink,meta.link 包含完整 iframe 地址 + r.Redirect = "noRedirect"; + r.AlwaysShow = false; + r.Component = "InnerLink"; + + // meta.link 应该包含完整的 iframe 地址,优先使用 Router + string iframeUrl = !string.IsNullOrEmpty(m.Router) ? m.Router : m.Component ?? string.Empty; + + // 清理 path:去除协议和特殊字符,避免前端路由拼接时出现问题 + string cleanedPath = m.Router ?? m.Component ?? string.Empty; + if (!string.IsNullOrEmpty(cleanedPath)) + { + // 去除 http:// 或 https:// + cleanedPath = Regex.Replace(cleanedPath, @"^https?://", "", RegexOptions.IgnoreCase); + // 去除 /#/ + cleanedPath = cleanedPath.Replace("/#/", ""); + // 去除 # + cleanedPath = cleanedPath.Replace("#", ""); + // 去除 ? 和 & + cleanedPath = cleanedPath.Replace("?", "").Replace("&", ""); + } + + // 使用清理后的 path,用于前端路由匹配 + r.Path = cleanedPath; + + r.Meta = new Meta + { + Title = m.MenuName!, + Icon = m.MenuIcon ?? string.Empty, + NoCache = !m.IsCache, + link = iframeUrl // meta.link 保持完整的 URL,用于 iframe 加载 + }; + } + // 处理外链场景(新标签页打开) + // 触发条件:检测到 URL 且 isLink = true + else if (isExternalLink) + { + // 外链:path 保持原样,component 为 Layout 或 ParentView,meta.link 包含完整外链地址 + r.Redirect = "noRedirect"; + r.AlwaysShow = false; + + // 判断是否为最顶层的路由 + if (Guid.Empty == m.ParentId) + { + r.Component = "Layout"; + } + else + { + r.Component = "ParentView"; + } + + r.Meta = new Meta + { + Title = m.MenuName!, + Icon = m.MenuIcon ?? string.Empty, + NoCache = !m.IsCache, + link = m.Router! // 完整的外链地址 + }; + } + // 处理普通路由菜单 + else + { + if (m.MenuType == MenuTypeEnum.Catalogue) + { + r.Redirect = "noRedirect"; + r.AlwaysShow = true; + + // 判断是否为最顶层的路由 + if (Guid.Empty == m.ParentId) + { + r.Component = "Layout"; + } + else + { + r.Component = "ParentView"; + } + } + else if (m.MenuType == MenuTypeEnum.Menu) + { + r.Redirect = "noRedirect"; + r.AlwaysShow = false; + r.Component = m.Component ?? string.Empty; + } + + r.Meta = new Meta + { + Title = m.MenuName!, + Icon = m.MenuIcon ?? string.Empty, + NoCache = !m.IsCache + }; + + // 如果 IsLink 为 true 但不是外链,则可能是其他类型的链接 + if (m.IsLink && !string.IsNullOrEmpty(m.Router)) + { + r.Meta.link = m.Router; + } + } + + routers.Add(r); + } + + return TreeHelper.SetTree(routers); + } /// /// 构建vue3 pure路由 diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/DataSeeds/MenuVben5DataSeed.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/DataSeeds/MenuVben5DataSeed.cs index 4fff3492..f6437dd4 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/DataSeeds/MenuVben5DataSeed.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/DataSeeds/MenuVben5DataSeed.cs @@ -184,7 +184,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds { MenuName = "定时任务", MenuType = MenuTypeEnum.Menu, - Router = "http://127.0.0.1:19002/hangfire", + Router = "http://127.0.0.1:19001/hangfire", IsShow = true, IsLink = true, MenuIcon = "tabler:calendar-clock", @@ -212,9 +212,9 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds { MenuName = "接口文档", MenuType = MenuTypeEnum.Menu, - Router = "http://127.0.0.1:19002/swagger", + Router = "http://127.0.0.1:19001/swagger", IsShow = true, - IsLink = true, + IsLink = false, // Vben5RouterBuild方法会基于Router属性判断是否为链接,如果是再根据IsLink字段判断是内嵌还是跳转 MenuIcon = "devicon:swagger", OrderNum = 100, IsDeleted = false,