You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过2sxc应用C# Razor模板输出DNN页面替代DDR Menu实现自定义菜单

可行性确认

该需求完全可以实现。2sxc原生支持调用DNN核心页面API读取页面数据,同时支持自定义内容类型存储非DNN页面的巨型菜单内容(比如宣传卡片、分类导航、产品入口等),可以完全覆盖DDR Menu的原有能力,还能实现更高自由度的菜单定制。

具体操作步骤
  • 第一步:创建2sxc应用与自定义内容类型
    新建名为「CustomNavigation」的2sxc应用,新增两个自定义内容类型:
    • MenuCustomItem:存储非DNN页面的自定义菜单项,字段包含:项名称、跳转链接、打开方式、上级项ID、排序值、是否显示、关联媒体资源(用于巨型菜单的图片/banner)
    • MegaMenuBlock:存储巨型菜单的自定义区块内容,字段包含:关联父菜单ID、区块标题、区块内容、排序值、是否启用
  • 第二步:配置DNN接口调用权限
    在2sxc应用的代码配置中启用DNN核心API调用权限,确保Razor模板可以正常读取DotNetNuke.Entities.Tabs命名空间下的页面数据
  • 第三步:对齐DDR Menu渲染逻辑
    参照DDR Menu默认过滤规则,过滤已删除、隐藏、权限不足的页面,同时支持按页面层级渲染嵌套菜单结构
  • 第四步:合并自定义内容与原生页面数据
    渲染前将DNN原生页面数据和自定义菜单项按层级、排序值合并,支持将自定义项插入到任意菜单层级
示例C# Razor代码
@inherits ToSic.Sxc.Dnn.RazorComponent
@using DotNetNuke.Entities.Tabs;
@using DotNetNuke.Security.Permissions;

@{
    // 1. 获取当前门户所有权限可见的有效页面
    var portalId = Dnn.Portal.PortalId;
    var allDnnPages = TabController.Instance.GetTabsByPortal(portalId).AsList()
        .Where(t => !t.IsDeleted && t.IsVisible && !t.DisableLink && TabPermissionController.CanViewPage(t))
        .OrderBy(t => t.Level).ThenBy(t => t.TabOrder)
        .ToList();
    
    // 2. 获取2sxc中存储的自定义菜单项、巨型菜单内容
    var customMenuItems = AsList(App.Data["MenuCustomItem"]).Where(i => i.Show == true).OrderBy(i => i.Sort).ToList();
    var megaMenuBlocks = AsList(App.Data["MegaMenuBlock"]).Where(b => b.Enable == true).OrderBy(b => b.Sort).ToList();

    // 3. 递归渲染菜单层级的方法
    Func<int, IHtmlString> RenderMenuLevel = null;
    RenderMenuLevel = (parentId) => {
        // 合并当前层级的DNN页面与自定义菜单项
        var levelItems = allDnnPages.Where(p => p.ParentId == parentId).Cast<dynamic>()
            .Concat(customMenuItems.Where(c => c.ParentId == parentId))
            .OrderBy(i => i.Sort ?? i.TabOrder)
            .ToList();
        
        if (!levelItems.Any()) return new HtmlString("");
        
        var html = new StringBuilder($"<ul class=\"nav-level-{parentId}\">");
        foreach (var item in levelItems)
        {
            var isActive = item.TabID == Dnn.Tab.TabID;
            var itemUrl = item.Url ?? DotNetNuke.Common.Globals.NavigateURL(item.TabID);
            var itemTarget = item.Target ?? "_self";
            var hasMegaMenu = megaMenuBlocks.Any(b => b.RelatedParentMenuId == (item.TabID ?? item.Id));
            
            html.Append($"<li class=\"nav-item {(isActive ? "active" : "")} {(hasMegaMenu ? "has-mega-menu" : "")}\">");
            html.Append($"<a href=\"{itemUrl}\" target=\"{itemTarget}\">{item.MenuName ?? item.TabName}</a>");
            
            // 渲染关联的巨型菜单内容
            if (hasMegaMenu)
            {
                var blocks = megaMenuBlocks.Where(b => b.RelatedParentMenuId == (item.TabID ?? item.Id)).ToList();
                html.Append("<div class=\"mega-menu\">");
                foreach (var block in blocks)
                {
                    html.Append("<div class=\"mega-menu-block\">");
                    if (!string.IsNullOrEmpty(block.Title)) html.Append($"<h4>{block.Title}</h4>");
                    html.Append(block.Content);
                    if (block.Image != null) html.Append($"<img src=\"{block.Image.Url}\" alt=\"{block.Title}\">");
                    html.Append("</div>");
                }
                html.Append("</div>");
            }
            
            // 递归渲染子菜单
            html.Append(RenderMenuLevel(item.TabID ?? item.Id));
            html.Append("</li>");
        }
        html.Append("</ul>");
        return new HtmlString(html.ToString());
    };
}

<!-- 最终输出菜单 -->
<nav class="custom-main-nav">
    @RenderMenuLevel(-1)
</nav>
补充说明
  • 代码中的CSS类名可以根据你的前端样式需求自由调整,如果需要完全对齐原有DDR Menu的输出结构,直接修改RenderMenuLevel方法中的HTML输出规则即可
  • 自定义菜单项、巨型菜单的内容、排序、显隐都可以直接在2sxc的可视化管理界面操作,无需修改代码
  • 权限校验逻辑可以根据业务需求调整,也可以新增对自定义菜单项的权限控制规则

内容的提问来源于stack exchange,提问作者Aaron - Wolf X Machina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 22:48:04