using Mapster; using Microsoft.Extensions.Configuration; using Prism.Dialogs; 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; 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.Helper; 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; 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 OpenTabs { get; } = new ObservableCollection(); private CancellationTokenSource? _navigateCts; private bool _isTabClosing; private TabItemModel? _selectedTab; public TabItemModel? SelectedTab { get => _selectedTab; set { if (SetProperty(ref _selectedTab, value) && value != null) { // 发布事件 _eventAggregator.GetEvent().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? NavItems { get; set; } public ObservableCollection? TopNavItems { get; set; } public ObservableCollection? 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); } /// /// 为 true 时隐藏左侧功能菜单树(次级侧栏) /// private bool _isMenuTreePanelCollapsed; public bool IsMenuTreePanelCollapsed { get => _isMenuTreePanelCollapsed; set => SetProperty(ref _isMenuTreePanelCollapsed, value); } private NavItem? _menuTreeToggleNavItem; 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; } public ICommand ToggleMenuTreePanelCommand { 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(); _loginLogReportService.StartBackgroundSync(); // 订阅用户变更事件 _currentUser = _authService.CurrentUser; _authService.UserChanged += OnUserChanged; // 登出命令 LogoutCommand = new DelegateCommand(LogoutAsync); // 系统设置命令 OpenAppSettingsCommand = new DelegateCommand(OpenAppSettings); ResetAppSettingsCommand = new DelegateCommand(ResetAppSettings); OpenServerSettingsCommand = new DelegateCommand(OpenServerSettings); ToggleMenuTreePanelCommand = new DelegateCommand(ToggleMenuTreePanel); // 初始化Sidebar数据 InitNavItems(); // 订阅事件 _openOrActivateTabToken = _eventAggregator.GetEvent().Subscribe(OnOpenOrActivateTab); _tabClosedToken = _eventAggregator.GetEvent().Subscribe(OnTabClosed); _refreshTabToken = _eventAggregator.GetEvent().Subscribe(OnRefreshTab); _loginOutToken = _eventAggregator.GetEvent().Subscribe(Destroy); // 首次按默认连接;后续按本地保存状态恢复 var settings = ServerSettingsStore.Load(); IsServerConnectionEnabled = !settings.DisconnectConnection; // 主窗口底部连接状态圆点 _ = StartBackendConnectivityLoopAsync(_backendConnectivityCts.Token); } public SysUser? CurrentUser { get => _currentUser; set => SetProperty(ref _currentUser, value); } /// /// 后端连接状态:true=连接中,false=已断开 /// 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; /// /// 是否启用服务器连接(默认启用) /// 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 { new NavItem { Icon = "FileTreeOutline", Name = "功能菜单", ViewName = "MenuTreeView", Command = new DelegateCommand(it => _ = NavigateToViewAsync(CommonConst.MenuRegion, it.ViewName)) }, // 已隐藏开发调试用「菜单区域」「Tab区域」,日常仅保留「功能菜单」 (_menuTreeToggleNavItem = new NavItem { Icon = "ChevronDoubleLeft", Name = "折叠菜单", AlignBottom = true, IsActive = false, Command = ToggleMenuTreePanelCommand }), new NavItem { Icon = "Server", Name = "服务器设置", AlignBottom = true, IsActive = false, Command = OpenServerSettingsCommand }, new NavItem { Icon = "AccountCircleOutline", Name = "个人中心", AlignBottom = true, Command = new DelegateCommand(OnOpenOrActivateTab) }, new NavItem { Icon = StringUtil.ConvertHtmlEntityToUnicode(""), 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(); } /// /// 导航 /// /// 区域名称 /// 视图名称 /// private async Task NavigateToViewAsync(string regionName, string? viewName, INavigationParameters? parameters = null) { try { if (string.IsNullOrEmpty(regionName)) return; var tcs = new TaskCompletionSource(); // 视图为空 或者 视图未在容器中注册 if (string.IsNullOrEmpty(viewName) || !(_container as IContainerProvider).IsRegistered(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?.ToString() ?? "null"; var innerExMsg = result.Exception?.InnerException?.ToString() ?? "null"; _logger.Error($"导航失败: {viewName}, Region={regionName}, Exception={exMsg}, InnerException={innerExMsg}"); tcs.SetResult(false); } }, parameters); }, DispatcherPriority.Background); // 使用较低优先级 await tcs.Task; } catch (Exception ex) { _logger.Error($"导航异常: {ex.Message}"); } } /// /// 打开或激活一个标签页(若已打开则激活,否则新建) /// 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; } /// /// 刷新Tab /// /// 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); } /// /// TabItem关闭回调 /// 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().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().Publish(AppSession.CurrentUser!); Logout(); } // var messageBoxResult = HandyControl.Controls.MessageBox.Show( // $"确定退出登录吗?", // "确认登出", //MessageBoxButton.OKCancel, //MessageBoxImage.Warning); // if (messageBoxResult != MessageBoxResult.OK) return; // // 发布登出事件 // _eventAggregator.GetEvent().Publish(AppSession.CurrentUser!); // Logout(); } #region 主题 /// /// 打开系统设置 /// private void OpenAppSettings() { IsAppSettingsOpen = true; } /// /// 重置系统设置 /// private void ResetAppSettings() { // 重置为默认值 // new AppSettingsViewModel().Adapt(AppSettingsViewModel); AppSettingsViewModel = new AppSettingsViewModel(); AppSettingsViewModel.UpdateSkin(AppSettingsViewModel.SkinType!.Value); AppSettingsViewModel.UpdateAppSettings(); } /// /// 加载系统设置 /// 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(json); AppSettingsViewModel = appSettings.Adapt(); 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("JeecgIntegration:BaseUrl")?.TrimEnd('/'); var userListPath = _configuration.GetValue("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 ToggleMenuTreePanel() { IsMenuTreePanelCollapsed = !IsMenuTreePanelCollapsed; if (_menuTreeToggleNavItem == null) { return; } if (IsMenuTreePanelCollapsed) { _menuTreeToggleNavItem.Name = "展示菜单"; _menuTreeToggleNavItem.Icon = "ChevronDoubleRight"; } else { _menuTreeToggleNavItem.Name = "折叠菜单"; _menuTreeToggleNavItem.Icon = "ChevronDoubleLeft"; } } private void OpenServerSettings() { var parameters = new DialogParameters { { KnownDialogParameters.WindowName, DialogWindowNames.ChromeDialogWindow }, }; _dialogService.ShowDialog("ServerSettingsDialog", parameters, r => { if (r.Result == ButtonResult.OK) { var settings = ServerSettingsStore.Load(); IsServerConnectionEnabled = !settings.DisconnectConnection; if (settings.DisconnectConnection) { var signalService = _container.Resolve(); _ = signalService.DisconnectAsync(); } else { var signalService = _container.Resolve(); _ = signalService.ConnectUnifiedDeviceChannelAsync(CancellationToken.None); } } }); } /// /// 清空资源 /// private void Destroy(SysUser? sysUser = null) { if (_openOrActivateTabToken != null) { _eventAggregator .GetEvent() .Unsubscribe(_openOrActivateTabToken); _openOrActivateTabToken = null; } if (_tabClosedToken != null) { _eventAggregator .GetEvent() .Unsubscribe(_tabClosedToken); _tabClosedToken = null; } if (_refreshTabToken != null) { _eventAggregator .GetEvent() .Unsubscribe(_refreshTabToken); _refreshTabToken = null; } if (_loginOutToken != null) { _eventAggregator .GetEvent() .Unsubscribe(_loginOutToken); _loginOutToken = null; } if (_authService != null) { _authService.UserChanged -= OnUserChanged; } if (!_backendConnectivityCts.IsCancellationRequested) { _backendConnectivityCts.Cancel(); } _jeecgUserSyncCoordinator.Stop(); } } }