2026-04-28 10:23:58 +08:00
|
|
|
|
using Mapster;
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using Prism.Dialogs;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using YY.Admin;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using YY.Admin.Core;
|
|
|
|
|
|
using YY.Admin.Core.Const;
|
|
|
|
|
|
using YY.Admin.Core.Model;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
using YY.Admin.Core.Services;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using YY.Admin.Core.Session;
|
|
|
|
|
|
using YY.Admin.Core.Util;
|
|
|
|
|
|
using YY.Admin.Event;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using YY.Admin.Helper;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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<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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 16:05:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 为 true 时隐藏左侧功能菜单树(次级侧栏)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool _isMenuTreePanelCollapsed;
|
|
|
|
|
|
public bool IsMenuTreePanelCollapsed
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isMenuTreePanelCollapsed;
|
|
|
|
|
|
set => SetProperty(ref _isMenuTreePanelCollapsed, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private NavItem? _menuTreeToggleNavItem;
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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; }
|
2026-05-14 16:05:22 +08:00
|
|
|
|
public ICommand ToggleMenuTreePanelCommand { get; }
|
2026-04-28 10:23:58 +08:00
|
|
|
|
|
|
|
|
|
|
#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);
|
2026-05-14 16:05:22 +08:00
|
|
|
|
ToggleMenuTreePanelCommand = new DelegateCommand(ToggleMenuTreePanel);
|
2026-04-28 10:23:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化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);
|
|
|
|
|
|
|
2026-04-30 15:28:20 +08:00
|
|
|
|
// 首次按默认连接;后续按本地保存状态恢复
|
|
|
|
|
|
var settings = ServerSettingsStore.Load();
|
|
|
|
|
|
IsServerConnectionEnabled = !settings.DisconnectConnection;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 主窗口底部连接状态圆点
|
|
|
|
|
|
_ = 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;
|
|
|
|
|
|
|
2026-04-30 15:28:20 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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))
|
|
|
|
|
|
},
|
2026-05-18 15:55:11 +08:00
|
|
|
|
// 已隐藏开发调试用「菜单区域」「Tab区域」,日常仅保留「功能菜单」
|
2026-05-14 16:05:22 +08:00
|
|
|
|
(_menuTreeToggleNavItem = new NavItem {
|
|
|
|
|
|
Icon = "ChevronDoubleLeft",
|
|
|
|
|
|
Name = "折叠菜单",
|
|
|
|
|
|
AlignBottom = true,
|
|
|
|
|
|
IsActive = false,
|
|
|
|
|
|
Command = ToggleMenuTreePanelCommand
|
|
|
|
|
|
}),
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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(""),
|
|
|
|
|
|
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
|
|
|
|
|
|
{
|
2026-05-06 15:30:31 +08:00
|
|
|
|
var exMsg = result.Exception?.ToString() ?? "null";
|
|
|
|
|
|
var innerExMsg = result.Exception?.InnerException?.ToString() ?? "null";
|
|
|
|
|
|
_logger.Error($"导航失败: {viewName}, Region={regionName}, Exception={exMsg}, InnerException={innerExMsg}");
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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;
|
2026-04-30 15:28:20 +08:00
|
|
|
|
if (!IsServerConnectionEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Application.Current.Dispatcher.InvokeAsync(() => { IsBackendConnected = false; });
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(ConnectivityCheckIntervalSeconds), cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-04-28 10:23:58 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 16:05:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 折叠 / 展示左侧菜单树区域
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ToggleMenuTreePanel()
|
|
|
|
|
|
{
|
|
|
|
|
|
IsMenuTreePanelCollapsed = !IsMenuTreePanelCollapsed;
|
|
|
|
|
|
if (_menuTreeToggleNavItem == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (IsMenuTreePanelCollapsed)
|
|
|
|
|
|
{
|
|
|
|
|
|
_menuTreeToggleNavItem.Name = "展示菜单";
|
|
|
|
|
|
_menuTreeToggleNavItem.Icon = "ChevronDoubleRight";
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_menuTreeToggleNavItem.Name = "折叠菜单";
|
|
|
|
|
|
_menuTreeToggleNavItem.Icon = "ChevronDoubleLeft";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
private void OpenServerSettings()
|
|
|
|
|
|
{
|
2026-05-15 17:30:30 +08:00
|
|
|
|
var parameters = new DialogParameters
|
|
|
|
|
|
{
|
|
|
|
|
|
{ KnownDialogParameters.WindowName, DialogWindowNames.ChromeDialogWindow },
|
|
|
|
|
|
};
|
|
|
|
|
|
_dialogService.ShowDialog("ServerSettingsDialog", parameters, r =>
|
2026-04-30 15:28:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-28 10:23:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|