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

757 lines
27 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 Mapster;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using SqlSugar;
using System.Collections.ObjectModel;
using System.IO;
using System.Net.Http;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using YY.Admin.Core;
using YY.Admin.Core.Const;
using YY.Admin.Core.Model;
using YY.Admin.Core.Services;
using YY.Admin.Core.Session;
using YY.Admin.Core.Util;
using YY.Admin.Event;
using YY.Admin.Module;
using YY.Admin.Services.Service.Auth;
using YY.Admin.Services.Service.Jeecg;
using YY.Admin.Services.Service.Menu;
using YY.Admin.ViewModels.Control;
using YY.Admin.Helper;
namespace YY.Admin.ViewModels
{
public class MainWindowViewModel : BaseViewModel
{
private readonly ISysAuthService _authService;
private readonly IDialogService _dialogService;
private readonly IJeecgLoginLogReportService _loginLogReportService;
private readonly IJeecgUserSyncCoordinator _jeecgUserSyncCoordinator;
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient = new();
private readonly CancellationTokenSource _backendConnectivityCts = new();
private const int ConnectivityCheckIntervalSeconds = 5;
private SysUser? _currentUser;
private bool _isBackendConnected;
#region
public ObservableCollection<TabItemModel> OpenTabs { get; } = new ObservableCollection<TabItemModel>();
private CancellationTokenSource? _navigateCts;
private bool _isTabClosing;
private TabItemModel? _selectedTab;
public TabItemModel? SelectedTab
{
get => _selectedTab;
set
{
if (SetProperty(ref _selectedTab, value) && value != null)
{
// 发布事件
_eventAggregator.GetEvent<TabSelectedEvent>().Publish(value);
bool isNotMenuTreeView = _regionManager.Regions[CommonConst.MenuRegion].ActiveViews.Where(it => it.GetType().Name != "MenuTreeView").Any();
if (value.TabSource is MenuItem menuItem && !isNotMenuTreeView)
{
var navItem = NavItems?.Where(it => it.ViewName == "MenuTreeView").FirstOrDefault();
if (navItem != null)
{
if (navItem.AlignBottom)
{
SelectedBottomNavItem = navItem;
}
else
{
SelectedTopNavItem = navItem;
}
}
} else if (value.TabSource is NavItem navItem)
{
if (navItem.AlignBottom)
{
SelectedBottomNavItem = navItem;
} else
{
SelectedTopNavItem = navItem;
}
}
// 取消上一次延迟导航
// 防止拖拽Tab时由于两次导航出现视觉上闪烁问题
// TabA拖动到TabB过程TabA -> TabB -> TabA
_navigateCts?.Cancel();
_navigateCts = new CancellationTokenSource();
// 延迟 100ms 执行导航(过滤临时选中)
_ = Task.Delay(100, _navigateCts.Token)
.ContinueWith(_ =>
{
Application.Current.Dispatcher.Invoke(() =>
{
_ = NavigateToViewAsync(CommonConst.ContentRegion, value.ViewName, value.TabSource?.NavigationParameter);
});
},
TaskContinuationOptions.OnlyOnRanToCompletion
);
}
}
}
#endregion
# region
public ObservableCollection<NavItem>? NavItems { get; set; }
public ObservableCollection<NavItem>? TopNavItems { get; set; }
public ObservableCollection<NavItem>? BottomNavItems { get; set; }
private NavItem? _selectedNavItem;
public NavItem? SelectedNavItem
{
get => _selectedNavItem;
set {
if (SetProperty(ref _selectedNavItem, value) && value != null)
{
// 执行命令(如果有)
if (value.Command?.CanExecute(value) == true)
{
value.Command.Execute(value);
}
}
}
}
private NavItem? _selectedTopNavItem;
public NavItem? SelectedTopNavItem
{
get => _selectedTopNavItem;
set
{
if (SetProperty(ref _selectedTopNavItem, value) && value != null)
{
// 取消底部选中 互斥
SelectedBottomNavItem = null;
SelectedNavItem = value;
}
}
}
private NavItem? _selectedBottomNavItem;
public NavItem? SelectedBottomNavItem
{
get => _selectedBottomNavItem;
set
{
if (SetProperty(ref _selectedBottomNavItem, value) && value != null)
{
// 取消顶部选中 互斥
SelectedTopNavItem = null;
SelectedNavItem = value;
}
}
}
#endregion
#region
private bool _isAppSettingsOpen;
public bool IsAppSettingsOpen
{
get => _isAppSettingsOpen;
set => SetProperty(ref _isAppSettingsOpen, value);
}
private AppSettingsViewModel? _appSettingsViewModel;
public AppSettingsViewModel? AppSettingsViewModel {
get => _appSettingsViewModel;
set => SetProperty(ref _appSettingsViewModel, value);
}
// 系统设置命令
public ICommand ResetAppSettingsCommand { get; }
public ICommand OpenAppSettingsCommand { get; }
public ICommand OpenServerSettingsCommand { get; }
#endregion
private SubscriptionToken? _openOrActivateTabToken;
private SubscriptionToken? _tabClosedToken;
private SubscriptionToken? _refreshTabToken;
private SubscriptionToken? _loginOutToken;
public MainWindowViewModel(
ISysAuthService authService,
IDialogService dialogService,
IJeecgLoginLogReportService loginLogReportService,
IJeecgUserSyncCoordinator jeecgUserSyncCoordinator,
ISysMenuService sysMenuService,
IContainerExtension _container,
IRegionManager regionManager
) : base(_container, regionManager)
{
// 加载系统设置
LoadAppSettings();
_authService = authService;
_dialogService = dialogService;
_loginLogReportService = loginLogReportService;
_jeecgUserSyncCoordinator = jeecgUserSyncCoordinator;
_configuration = _container.Resolve<IConfiguration>();
_loginLogReportService.StartBackgroundSync();
// 订阅用户变更事件
_currentUser = _authService.CurrentUser;
_authService.UserChanged += OnUserChanged;
// 登出命令
LogoutCommand = new DelegateCommand(LogoutAsync);
// 系统设置命令
OpenAppSettingsCommand = new DelegateCommand(OpenAppSettings);
ResetAppSettingsCommand = new DelegateCommand(ResetAppSettings);
OpenServerSettingsCommand = new DelegateCommand(OpenServerSettings);
// 初始化Sidebar数据
InitNavItems();
// 订阅事件
_openOrActivateTabToken = _eventAggregator.GetEvent<TabSourceSelectedEvent>().Subscribe(OnOpenOrActivateTab);
_tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed);
_refreshTabToken = _eventAggregator.GetEvent<TabRefreshEvent>().Subscribe(OnRefreshTab);
_loginOutToken = _eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Subscribe(Destroy);
// 首次按默认连接;后续按本地保存状态恢复
var settings = ServerSettingsStore.Load();
IsServerConnectionEnabled = !settings.DisconnectConnection;
// 主窗口底部连接状态圆点
_ = StartBackendConnectivityLoopAsync(_backendConnectivityCts.Token);
}
public SysUser? CurrentUser
{
get => _currentUser;
set => SetProperty(ref _currentUser, value);
}
/// <summary>
/// 后端连接状态true=连接中false=已断开
/// </summary>
public bool IsBackendConnected
{
get => _isBackendConnected;
set
{
if (SetProperty(ref _isBackendConnected, value))
{
RaisePropertyChanged(nameof(BackendConnectionStatusBrush));
}
}
}
public Brush BackendConnectionStatusBrush => IsBackendConnected ? Brushes.LimeGreen : Brushes.Red;
private bool _isServerConnectionEnabled = true;
/// <summary>
/// 是否启用服务器连接(默认启用)
/// </summary>
public bool IsServerConnectionEnabled
{
get => _isServerConnectionEnabled;
set
{
if (SetProperty(ref _isServerConnectionEnabled, value))
{
if (!value)
{
IsBackendConnected = false;
_jeecgUserSyncCoordinator.Stop();
}
else
{
_jeecgUserSyncCoordinator.Start();
}
}
}
}
public DelegateCommand LogoutCommand { get; }
private void InitNavItems()
{
NavItems = new ObservableCollection<NavItem>
{
new NavItem {
Icon = "FileTreeOutline",
Name = "功能菜单",
ViewName = "MenuTreeView",
Command = new DelegateCommand<NavItem>(it => _ = NavigateToViewAsync(CommonConst.MenuRegion, it.ViewName))
},
new NavItem {
Icon = "GamepadVariantOutline",
Name = "菜单区域",
Command = new DelegateCommand<NavItem>(it => _ = NavigateToViewAsync(CommonConst.MenuRegion, it.ViewName))
},
new NavItem {
Icon = "FoodAppleOutline",
Name = "Tab区域",
Command = new DelegateCommand<NavItem>(OnOpenOrActivateTab)
},
new NavItem {
Icon = "Server",
Name = "服务器设置",
AlignBottom = true,
IsActive = false,
Command = OpenServerSettingsCommand
},
new NavItem {
Icon = "AccountCircleOutline",
Name = "个人中心",
AlignBottom = true,
Command = new DelegateCommand<NavItem>(OnOpenOrActivateTab)
},
new NavItem {
Icon = StringUtil.ConvertHtmlEntityToUnicode("&#xe7a3;"),
IconType = IconTypeEnum.AntDesign,
Name = "系统设置",
AlignBottom = true,
IsActive = false,
Command = OpenAppSettingsCommand
},
new NavItem {
Icon = "Power",
Name = "退出登录",
AlignBottom = true,
IsActive = false,
Command = LogoutCommand
}
};
TopNavItems = new(NavItems.Where(t => !t.AlignBottom));
BottomNavItems = new(NavItems.Where(t => t.AlignBottom));
SelectedTopNavItem = TopNavItems.FirstOrDefault();
}
private void OnUserChanged(object? sender, SysUser? user)
{
CurrentUser = user;
//// 用户变更时刷新菜单
//LoadMenuAsync();
}
/// <summary>
/// 导航
/// </summary>
/// <param name="regionName">区域名称</param>
/// <param name="viewName">视图名称</param>
/// <returns></returns>
private async Task NavigateToViewAsync(string regionName, string? viewName, INavigationParameters? parameters = null)
{
try
{
if (string.IsNullOrEmpty(regionName))
return;
var tcs = new TaskCompletionSource<bool>();
// 视图为空 或者 视图未在容器中注册
if (string.IsNullOrEmpty(viewName) || !(_container as IContainerProvider).IsRegistered<object>(viewName))
{
_logger.Error($"视图未注册: {viewName}");
_regionManager.RequestNavigate(regionName, "NotFoundView");
tcs.SetResult(false);
return;
}
_logger.Debug($"开始后台异步导航到: {viewName}");
// 在UI线程执行导航但不在属性设置的调用栈中
await Application.Current.Dispatcher.InvokeAsync(() =>
{
_regionManager.RequestNavigate(regionName, viewName,
result =>
{
if (result.Success)
{
_logger.Debug($"导航成功: {viewName}");
tcs.SetResult(true);
}
else
{
var exMsg = result.Exception?.Message;
_logger.Error($"导航失败: {viewName}, Region={regionName}, Exception={exMsg}");
tcs.SetResult(false);
}
}, parameters);
}, DispatcherPriority.Background); // 使用较低优先级
await tcs.Task;
}
catch (Exception ex)
{
_logger.Error($"导航异常: {ex.Message}");
}
}
/// <summary>
/// 打开或激活一个标签页(若已打开则激活,否则新建)
/// </summary>
private void OnOpenOrActivateTab(TabSource tabSource)
{
if (tabSource == null) {
_logger.Debug("tabSource为空");
return;
}
if (tabSource is MenuItem menuItem)
{
// 检查是否是父级菜单(有子菜单)
if (menuItem.Children?.Count > 0)
{
_logger.Debug($"跳过父级菜单: {menuItem.Name},它有 {menuItem.Children.Count} 个子菜单");
return;
}
// 检查是否有有效的 ViewName
//if (string.IsNullOrEmpty(menuItem.ViewName))
//{
// _logger.Debug($"菜单没有 ViewName: {menuItem.Name}");
// return;
//}
_logger.Debug($"点击菜单: {menuItem.Name}, ViewName: {menuItem.ViewName}");
}
// 检查标签是否已打开
var existingTab = OpenTabs.FirstOrDefault(t => t.TabSource == tabSource);
if (existingTab != null)
{
_logger.Debug($"切换到已存在标签: {tabSource.ViewName}");
SelectedTab = existingTab;
return;
}
// 创建新标签
var newTab = new TabItemModel
{
Header = tabSource.Name,
Icon = tabSource.Icon,
IconType = tabSource.IconType,
ViewName = tabSource.ViewName,
IsClosable = tabSource.IsClosable,
TabSource = tabSource,
OpenTabs = OpenTabs,
EventAggregator = _eventAggregator
};
_logger.Debug($"创建新标签: {tabSource.ViewName}");
OpenTabs.Add(newTab);
SelectedTab = newTab;
}
/// <summary>
/// 刷新Tab
/// </summary>
/// <param name="tabItemModel"></param>
private void OnRefreshTab(TabItemModel tabItemModel)
{
if (tabItemModel?.TabSource == null)
{
return;
}
// 当前Tab选中
SelectedTab = tabItemModel;
// 从Region中移除现有视图避免缓存问题
RemoveContentRegionView(tabItemModel.ViewName);
// 重新导航,确保加载新的视图
_ = NavigateToViewAsync(CommonConst.ContentRegion, tabItemModel.ViewName, tabItemModel.TabSource.NavigationParameter);
}
/// <summary>
/// TabItem关闭回调
/// </summary>
private void OnTabClosed(TabItemModel tabModel)
{
if (tabModel != null)
{
_logger.Debug($"标签已关闭: {tabModel.Header}");
//var viewName = OpenTabs.Count <= 1 ? null : tabModel.ViewName;
var viewName = tabModel.ViewName;
RemoveContentRegionView(viewName);
// 当前Tab是最后一个
if (OpenTabs.Count <= 1)
{
var region = _regionManager.Regions[CommonConst.MenuRegion];
var activeView = region.ActiveViews.FirstOrDefault();
if (activeView != null)
{
var navItem = NavItems?.Where(it => it.ViewName == activeView.GetType().Name).FirstOrDefault();
if (navItem != null)
{
if (navItem.AlignBottom)
{
SelectedBottomNavItem = navItem;
}
else
{
SelectedTopNavItem = navItem;
}
}
}
} else if (tabModel.TabSource is NavItem navItem)
{
if (navItem == SelectedTopNavItem)
{
SelectedTopNavItem = null;
SelectedNavItem = null;
}
else if (navItem == SelectedBottomNavItem)
{
SelectedBottomNavItem = null;
SelectedNavItem = null;
}
}
// 如果关闭的不是当前选中的Tab
if (SelectedTab != null && SelectedTab != tabModel)
{
// 发布事件强制刷新当前选中因为右键关闭未切换不会触发SelectedTab属性setter方法
_eventAggregator.GetEvent<TabSelectedEvent>().Publish(SelectedTab);
}
}
}
private async void LogoutAsync()
{
// 使用异步版本
var confirmed = await ConfirmAsync("确定退出登录吗?");
if (confirmed)
{
var account = AppSession.CurrentUser?.Account ?? CurrentUser?.Account ?? string.Empty;
_ = _loginLogReportService.ReportLogAsync("LOGIN", "退出登录", account, true);
// 发布登出事件
_eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Publish(AppSession.CurrentUser!);
Logout();
}
// var messageBoxResult = HandyControl.Controls.MessageBox.Show(
// $"确定退出登录吗?",
// "确认登出",
//MessageBoxButton.OKCancel,
//MessageBoxImage.Warning);
// if (messageBoxResult != MessageBoxResult.OK) return;
// // 发布登出事件
// _eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Publish(AppSession.CurrentUser!);
// Logout();
}
#region
/// <summary>
/// 打开系统设置
/// </summary>
private void OpenAppSettings()
{
IsAppSettingsOpen = true;
}
/// <summary>
/// 重置系统设置
/// </summary>
private void ResetAppSettings()
{
// 重置为默认值
// new AppSettingsViewModel().Adapt(AppSettingsViewModel);
AppSettingsViewModel = new AppSettingsViewModel();
AppSettingsViewModel.UpdateSkin(AppSettingsViewModel.SkinType!.Value);
AppSettingsViewModel.UpdateAppSettings();
}
/// <summary>
/// 加载系统设置
/// </summary>
private void LoadAppSettings()
{
try
{
var filePath = AppSettingsViewModel.GetFilePath();
if (!File.Exists(filePath))
{
AppSettingsViewModel = new AppSettingsViewModel();
AppSettingsViewModel.UpdateSkin(AppSettingsViewModel.SkinType!.Value);
AppSettingsViewModel.SaveAppSettings();
return;
}
var json = File.ReadAllText(filePath);
var appSettings = JsonConvert.DeserializeObject<AppSettings>(json);
AppSettingsViewModel = appSettings.Adapt<AppSettingsViewModel>();
AppSettingsViewModel.SkinType = AppSettingsViewModel.SyncWithSystem ? AppSettingsViewModel.GetSkinTypeBySystem() : AppSettingsViewModel.SkinType;
AppSettingsViewModel.UpdateSkin(AppSettingsViewModel.SkinType!.Value);
AppSettingsViewModel.UpdateAppSettings();
}
catch (Exception ex)
{
_logger.Error($"加载系统设置失败: {ex.Message}", ex);
}
}
#endregion
private async Task StartBackendConnectivityLoopAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
bool connected = false;
if (!IsServerConnectionEnabled)
{
try
{
await Application.Current.Dispatcher.InvokeAsync(() => { IsBackendConnected = false; });
}
catch
{
}
try
{
await Task.Delay(TimeSpan.FromSeconds(ConnectivityCheckIntervalSeconds), cancellationToken);
}
catch (OperationCanceledException)
{
break;
}
continue;
}
try
{
// 每轮都重新读取配置,保存服务器设置后可即时生效
var baseUrl = _configuration.GetValue<string>("JeecgIntegration:BaseUrl")?.TrimEnd('/');
var userListPath = _configuration.GetValue<string>("JeecgIntegration:UserListPath") ?? "/sys/user/scada/queryUser";
var probeUrl = string.IsNullOrWhiteSpace(baseUrl)
? string.Empty
: $"{baseUrl}{userListPath}?pageNo=1&pageSize=1&includeDetail=false";
if (!string.IsNullOrWhiteSpace(probeUrl))
{
using var req = new HttpRequestMessage(HttpMethod.Get, probeUrl);
using var resp = await _httpClient.SendAsync(req, cancellationToken);
connected = resp.IsSuccessStatusCode;
}
}
catch
{
connected = false;
}
try
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
IsBackendConnected = connected;
});
}
catch
{
// 忽略窗口关闭后的调度异常
}
try
{
await Task.Delay(TimeSpan.FromSeconds(ConnectivityCheckIntervalSeconds), cancellationToken);
}
catch (OperationCanceledException)
{
break;
}
}
}
private void OpenServerSettings()
{
_dialogService.ShowDialog("ServerSettingsDialog", r =>
{
if (r.Result == ButtonResult.OK)
{
var settings = ServerSettingsStore.Load();
IsServerConnectionEnabled = !settings.DisconnectConnection;
if (settings.DisconnectConnection)
{
var signalService = _container.Resolve<ISignalRService>();
_ = signalService.DisconnectAsync();
}
else
{
var signalService = _container.Resolve<ISignalRService>();
_ = signalService.ConnectUnifiedDeviceChannelAsync(CancellationToken.None);
}
}
});
}
/// <summary>
/// 清空资源
/// </summary>
private void Destroy(SysUser? sysUser = null)
{
if (_openOrActivateTabToken != null)
{
_eventAggregator
.GetEvent<TabSourceSelectedEvent>()
.Unsubscribe(_openOrActivateTabToken);
_openOrActivateTabToken = null;
}
if (_tabClosedToken != null)
{
_eventAggregator
.GetEvent<TabClosedEvent>()
.Unsubscribe(_tabClosedToken);
_tabClosedToken = null;
}
if (_refreshTabToken != null)
{
_eventAggregator
.GetEvent<TabRefreshEvent>()
.Unsubscribe(_refreshTabToken);
_refreshTabToken = null;
}
if (_loginOutToken != null)
{
_eventAggregator
.GetEvent<SysUserEvents.LoginOutEvent>()
.Unsubscribe(_loginOutToken);
_loginOutToken = null;
}
if (_authService != null)
{
_authService.UserChanged -= OnUserChanged;
}
if (!_backendConnectivityCts.IsCancellationRequested)
{
_backendConnectivityCts.Cancel();
}
_jeecgUserSyncCoordinator.Stop();
}
}
}