feat(menu): 添加 Vben5 路由构建功能并优化菜单转换逻辑
- 为 MenuAggregateRoot 添加 Vben5RouterBuild 扩展方法,支持 Vben5 框架的路由构建 - 在 Vben5RouterBuild 中实现完整的 URL 类型检测和内嵌 iframe 处理逻辑 - 添加对内嵌链接、外部链接和普通路由的不同处理策略 - 优化路由名称生成规则,支持开头大写处理 - 在种子数据中添加示例并注释说明
This commit is contained in:
@@ -417,7 +417,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
{
|
{
|
||||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||||
output =
|
output =
|
||||||
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Ruoyi).ToList()).Vue3RuoYiRouterBuild(MenuSourceEnum.Ruoyi);
|
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Ruoyi).ToList()).Vue3RuoYiRouterBuild();
|
||||||
}
|
}
|
||||||
else if (routerType == "pure")
|
else if (routerType == "pure")
|
||||||
{
|
{
|
||||||
@@ -429,7 +429,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
{
|
{
|
||||||
//将后端菜单转换成前端路由,组件级别需要过滤
|
//将后端菜单转换成前端路由,组件级别需要过滤
|
||||||
output =
|
output =
|
||||||
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Vben5).ToList()).Vue3RuoYiRouterBuild(MenuSourceEnum.Vben5);
|
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Vben5).ToList()).Vben5RouterBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Web;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Web;
|
||||||
using NUglify.Helpers;
|
using NUglify.Helpers;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
@@ -168,12 +169,12 @@ namespace Yi.Framework.Rbac.Domain.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="menus"></param>
|
/// <param name="menus"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static List<Vue3RouterDto> Vue3RuoYiRouterBuild(this List<MenuAggregateRoot> menus,MenuSourceEnum menuSource)
|
public static List<Vue3RouterDto> Vue3RuoYiRouterBuild(this List<MenuAggregateRoot> menus)
|
||||||
{
|
{
|
||||||
menus = menus
|
menus = menus
|
||||||
.Where(m => m.State == true)
|
.Where(m => m.State == true)
|
||||||
.Where(m => m.MenuType != MenuTypeEnum.Component)
|
.Where(m => m.MenuType != MenuTypeEnum.Component)
|
||||||
.Where(m => m.MenuSource == menuSource)
|
.Where(m => m.MenuSource == MenuSourceEnum.Ruoyi)
|
||||||
.ToList();
|
.ToList();
|
||||||
List<Vue3RouterDto> routers = new();
|
List<Vue3RouterDto> routers = new();
|
||||||
foreach (var m in menus)
|
foreach (var m in menus)
|
||||||
@@ -231,7 +232,194 @@ namespace Yi.Framework.Rbac.Domain.Entities
|
|||||||
|
|
||||||
return TreeHelper.SetTree(routers);
|
return TreeHelper.SetTree(routers);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 构建vue3路由
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="menus"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static List<Vue3RouterDto> Vben5RouterBuild(this List<MenuAggregateRoot> menus)
|
||||||
|
{
|
||||||
|
menus = menus
|
||||||
|
.Where(m => m.State == true)
|
||||||
|
.Where(m => m.MenuType != MenuTypeEnum.Component)
|
||||||
|
.Where(m => m.MenuSource == MenuSourceEnum.Vben5)
|
||||||
|
.ToList();
|
||||||
|
List<Vue3RouterDto> 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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建vue3 pure路由
|
/// 构建vue3 pure路由
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
|
|||||||
{
|
{
|
||||||
MenuName = "定时任务",
|
MenuName = "定时任务",
|
||||||
MenuType = MenuTypeEnum.Menu,
|
MenuType = MenuTypeEnum.Menu,
|
||||||
Router = "http://127.0.0.1:19002/hangfire",
|
Router = "http://127.0.0.1:19001/hangfire",
|
||||||
IsShow = true,
|
IsShow = true,
|
||||||
IsLink = true,
|
IsLink = true,
|
||||||
MenuIcon = "tabler:calendar-clock",
|
MenuIcon = "tabler:calendar-clock",
|
||||||
@@ -212,9 +212,9 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
|
|||||||
{
|
{
|
||||||
MenuName = "接口文档",
|
MenuName = "接口文档",
|
||||||
MenuType = MenuTypeEnum.Menu,
|
MenuType = MenuTypeEnum.Menu,
|
||||||
Router = "http://127.0.0.1:19002/swagger",
|
Router = "http://127.0.0.1:19001/swagger",
|
||||||
IsShow = true,
|
IsShow = true,
|
||||||
IsLink = true,
|
IsLink = false, // Vben5RouterBuild方法会基于Router属性判断是否为链接,如果是再根据IsLink字段判断是内嵌还是跳转
|
||||||
MenuIcon = "devicon:swagger",
|
MenuIcon = "devicon:swagger",
|
||||||
OrderNum = 100,
|
OrderNum = 100,
|
||||||
IsDeleted = false,
|
IsDeleted = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user