Blazor中无需整页刷新更新导航侧边菜单标签的方法
问题描述
我有一个Razor页面,页面右侧展示带有列表项的导航菜单。用户可通过UI修改这些菜单项的标签,其他UI区域的标签会自动刷新,但导航菜单的标签仅在整页刷新时才会更新。整页刷新速度较慢,希望能找到无需整页刷新即可重新加载导航菜单Razor页面的方法。
参考代码
NavMenuTree.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
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
相关产品推荐
相关产品推荐

