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

Blazor中无需整页刷新更新导航侧边菜单标签的方法

问题描述

我有一个Razor页面,页面右侧展示带有列表项的导航菜单。用户可通过UI修改这些菜单项的标签,其他UI区域的标签会自动刷新,但导航菜单的标签仅在整页刷新时才会更新。整页刷新速度较慢,希望能找到无需整页刷新即可重新加载导航菜单Razor页面的方法。


参考代码

NavMenuTree.razor

<AuthorizeView Context="Home">
    <Authorized>
       
        <div class="k-card-header mb-3">
            <span class="tb-icon-container">
                <TelerikSvgIcon Icon="@SvgIcon.Search" Size="@size" />
                <TelerikTextBox Value="@FilteredValue" ValueChanged="@((string input) => ChangeValues(input))" Id="filter-value" Size="@size" Rounded="@size" Placeholder="@MOM.UI.Client.Constants.Common.SearchBox" />
            </span>
        </div> 
        <div class="menu">

            <TelerikTreeView @ref="treeView" Data="@MenuData" Size="@size" OnItemClick="OnTreeViewItemClick"
                             SelectionMode="TreeViewSelectionMode.Single"
                             @bind-SelectedItems="@SelectedItems"
                             @bind-ExpandedItems="@ExpandedItems">
                <TreeViewBindings>
                    <TreeViewBinding TextField="Text" IconField="Icon" IdField="Id" ParentIdField="ParentId" UrlField="Route">
                        <ItemTemplate>
                            @{
                                MenuItem itm = context as MenuItem;
                                if (itm?.Type == "Screen")
                                {
                                    <a href="@itm.Route" class="no-decoration">
                                        @if (!string.IsNullOrEmpty(itm.Icon))
                                        {
                                            <span class="@itm.Icon"></span>
                                        }
                                        @itm.Text
                                    </a>
                                }
                                else
                                {
                                    @if (!string.IsNullOrEmpty(itm?.Icon))
                                    {
                                        <span class="@itm.Icon"></span>
                                    }
                                    @itm?.Text
                                }
                            }
                        </ItemTemplate>
                    </TreeViewBinding>
                </TreeViewBindings>
            </TelerikTreeView>
        </div>
    </Authorized>
</AuthorizeView>


