425 lines
16 KiB
C#
425 lines
16 KiB
C#
using Avalonia;
|
||
using Avalonia.Media;
|
||
using Avalonia.Styling;
|
||
using Avalonia.Threading;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using CommunityToolkit.Mvvm.Messaging;
|
||
using Cowain.Base.Abstractions.Navigation;
|
||
using Cowain.Base.Helpers;
|
||
using Cowain.Base.Models.Menu;
|
||
using Cowain.Base.ViewModels;
|
||
using Cowain.TestProject.ViewModels.Admin;
|
||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||
using Microsoft.Extensions.Configuration;
|
||
using Microsoft.Extensions.Logging;
|
||
using Plugin.Cowain.Base.Models;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Threading;
|
||
|
||
namespace Cowain.TestProject.ViewModels
|
||
{
|
||
public partial class MainWindowViewModel : ViewModelBase, IRecipient<AlarmChangedMessage>
|
||
{
|
||
private PageViewMenuHelper _pageViewMenuActions;
|
||
[ObservableProperty]
|
||
private MenuItemViewModel? _selectedMenuItem;
|
||
/// <summary>
|
||
/// 工具栏按钮集合
|
||
/// </summary>
|
||
[ObservableProperty]
|
||
private ObservableCollection<MenuItemViewModel>? _toolbarMenus;
|
||
/// <summary>
|
||
/// 设置菜单集合
|
||
/// </summary>
|
||
[ObservableProperty]
|
||
private ObservableCollection<MenuItemViewModel>? _settingMenus;
|
||
|
||
/// <summary>
|
||
/// 存储筛选前的菜单数据
|
||
/// </summary>
|
||
private ObservableCollection<MenuItemViewModel>? _originalSettingMenus;
|
||
/// <summary>
|
||
/// 本地化资源
|
||
/// </summary>
|
||
private readonly ILocalizer _l;
|
||
|
||
private readonly ILogger<MainWindowViewModel> _logger;
|
||
/// <summary>
|
||
/// 菜单数据
|
||
/// </summary>
|
||
private readonly List<MenuItem> _menuItems;
|
||
/// <summary>
|
||
/// 防抖器
|
||
/// </summary>
|
||
private readonly Debounce _debounce;
|
||
/// <summary>
|
||
/// 视图导航器
|
||
/// </summary>
|
||
private readonly IViewNavigator _viewNavigator;
|
||
/// <summary>
|
||
/// 当前页面视图模型
|
||
/// </summary>
|
||
[ObservableProperty]
|
||
private PageViewModelBase? _currentPage;
|
||
|
||
/// <summary>
|
||
/// 用户图标
|
||
/// </summary>
|
||
[ObservableProperty]
|
||
private UserItemViewModel? _userItem;
|
||
|
||
/// <summary>
|
||
/// 报警信息
|
||
/// </summary>
|
||
[ObservableProperty]
|
||
private string? _alarmMsg = string.Empty;
|
||
|
||
|
||
private List<AlarmViewModel> _alarms = new();
|
||
// 获取版本号的属性
|
||
public string AppVersion => GetAssemblyVersion();
|
||
|
||
[RelayCommand]
|
||
private void SerachKeyUp(string e)
|
||
{
|
||
OnSerachMenu(e);
|
||
}
|
||
|
||
|
||
|
||
[RelayCommand]
|
||
private void ToggleTheme()
|
||
{
|
||
var app = Application.Current;
|
||
if (app is null) return;
|
||
var theme = app.ActualThemeVariant;
|
||
app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// MenuItem 到 MenuItemViewModel 的转换器
|
||
/// </summary>
|
||
private Func<MenuItem, MenuItemViewModel> MenuItemToViewModel => x => new MenuItemViewModel(_l[x.LocaleKey])
|
||
{
|
||
Key = x.Key,
|
||
IsActive = x.IsActive == true,
|
||
Icon = string.IsNullOrWhiteSpace(x.Icon) ? null : StreamGeometry.Parse(x.Icon),
|
||
CommandParameter = x.CommandParameter,
|
||
//PageActions = x.PageActions,
|
||
Items = x.Items.Select(MenuItemToViewModel).ToList(),
|
||
MenuClickCommand = GetRelayCommand(x.CommandType),
|
||
};
|
||
|
||
/// <summary>
|
||
/// 根据命令类型返回中继命令
|
||
/// </summary>
|
||
/// <param name="commandType"></param>
|
||
/// <returns></returns>
|
||
private IRelayCommand? GetRelayCommand(string? commandType)
|
||
{
|
||
if (!Enum.TryParse<MenuClickCommandType>(commandType, out var cmdType))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
return cmdType switch
|
||
{
|
||
// 导航到视图中继命令
|
||
MenuClickCommandType.Navigate => new RelayCommand(() =>
|
||
{
|
||
if (SelectedMenuItem?.CommandParameter is null)
|
||
{
|
||
return;
|
||
}
|
||
// 导航到视图
|
||
_viewNavigator.NavigateTo(SelectedMenuItem.CommandParameter);
|
||
}),
|
||
// 主题切换返回的中继命令
|
||
MenuClickCommandType.SwitchTheme => new RelayCommand<MenuItemViewModel>((MenuItemViewModel? menuItem) =>
|
||
{
|
||
var app = Application.Current;
|
||
if (app is not null)
|
||
{
|
||
ThemeVariant? tv = menuItem?.CommandParameter switch
|
||
{
|
||
nameof(ThemeVariant.Default) => ThemeVariant.Default,
|
||
nameof(ThemeVariant.Dark) => ThemeVariant.Dark,
|
||
"Desert" => Semi.Avalonia.SemiTheme.Desert,
|
||
"Dusk" => Semi.Avalonia.SemiTheme.Dusk,
|
||
"NightSky" => Semi.Avalonia.SemiTheme.NightSky,
|
||
_ => ThemeVariant.Default
|
||
};
|
||
app.RequestedThemeVariant = tv;
|
||
// 重载视图
|
||
_viewNavigator.ReloadCurrentPage();
|
||
}
|
||
}),
|
||
|
||
// 打开链接返回的中继命令
|
||
MenuClickCommandType.Link => new RelayCommand<MenuItemViewModel>((MenuItemViewModel? menuItem) =>
|
||
{
|
||
var url = menuItem?.CommandParameter;
|
||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||
{
|
||
FileName = url,
|
||
UseShellExecute = true
|
||
});
|
||
}),
|
||
|
||
// 切换本地化语言
|
||
MenuClickCommandType.SwitchLanguage => new RelayCommand<MenuItemViewModel>((MenuItemViewModel? menuItem) =>
|
||
{
|
||
_l.CurrentCulture = _l.AvailableCultures.FirstOrDefault(f => f.IetfLanguageTag == menuItem?.CommandParameter) ??
|
||
Thread.CurrentThread.CurrentCulture;
|
||
// 重载视图,触发本地化更新
|
||
_viewNavigator.ClearCurrentPage();
|
||
//这里重新加载菜单,菜单选中项会被破坏
|
||
LoadToolbarMenus();
|
||
SelectedMenuItem = null;
|
||
//重新选中菜单
|
||
var app = Application.Current;
|
||
if (app is null) return;
|
||
CultureInfo cultureInfo = menuItem?.CommandParameter is string parameter
|
||
? new CultureInfo(parameter)
|
||
: Thread.CurrentThread.CurrentCulture;
|
||
Semi.Avalonia.SemiTheme.OverrideLocaleResources(app, cultureInfo);
|
||
Ursa.Themes.Semi.SemiTheme.OverrideLocaleResources(app, cultureInfo);
|
||
}),
|
||
|
||
// 返回 null
|
||
_ => null
|
||
};
|
||
}
|
||
private readonly IMessenger _messenger;
|
||
public MainWindowViewModel(MenuConfigurationContext menuContext, ILocalizer localizer, IViewNavigator viewNavigator, PageViewMenuHelper viewMenuActions, IMessenger messenger, ILogger<MainWindowViewModel> logger, IConfiguration configuration)
|
||
{
|
||
_l = localizer;
|
||
_logger = logger;
|
||
_messenger = messenger;
|
||
_menuItems = menuContext.Menus;
|
||
_userItem = new UserItemViewModel();
|
||
_pageViewMenuActions = viewMenuActions;
|
||
_debounce = new Debounce(500);
|
||
_viewNavigator = viewNavigator;
|
||
_viewNavigator.PropertyChanged += OnNavigatorPropertyChanged;
|
||
LoadToolbarMenus();
|
||
_messenger.RegisterAll(this);
|
||
var homeMenu = ToolbarMenus?.Where(x => x.Key == "Home").FirstOrDefault();
|
||
if (homeMenu != null && !string.IsNullOrEmpty(homeMenu.CommandParameter))
|
||
{
|
||
SelectedMenuItem = homeMenu;
|
||
//自动导航到首页
|
||
_viewNavigator.NavigateTo(homeMenu.CommandParameter);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
// 反射获取程序集版本
|
||
private string GetAssemblyVersion()
|
||
{
|
||
var assembly = Assembly.GetExecutingAssembly();
|
||
var version = assembly.GetName().Version;
|
||
|
||
if (version == null)
|
||
return "1.0.0";
|
||
|
||
// 只取前三个部分(主版本.次版本.生成号)
|
||
return $"{version.Major}.{version.Minor}.{version.Build}";
|
||
}
|
||
private void OnNavigatorPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||
{
|
||
if (e.PropertyName == nameof(_viewNavigator.CurrentPage))
|
||
{
|
||
CurrentPage = _viewNavigator.CurrentPage;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 载入工具栏菜单
|
||
/// </summary>
|
||
private void LoadToolbarMenus()
|
||
{
|
||
var toolbarMenus = _menuItems.Where(x => x.Group == "Toolbar").Select(MenuItemToViewModel);
|
||
var userMenus = toolbarMenus.Where(x => IsUserMenu(x)).Select(x => GetUserMenuItem(x));
|
||
_originalSettingMenus = [.. userMenus];
|
||
ToolbarMenus = _originalSettingMenus;
|
||
var settingMenus = _menuItems.Where(x => x.Group == "Settings").Select(MenuItemToViewModel);
|
||
SettingMenus = [.. settingMenus];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断用户是否有此菜单权限
|
||
/// </summary>
|
||
private bool IsUserMenu(MenuItemViewModel menuItem)
|
||
{
|
||
if (GlobalData.Instance.CurrentUser?.Menus?.Any(menu => menu.MenuKey == menuItem.Key) == true)
|
||
{
|
||
return true;
|
||
}
|
||
if (menuItem.Items != null)
|
||
{
|
||
foreach (var subItem in menuItem.Items)
|
||
{
|
||
if (IsUserMenu(subItem))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private MenuItemViewModel GetUserMenuItem(MenuItemViewModel menuItem)
|
||
{
|
||
var filteredSubItems = menuItem.Items?
|
||
.Where(x => IsUserMenu(x))
|
||
.Select(x => GetUserMenuItem(x))
|
||
.ToList();
|
||
return new MenuItemViewModel(menuItem.Text)
|
||
{
|
||
Key = menuItem.Key,
|
||
IsActive = menuItem.IsActive,
|
||
Icon = menuItem.Icon,
|
||
CommandParameter = menuItem.CommandParameter,
|
||
Items = filteredSubItems,
|
||
MenuClickCommand = menuItem.MenuClickCommand,
|
||
};
|
||
}
|
||
|
||
#region 菜单搜索
|
||
|
||
|
||
/// <summary>
|
||
/// 搜索查找菜单
|
||
/// </summary>
|
||
/// <param name="keywords"></param>
|
||
public void OnSerachMenu(string? keywords)
|
||
{
|
||
// 防抖是指在一定时间范围内,只有最后一次操作才会被执行。如果在这段时间内再次触发操作,则重新计时。
|
||
// KeyUp 事件会在每个按键弹起时执行,为了避免频繁触发事件。所以进行防抖处理
|
||
_debounce.Trigger(() =>
|
||
{
|
||
// 筛选菜单。在国际化场景中,使用 InvariantCultureIgnoreCase,以确保跨文化的比较行为一致
|
||
var filterdMenus = _originalSettingMenus?
|
||
.Where(x => ContainsKeyword(x, keywords ?? string.Empty, StringComparison.InvariantCultureIgnoreCase))
|
||
.Select(x => FilterMenuItem(x, keywords ?? string.Empty, StringComparison.InvariantCultureIgnoreCase))
|
||
.ToList();
|
||
|
||
if (filterdMenus != null && filterdMenus.Count > 0)
|
||
{
|
||
var menus = new ObservableCollection<MenuItemViewModel>(filterdMenus);
|
||
ToolbarMenus = [.. menus];
|
||
}
|
||
});
|
||
}
|
||
|
||
private MenuItemViewModel FilterMenuItem(MenuItemViewModel menuItem, string keyword, StringComparison comparison)
|
||
{
|
||
var filteredSubItems = menuItem.Items?
|
||
.Where(x => ContainsKeyword(x, keyword, comparison))
|
||
.Select(x => FilterMenuItem(x, keyword, comparison))
|
||
.ToList();
|
||
|
||
return new MenuItemViewModel(menuItem.Text)
|
||
{
|
||
Key = menuItem.Key,
|
||
IsActive = menuItem.IsActive,
|
||
Icon = menuItem.Icon,
|
||
CommandParameter = menuItem.CommandParameter,
|
||
Items = filteredSubItems,
|
||
MenuClickCommand = menuItem.MenuClickCommand,
|
||
};
|
||
}
|
||
|
||
|
||
|
||
private bool ContainsKeyword(MenuItemViewModel menuItem, string keyword, StringComparison comparison)
|
||
{
|
||
if (menuItem.Text.Contains(keyword, comparison))
|
||
{
|
||
return true;
|
||
}
|
||
if (menuItem.Items != null)
|
||
{
|
||
foreach (var subItem in menuItem.Items)
|
||
{
|
||
if (ContainsKeyword(subItem, keyword, comparison))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 报警滚动条消息接收
|
||
/// </summary>
|
||
public void Receive(AlarmChangedMessage message)
|
||
{
|
||
Dispatcher.UIThread.Post(() =>
|
||
{
|
||
// 标记:是否有实际的报警列表变更(新增/删除)
|
||
bool hasAlarmChanged = false;
|
||
var latestAlarms = message.Value ?? new List<AlarmViewModel>();
|
||
var currentTagIds = _alarms.Select(a => a.TagId).ToHashSet();
|
||
var latestTagIds = latestAlarms.Select(a => a.TagId).ToHashSet();
|
||
var alarmsToAdd = latestAlarms.Where(alarm => !currentTagIds.Contains(alarm.TagId)).ToList();
|
||
if (alarmsToAdd.Any()) // 有新增项,标记变更
|
||
{
|
||
hasAlarmChanged = true;
|
||
foreach (var alarm in alarmsToAdd)
|
||
{
|
||
_alarms.Add(alarm);
|
||
}
|
||
}
|
||
var tagIdsToRemove = currentTagIds.Where(tagId => !latestTagIds.Contains(tagId)).ToList();
|
||
if (tagIdsToRemove.Any()) // 有删除项,标记变更
|
||
{
|
||
hasAlarmChanged = true;
|
||
foreach (var tagId in tagIdsToRemove)
|
||
{
|
||
var alarmToRemove = _alarms.FirstOrDefault(a => a.TagId == tagId);
|
||
if (alarmToRemove != null)
|
||
{
|
||
_alarms.Remove(alarmToRemove);
|
||
}
|
||
}
|
||
}
|
||
if (hasAlarmChanged)
|
||
{
|
||
// 拼接所有报警的Desc(自定义分隔符,这里用换行符,适合UI显示;也可改用分号";")
|
||
//AlarmMsg = string.Join(Environment.NewLine, _alarms.Select(a => a.Desc));
|
||
//需要去重Desc,过滤空Desc
|
||
AlarmMsg = string.Join(" ",
|
||
_alarms.Where(a => !string.IsNullOrEmpty(a.Desc))
|
||
.Select(a => a.Desc)
|
||
.Distinct());
|
||
}
|
||
});
|
||
}
|
||
|
||
// 销毁时取消注册,避免内存泄漏
|
||
protected override void Dispose(bool disposing)
|
||
{
|
||
if (disposing) _messenger.UnregisterAll(this);
|
||
base.Dispose(disposing);
|
||
}
|
||
#endregion
|
||
|
||
}
|
||
}
|