Files
qhmes/yy-admin-master/YY.Admin/ViewModels/Control/MenuTreeViewModel.cs

475 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Threading;
using YY.Admin.Core;
using YY.Admin.Core.Util;
using YY.Admin.Event;
using Prism.Events;
using YY.Admin.Module;
using YY.Admin.Services;
using YY.Admin.Services.Service.Menu;
namespace YY.Admin.ViewModels.Control
{
/// <summary>
/// 菜单选项
/// </summary>
public class MenuItem : TabSource
{
public MenuItem? Parent { get; set; } // 父节点引用
public ObservableCollection<MenuItem> Children { get; set; } = [];
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
}
public class MenuTreeViewModel : BaseViewModel
{
private static readonly Dictionary<string, string> RouteToViewMap = new(StringComparer.OrdinalIgnoreCase)
{
// 已实现页面:仪表盘
["DashboardView"] = "DashboardView",
["/dashboard"] = "DashboardView",
["/dashboard/index"] = "DashboardView",
["/home/index"] = "DashboardView",
["dashboard"] = "DashboardView",
["home"] = "DashboardView",
// 已实现页面:账号管理
["UserManagementView"] = "UserManagementView",
["/system/user"] = "UserManagementView",
["/system/user/index"] = "UserManagementView",
["sysUser"] = "UserManagementView",
// 已实现页面:数据字典
["DataDictionaryManagementView"] = "DataDictionaryManagementView",
["/system/dict"] = "DataDictionaryManagementView",
["/system/dict/index"] = "DataDictionaryManagementView",
["/platform/dict"] = "DataDictionaryManagementView",
["sysDict"] = "DataDictionaryManagementView",
// 已实现页面:分类字典
["CategoryDictionaryManagementView"] = "CategoryDictionaryManagementView",
["/system/category"] = "CategoryDictionaryManagementView",
["/system/category/index"] = "CategoryDictionaryManagementView",
["/platform/category"] = "CategoryDictionaryManagementView",
["sysCategory"] = "CategoryDictionaryManagementView",
// 已实现页面:角色管理
["RoleManagementView"] = "RoleManagementView",
["/system/role"] = "RoleManagementView",
["/system/role/index"] = "RoleManagementView",
["sysRole"] = "RoleManagementView",
// 已实现页面:租户管理
["TenantManagementView"] = "TenantManagementView",
["/system/tenant"] = "TenantManagementView",
["/system/tenant/index"] = "TenantManagementView",
["/platform/tenant"] = "TenantManagementView",
["sysTenant"] = "TenantManagementView",
// 已实现页面:菜单管理
["MenuManagementView"] = "MenuManagementView",
["/platform/menu"] = "MenuManagementView",
["/system/menu/index"] = "MenuManagementView",
["sysMenu"] = "MenuManagementView",
// 已实现页面:登录设置
["LoginSettingsView"] = "LoginSettingsView",
["/system/loginSettings"] = "LoginSettingsView",
["/system/login-setting"] = "LoginSettingsView",
["/system/login-settings"] = "LoginSettingsView",
["loginSettings"] = "LoginSettingsView",
["登录设置"] = "LoginSettingsView",
["登陆设置"] = "LoginSettingsView",
// 已实现页面:车辆管理
["VehicleListView"] = "VehicleListView",
["/xslmes/mesXslVehicle"] = "VehicleListView",
["/xslmes/vehicle"] = "VehicleListView",
["mesXslVehicle"] = "VehicleListView",
// 已实现页面:客户管理
["CustomerListView"] = "CustomerListView",
["/xslmes/mesXslCustomer"] = "CustomerListView",
["/xslmes/customer"] = "CustomerListView",
["mesXslCustomer"] = "CustomerListView",
// 已实现页面:供应商管理
["SupplierListView"] = "SupplierListView",
["/xslmes/mesXslSupplier"] = "SupplierListView",
["/xslmes/supplier"] = "SupplierListView",
["mesXslSupplier"] = "SupplierListView",
// 已实现页面:磅单记录管理
["WeightRecordListView"] = "WeightRecordListView",
["/xslmes/mesXslWeightRecord"] = "WeightRecordListView",
["mesXslWeightRecord"] = "WeightRecordListView",
// 已实现页面:地磅称重操作
["WeightRecordOperationView"] = "WeightRecordOperationView",
["/xslmes/weightRecordOperation"] = "WeightRecordOperationView",
["weightRecordOperation"] = "WeightRecordOperationView",
// 已实现页面:密炼物料信息
["MixerMaterialListView"] = "MixerMaterialListView",
["/xslmes/mesMixerMaterial"] = "MixerMaterialListView",
["mesMixerMaterial"] = "MixerMaterialListView",
// 已实现页面:原料入场记录
["RawMaterialEntryListView"] = "RawMaterialEntryListView",
["/xslmes/mesXslRawMaterialEntry"] = "RawMaterialEntryListView",
["mesXslRawMaterialEntry"] = "RawMaterialEntryListView",
// 已实现页面:新增原料入场记录
["RawMaterialEntryOperationView"] = "RawMaterialEntryOperationView",
["/xslmes/rawMaterialEntryOperation"] = "RawMaterialEntryOperationView",
["rawMaterialEntryOperation"] = "RawMaterialEntryOperationView"
};
private MenuItem? _selectedMenuItem;
public MenuItem? SelectedMenuItem
{
get => _selectedMenuItem;
set => SetProperty(ref _selectedMenuItem, value);
}
private readonly ISysMenuService _sysMenuService;
public ObservableCollection<MenuItem> MenuItems { get; } = [];
public DelegateCommand<MenuItem> NavigateCommand { get; }
private SubscriptionToken? tabSelectedToken;
private SubscriptionToken? tabClosedToken;
private SubscriptionToken? _menuStructureToken;
public MenuTreeViewModel(
ISysMenuService sysMenuService,
IContainerExtension _container,
IRegionManager regionManager) : base(_container, regionManager)
{
_sysMenuService = sysMenuService;
// 异步初始化菜单
LoadMenuAsync();
NavigateCommand = new DelegateCommand<MenuItem>(OpenOrActivateTab);
// 订阅事件
tabSelectedToken = _eventAggregator.GetEvent<TabSelectedEvent>().Subscribe(OnTabSelected);
tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed);
_menuStructureToken = _eventAggregator.GetEvent<MenuStructureChangedEvent>()
.Subscribe(async _ => await ReloadMenusAsync(), ThreadOption.UIThread);
}
/// <summary>
/// 菜单数据变更后刷新左侧树(不重复执行默认页签导航)
/// </summary>
private async Task ReloadMenusAsync()
{
try
{
var menuTree = await _sysMenuService.GetLoginMenuTree();
MenuItems.Clear();
ConvertMenuTreeToViewModel(menuTree);
}
catch (Exception ex)
{
_logger.Error($"菜单重新加载失败: {ex.Message}", ex);
}
}
public void OpenOrActivateTab(MenuItem menuItem)
{
_logger.Debug($"菜单点击触发: Name={menuItem?.Name}, ViewName={menuItem?.ViewName}, ChildrenCount={menuItem?.Children?.Count ?? 0}");
// 发布事件
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(menuItem);
}
private void OnTabSelected(TabItemModel tab)
{
if (tab.TabSource is MenuItem menuItem)
{
// 取消上一个选中菜单选中状态
SelectedMenuItem?.IsSelected = false;
SelectedMenuItem = menuItem;
// 设置菜单选中
SelectedMenuItem.IsSelected = true;
// 展开父节点
ToggleParents(SelectedMenuItem, true);
}
}
private void OnTabClosed(TabItemModel tab)
{
if (tab.TabSource is MenuItem menuItem)
{
// 折叠父节点
ToggleParents(menuItem, false);
// 取消菜单选中
menuItem?.IsSelected = false;
}
}
/// <summary>
/// 异步加载菜单【后续使用缓存以及权限管理】
/// </summary>
private async void LoadMenuAsync()
{
try
{
// 异步获取菜单数据
var menuTree = await _sysMenuService.GetLoginMenuTree();
// 转换菜单数据
ConvertMenuTreeToViewModel(menuTree);
// 默认导航
ScheduleDefaultNavigation();
}
catch (Exception ex)
{
_logger.Error($"菜单加载失败: {ex.Message}", ex);
// 显示错误菜单项
MenuItems.Add(new MenuItem
{
Name = "菜单加载失败",
Icon = "ErrorOutline",
ViewName = "ErrorView",
Children = { new MenuItem { Name = "点击重试", Icon = "Refresh" } }
});
}
}
/// <summary>
/// 将服务层菜单树转换为视图模型
/// </summary>
private void ConvertMenuTreeToViewModel(List<MenuOutput> menuTree)
{
MenuItems.Clear();
// 过滤并排序菜单项:只包含目录和菜单类型,排除按钮类型,并按排序号排序
var rootMenus = menuTree
.Where(m => m.Type == MenuTypeEnum.Dir || m.Type == MenuTypeEnum.Menu)
.Where(m => m.Status == StatusEnum.Enable) // 只包含启用状态的菜单
.Where(m => !(m?.IsHide ?? false)) // 排除隐藏的菜单
.OrderBy(m => m.OrderNo)
.ToList();
// 递归转换菜单项
void ConvertMenu(MenuOutput source, MenuItem target, MenuItem? parent = null)
{
target.Name = source?.Title ?? source?.Name;
target.Icon = ConvertHtmlEntityToUnicode(source?.Icon ?? "&#xe810;");
target.ViewName = ResolveViewName(source); // 将菜单路由映射到已注册的WPF视图
target.Parent = parent; // 设置父节点
// 添加子菜单(如果有)
if (source.Children != null && source.Children.Any())
{
// 过滤并排序子菜单
var childMenus = source.Children
.Where(c => c.Type == MenuTypeEnum.Dir || c.Type == MenuTypeEnum.Menu) // 子菜单支持目录和菜单两种类型
.Where(c => c.Status == StatusEnum.Enable)
.Where(c => !(c?.IsHide ?? false))
.OrderBy(c => c.OrderNo)
.ToList();
foreach (var child in childMenus)
{
var childItem = new MenuItem();
ConvertMenu(child, childItem, target);
target.Children.Add(childItem);
}
}
}
// 处理每个根菜单
foreach (var root in rootMenus)
{
var rootItem = new MenuItem();
ConvertMenu(root, rootItem);
// 如果根菜单是目录但没有子菜单,则不显示
if (root.Type == MenuTypeEnum.Dir && !rootItem.Children.Any())
continue;
MenuItems.Add(rootItem);
}
}
/// <summary>
/// 解析菜单对应的视图名称
/// </summary>
private string? ResolveViewName(MenuOutput? menu)
{
if (menu == null)
return null;
// 目录节点不参与内容区导航
if (menu.Type == MenuTypeEnum.Dir)
return null;
// 依次尝试 Path / Component / Name / Title兼容不同来源的菜单数据
var candidates = new[]
{
menu.Path,
menu.Component,
menu.Name,
menu.Title
};
foreach (var candidate in candidates)
{
if (string.IsNullOrWhiteSpace(candidate))
continue;
if (RouteToViewMap.TryGetValue(candidate.Trim(), out var viewName))
{
_logger.Debug($"菜单路由命中: Title={menu.Title}, Candidate={candidate}, View={viewName}");
return viewName;
}
}
// 保留原始Path若未注册将统一展示NotFoundView
_logger.Warning($"菜单路由未命中映射: Title={menu.Title}, Path={menu.Path}, Component={menu.Component}, Name={menu.Name}");
return menu.Path;
}
private string ConvertHtmlEntityToUnicode(string htmlEntity)
{
if (string.IsNullOrEmpty(htmlEntity))
return "\ue7c6"; // 默认图标
return StringUtil.ConvertHtmlEntityToUnicode(htmlEntity);
}
/// <summary>
/// 安排默认导航
/// </summary>
private void ScheduleDefaultNavigation()
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 默认菜单
var defaultMenuItem = GetFirstLeaf(MenuItems.FirstOrDefault());
if (defaultMenuItem != null)
{
// Tab不允许关闭
defaultMenuItem.IsClosable = false;
// 导航菜单
OpenOrActivateTab(defaultMenuItem);
}
}
catch (Exception ex)
{
_logger.Error($"默认导航失败: {ex.Message}", ex);
}
}), DispatcherPriority.ApplicationIdle);
}
private MenuItem? GetFirstLeaf(MenuItem? menu)
{
if (menu == null)
return null;
if (menu.Children == null || menu.Children.Count == 0)
return menu; // 自己就是叶子节点
// 递归向下找第一个叶子
return GetFirstLeaf(menu.Children.FirstOrDefault());
}
public void ToggleParents(MenuItem? item, bool IsExpanded)
{
if (item == null)
{
return;
}
var parent = item.Parent;
while (parent != null)
{
parent.IsExpanded = IsExpanded; // 展开父节点
if (!IsExpanded)
{
parent.IsSelected = IsExpanded;
}
parent = parent.Parent;
}
}
/// <summary>
/// 清空资源
/// </summary>
protected override void CleanUp()
{
if (tabSelectedToken != null)
{
_eventAggregator
.GetEvent<TabSelectedEvent>()
.Unsubscribe(tabSelectedToken);
tabSelectedToken = null;
}
if (tabClosedToken != null)
{
_eventAggregator
.GetEvent<TabClosedEvent>()
.Unsubscribe(tabClosedToken);
tabClosedToken = null;
}
if (_menuStructureToken != null)
{
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Unsubscribe(_menuStructureToken);
_menuStructureToken = null;
}
}
/// <summary>
/// 根据ViewName同步选中对应的菜单项
/// </summary>
//private MenuItem? GetSelectedMenuItem(string? viewName)
//{
// if (string.IsNullOrEmpty(viewName))
// return null;
// // 递归查找匹配的菜单项
// return FindMenuItemByViewName(MenuItems, viewName);
//}
/// <summary>
/// 递归查找匹配ViewName的菜单项
/// </summary>
//private MenuItem? FindMenuItemByViewName(ObservableCollection<MenuItem> menuItems, string viewName)
//{
// foreach (var menuItem in menuItems)
// {
// // 如果当前菜单项匹配
// if (menuItem.ViewName == viewName)
// return menuItem;
// // 递归查找子菜单
// if (menuItem.Children?.Count > 0)
// {
// var found = FindMenuItemByViewName(menuItem.Children, viewName);
// if (found != null)
// return found;
// }
// }
// return null;
//}
}
}