@code {


    [CascadingParameter]
    public Task<AuthenticationState>? AuthenticationState { get; set; }
    [CascadingParameter(Name = "size")]
    public string? size { get; set; }
    public bool firstTimeLoad = true;

    public TelerikTreeView? treeView { get; set; }
    private IEnumerable<object>? ExpandedItems { get; set; } = new List<object>();
    private IEnumerable<object>? SelectedItems { get; set; } = new List<object>();
    private IEnumerable<MenuItem>? MenuData { get; set; }
    private IEnumerable<MenuItem>? AllData { get; set; }

    public string FilteredValue { get; set; }

    public AuthenticationState? authState;
    public System.Security.Claims.Claim? authClaim;
    public System.Security.Claims.Claim? entityActionClaim;
    private ClaimsPrincipal? AuthenticationStateUser { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            AuthenticationState? authenticationState;

            authenticationState = await AuthenticationState;
            this.AuthenticationStateUser = authenticationState.User;

            if (MenuData == null)
            {
                List<MenuItem> result = (from Menu in MenuItem.GetMenuItems()
                                         from Perm in AuthenticationStateUser.Claims
                                     where ((Perm.Value.Contains(Menu.AuthorizationKey) && Perm.Type == EntityAction._contextType && Menu.Type == "Screen") ||
                                     (Perm.Value.Contains(Menu.AuthorizationKey) && Perm.Type == Module._contextType && Menu.Type == "Menu"))
                                         orderby Menu.ParentId, Menu.Rank
                                         select Menu).ToList();

                result.RemoveAll(x => x.Type == "Menu" && !result.Any(y => y.ParentId == x.Id));
                result.Add(
                new MenuItem
                {
                    Id = 25,
                    Text = "Logout",
                    Icon = @"fa-solid fa-sign-out fa-sm",
                    Type = "Screen",
                    HasChildren = false,
                    level = 1,
                    Rank = 14,
                    Route = "logout",
                    AuthorizationKey = null,
                });
                MenuData = result.AsEnumerable();
                AllData = result.AsEnumerable();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }

    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        try
        {
            if (treeView != null)
            {
                if (ExpandedItems?.Count() == 0)
                {
                    if (_navMan.BaseUri != _navMan.Uri)
                    {
                        string selectedRoute = _navMan.Uri.Replace(_navMan.BaseUri, "/");

                        var selectedItem = MenuData?.FirstOrDefault(x => x.Type == "Screen" && selectedRoute.ToLower().Contains(x.Route.ToLower()));
                        if (selectedItem != null)
                        {
                            SelectedItems = new List<object>() { selectedItem };
                            while (selectedItem != null)
                            {
                                ExpandedItems = ExpandedItems.Concat(new[] { MenuData.FirstOrDefault(x => x.Type == "Menu" && x.Id == selectedItem.ParentId) });
                                selectedItem = MenuData.FirstOrDefault(x => x.Type == "Menu" && x.Id == selectedItem.ParentId);
                            }
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        await base.OnAfterRenderAsync(firstRender);
    }
    protected override void OnInitialized()
    {
        firstTimeLoad = true;
        base.OnInitialized();
    }

}

MenuHelper.cs

using MOM.Model.Setup;

namespace MOM.UI.Client.Helper
{
    public static class UserLabelHelper
    {
        private static List<LabelDTO> _labels { get; set; }

        public static void SetUserLabel(string type, string label)
        {
            try
            {
                if (_labels.Any(x => x.Type == type))
                {
                    _labels.FirstOrDefault(x => x.Type == type).UserLabel = label;
                }

            }
            catch (System.Exception ex)
            {
                throw ex;
            }
        }
    }
}

解决方案

核心思路是让导航菜单组件感知标签变化,触发数据重载和局部渲染,无需整页刷新。具体步骤如下:

1. 给UserLabelHelper添加标签变化通知

当前SetUserLabel仅修改数据,无法通知UI更新,添加事件触发机制:

using MOM.Model.Setup;

namespace MOM.UI.Client.Helper
{
    public static class UserLabelHelper
    {
        private static List<LabelDTO> _labels { get; set; }
        // 定义标签更新事件
        public static event Action? OnLabelsUpdated;

        public static void SetUserLabel(string type, string label)
        {
            try
            {
                if (_labels.Any(x => x.Type == type))
                {
                    _labels.FirstOrDefault(x => x.Type == type).UserLabel = label;
                    // 触发更新通知
                    OnLabelsUpdated?.Invoke();
                }

            }
            catch (System.Exception ex)
            {
                throw ex;
            }
        }
    }
}

2. 在NavMenuTree组件中订阅事件并刷新数据

修改组件代码,订阅标签变化事件,触发菜单数据重载和局部渲染:

@code {
    // 注入NavigationManager(如果未添加)
    [Inject]
    private NavigationManager _navMan { get; set; }

    private List<int>? _savedSelectedIds;
    private List<int>? _savedExpandedIds;

    protected override void OnInitialized()
    {
        firstTimeLoad = true;
        // 订阅标签更新事件
        UserLabelHelper.OnLabelsUpdated += RefreshMenu;
        base.OnInitialized();
    }

    // 组件销毁时取消订阅,避免内存泄漏
    public void Dispose()
    {
        UserLabelHelper.OnLabelsUpdated -= RefreshMenu;
    }

    private void RefreshMenu()
    {
        // 保存当前选中和展开项的ID,避免刷新后丢失状态
        _savedSelectedIds = SelectedItems?.Cast<MenuItem>().Select(x => x.Id).ToList();
        _savedExpandedIds = ExpandedItems?.Cast<MenuItem>().Select(x => x.Id).ToList();

        // 重置MenuData,触发OnParametersSetAsync重新加载数据
        MenuData = null;
        // 强制组件局部渲染
        StateHasChanged();
    }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            // 原有加载菜单数据的逻辑...

            // 恢复之前保存的选中和展开状态
            if (_savedSelectedIds != null && _savedExpandedIds != null)
            {
                SelectedItems = MenuData?.Where(x => _savedSelectedIds.Contains(x.Id)).Cast<object>().ToList();
                ExpandedItems = MenuData?.Where(x => _savedExpandedIds.Contains(x.Id)).Cast<object>().ToList();
                
                // 清空临时保存的ID
                _savedSelectedIds = null;
                _savedExpandedIds = null;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    // 保留原有其他代码...
}

3. 确保MenuItem.GetMenuItems()使用最新标签

修改MenuItem.GetMenuItems()方法,从UserLabelHelper中读取用户自定义标签替换默认文本:

public static List<MenuItem> GetMenuItems()
{
    // 原有获取默认菜单数据的逻辑
    var menuItems = /* 你的默认菜单获取代码 */;

    // 遍历菜单,替换为用户自定义标签
    foreach (var item in menuItems)
    {
        var customLabel = UserLabelHelper._labels.FirstOrDefault(l => l.Type == item.Type);
        if (customLabel != null && !string.IsNullOrEmpty(customLabel.UserLabel))
        {
            item.Text = customLabel.UserLabel;
        }
    }

    return menuItems;
}

内容的提问来源于stack exchange,提问作者Prashil Shah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:35:01