更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。

This commit is contained in:
geht
2026-04-28 10:23:58 +08:00
parent bbe46dcf2d
commit 142a0bdaba
1013 changed files with 41858 additions and 28 deletions

View File

@@ -0,0 +1,376 @@
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 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",
// 已实现页面:角色管理
["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"
};
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;
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);
}
public void OpenOrActivateTab(MenuItem menuItem)
{
// 发布事件
_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)
{
// 过滤并排序菜单项:只包含目录和菜单类型,排除按钮类型,并按排序号排序
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))
return viewName;
}
// 保留原始Path若未注册将统一展示NotFoundView
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;
}
}
/// <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;
//}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YY.Admin.ViewModels.Control
{
public class PaginationDataGridViewModel<T> : BindableBase
{
private int _pageIndex = 1;
private int _dataCountPerPage = 10;
private int _totalCount;
private int _maxPageCount;
private ObservableCollection<T> _data;
//private string _pageInfo;
public int MyProperty { get; private set; }
public int PageIndex
{
get => _pageIndex;
set => SetProperty(ref _pageIndex, value);
}
public int DataCountPerPage
{
get => _dataCountPerPage;
set => SetProperty(ref _dataCountPerPage, value);
}
public int TotalCount
{
get => _totalCount;
set => SetProperty(ref _totalCount, value);
}
public int MaxPageCount
{
get => _maxPageCount;
set => SetProperty(ref _maxPageCount, value);
}
public ObservableCollection<T> Data
{
get => _data;
set => SetProperty(ref _data, value);
}
//public string PageInfo
//{
// get => _pageInfo;
// set => SetProperty(ref _pageInfo, value);
//}
// 页面大小选项
public Dictionary<int, string> PageSizes { get; } = new Dictionary<int, string>
{
{ 10, "10条/页" },
{ 20, "20条/页" },
{ 30, "30条/页" },
{ 40, "40条/页" },
{ 50, "50条/页" },
{ 100, "100条/页" }
};
public DelegateCommand PageUpdatedCmd { get; private set; }
public DelegateCommand PageSizeUpdatedCmd { get; private set; }
private Func<Task<(IEnumerable<T> data, int totalCount)>> _fetchData;
public PaginationDataGridViewModel(Func<Task<(IEnumerable<T> data, int totalCount)>> fetchData)
{
_fetchData = fetchData;
PageUpdatedCmd = new DelegateCommand(async () => await LoadDataAsync());
PageSizeUpdatedCmd = new DelegateCommand(async () => await LoadDataAsync());
// 初始化时加载数据
//_ = LoadData();
}
public async Task LoadDataAsync()
{
try
{
var (data, totalCount) = await _fetchData();
Data = new ObservableCollection<T>(data);
TotalCount = totalCount;
// 通知分页总数变化
//RaisePropertyChanged(nameof(MaxPageCount));
MaxPageCount = totalCount == 0 ? 1 : (int)Math.Ceiling((double)totalCount / DataCountPerPage);
// 更新分页信息
//PageInfo = $"共 {_totalCount} 条";
} catch (OperationCanceledException)
{
// 查询被取消,保持表格不变
return;
}
}
}
}