Files
WCS/Cowain.TestProject/ViewModels/MainWindowViewModel.cs

425 lines
16 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 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
}
}