更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。

This commit is contained in:
geht
2026-04-28 10:23:58 +08:00
parent bbe46dcf2d
commit 142a0bdaba
1013 changed files with 41858 additions and 28 deletions

View File

@@ -0,0 +1,257 @@
using HandyControl.Themes;
using HandyControl.Tools;
using Mapster;
using Newtonsoft.Json;
using SqlSugar;
using System.IO;
using System.Windows;
using YY.Admin.Core;
using YY.Admin.Core.Const;
using YY.Admin.Core.EventBus;
using YY.Admin.Core.Helper;
using YY.Admin.Core.Model;
using YY.Admin.Core.Session;
using HcSkinType = HandyControl.Data.SkinType;
namespace YY.Admin.ViewModels
{
/// <summary>
/// 系统设置
/// </summary>
public class AppSettingsViewModel : BindableBase
{
/// <summary>
/// 皮肤类型
/// </summary>
private HcSkinType? _skinType;
public HcSkinType? SkinType
{
get => _skinType ??= GetDefaultSkinType();
set
{
if (SetProperty(ref _skinType, value) && value != null)
{
if (!SyncWithSystem)
{
UpdateSkin(value.Value);
UpdateAppSettings();
}
}
}
}
/// <summary>
/// 皮肤是否与系统同步
/// </summary>
private bool _syncWithSystem;
public bool SyncWithSystem
{
get => _syncWithSystem;
set
{
if (SetProperty(ref _syncWithSystem, value))
{
RaisePropertyChanged(nameof(IsNotSyncWithSystem));
if (value)
{
SkinType = GetSkinTypeBySystem();
UpdateSkin(SkinType.Value);
}
UpdateAppSettings();
}
}
}
public bool IsNotSyncWithSystem => !SyncWithSystem;
/// <summary>
/// 是否显示TabControl
/// </summary>
private bool _isTabControlVisible = true;
public bool IsTabControlVisible
{
get => _isTabControlVisible;
set {
if (SetProperty(ref _isTabControlVisible, value))
{
UpdateAppSettings();
}
}
}
/// <summary>
/// 获取默认皮肤类型
/// </summary>
/// <returns></returns>
private HcSkinType GetDefaultSkinType()
{
return SyncWithSystem ? GetSkinTypeBySystem() : (HcSkinType)Properties.AppSettings.Default.SkinType;
}
/// <summary>
/// 根据系统主题配置获取对应皮肤类型
/// </summary>
/// <returns></returns>
public static HcSkinType GetSkinTypeBySystem()
{
return SystemHelper.DetermineIfInLightThemeMode()
? HcSkinType.Default
: HcSkinType.Dark;
}
/// <summary>
/// 从ResourceDictionary中获取皮肤类型
/// </summary>
/// <returns></returns>
public static HcSkinType GetSkinType()
{
var _theme = Application.Current.Resources.MergedDictionaries.OfType<Theme>().FirstOrDefault();
if (_theme != null)
{
return _theme?.Skin ?? HcSkinType.Default;
}
var skin = Application.Current.Resources.MergedDictionaries
.Where(it => it.Source.OriginalString.StartsWith("pack://application:,,,/HandyControl;component/Themes/Skin"))
.FirstOrDefault();
if (skin != null)
{
string OriginalString = skin.Source.OriginalString;
// 4Skin的长度
int skinStart = OriginalString.IndexOf("Skin") + 4;
int dotIndex = OriginalString.LastIndexOf('.');
string skinName = OriginalString.Substring(skinStart, dotIndex - skinStart);
return (HcSkinType)Enum.Parse(typeof(HcSkinType), skinName);
}
return HcSkinType.Default;
}
/// <summary>
/// 更新皮肤
/// </summary>
/// <param name="skinType"></param>
public static void UpdateSkin(HcSkinType skinType)
{
// 配置方式:<hc:Theme />
var theme = Application.Current.Resources.MergedDictionaries.OfType<Theme>().FirstOrDefault();
if (theme != null)
{
//theme.Skin = skin;
theme.MergedDictionaries[0].Source = ResourceHelper.GetSkin(skinType).Source;
theme.MergedDictionaries[1].Source = ResourceHelper.GetStandaloneTheme().Source;
AddResourceDictionary(skinType);
return;
}
// 配置方式:<ResourceDictionary />
var skins = Application.Current.Resources.MergedDictionaries
.Where(it => it.Source.OriginalString.StartsWith("pack://application:,,,/HandyControl;component/Themes/"))
.ToList();
if (skins == null || skins.Count < 2)
{
return;
}
skins[0].Source = ResourceHelper.GetSkin(skinType).Source;
skins[1].Source = ResourceHelper.GetStandaloneTheme().Source;
// 添加除HandyControl之外的资源字典
AddResourceDictionary(skinType);
ContainerLocator.Container.Resolve<IEventAggregator>().GetEvent<ThemeChangedEvent>().Publish(skinType);
}
/// <summary>
/// 获取文件完整路径
/// </summary>
/// <returns></returns>
public static string GetFilePath()
{
string filePathSuffix = string.Format(CommonConst.AppSettingsFilePath, AppSession.CurrentUser!.Account);
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePathSuffix);
}
/// <summary>
/// 更新系统设置
/// 默认配置由MainWindowViewModel保存
/// </summary>
/// <param name="appSettingsViewModel">系统设置VM</param>
public void UpdateAppSettings()
{
var filePath = GetFilePath();
if (!File.Exists(filePath))
{
return;
}
SaveAppSettings(filePath);
}
/// <summary>
/// 保存系统设置
/// </summary>
/// <param name="filePath"></param>
public void SaveAppSettings(string? filePath = null)
{
filePath ??= GetFilePath();
var directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory!);
}
var appSettings = this.Adapt<AppSettings>();
var json = JsonConvert.SerializeObject(appSettings, Formatting.Indented);
// 保存到文件
File.WriteAllText(filePath, json);
}
/// <summary>
/// 添加资源字典
/// </summary>
/// <param name="skinType"></param>
private static void AddResourceDictionary(HcSkinType skinType)
{
// 先移除上一次主题添加的资源字典,否则会影响当前主题
RemoveResourceDictionaryBySource("/Resources/Styles/HandyControl/Colors.xaml");
RemoveResourceDictionaryBySource("/Resources/Styles/HandyControl/ColorsDark.xaml");
RemoveResourceDictionaryBySource("/Resources/Styles/HandyControl/ColorsViolet.xaml");
RemoveResourceDictionaryBySource("/Resources/Styles/HandyControl/Brushes.xaml");
if (skinType == HcSkinType.Default)
{
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary
{
Source = new Uri("/Resources/Styles/HandyControl/Colors.xaml", UriKind.Relative)
});
} else if (skinType == HcSkinType.Dark)
{
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary
{
Source = new Uri("/Resources/Styles/HandyControl/ColorsDark.xaml", UriKind.Relative)
});
} else if (skinType == HcSkinType.Violet)
{
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary
{
Source = new Uri("/Resources/Styles/HandyControl/ColorsViolet.xaml", UriKind.Relative)
});
}
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary
{
Source = new Uri("/Resources/Styles/HandyControl/Brushes.xaml", UriKind.Relative)
});
}
/// <summary>
/// 移除资源字典
/// </summary>
/// <param name="sourceEndsWith"></param>
private static void RemoveResourceDictionaryBySource(string sourceEndsWith)
{
var dictToRemove = Application.Current.Resources.MergedDictionaries
.FirstOrDefault(d => d.Source?.OriginalString?.EndsWith(sourceEndsWith) == true);
if (dictToRemove != null)
{
Application.Current.Resources.MergedDictionaries.Remove(dictToRemove);
}
}
}
}

View File

@@ -0,0 +1,592 @@
using HandyControl.Data;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using YY.Admin.Core;
using YY.Admin.Core.Const;
using YY.Admin.Core.EventBus;
using YY.Admin.Core.Session;
using YY.Admin.Services.Service.Auth;
using YY.Admin.Views;
namespace YY.Admin.ViewModels
{
public class BaseViewModel : BindableBase, IDestructible
{
protected bool _isLoading;
private string _title = string.Empty;
// 添加Token检查定时器
private static DispatcherTimer? _tokenCheckTimer;
/// <summary>
///加载
/// </summary>
public bool IsLoading
{
get => _isLoading;
set
{
if (SetProperty(ref _isLoading, value))
{
// 触发一个虚拟方法,让派生类可以响应
OnIsLoadingChanged();
}
}
}
public bool IsNotLoading => !IsLoading;
/// <summary>
///标题
/// </summary>
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
private readonly IDialogService _dialogService;
protected readonly IRegionManager _regionManager;
/// <summary>
/// 日志对象
/// </summary>
protected ILoggerService _logger;
/// <summary>
/// 依赖注入容器
/// </summary>
protected IContainerExtension _container { get; }
/// <summary>
/// 事件汇总器,用于发布或订阅事件
/// </summary>
protected IEventAggregator _eventAggregator;
/// <summary>
/// 当前已登录用户信息
/// </summary>
protected static UserContext? _userContext { get; set; }
private SkinType _skinType;
public SkinType SkinType { get => _skinType; set => SetProperty(ref _skinType, value); }
private SubscriptionToken? _themeChangedEventToken;
protected BaseViewModel(IContainerExtension container, IRegionManager regionManager)
{
_container = container;
_logger = container.Resolve<ILoggerService>();
_eventAggregator = container.Resolve<IEventAggregator>();
this._dialogService = container.Resolve<IDialogService>();
this._regionManager = regionManager;
_themeChangedEventToken = _eventAggregator.GetEvent<ThemeChangedEvent>().Subscribe(skinType => SkinType = skinType);
}
#region
// 启动定时器的方法
public static void StartTokenCheckTimer()
{
if (_tokenCheckTimer == null)
{
_tokenCheckTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMinutes(1)
};
_tokenCheckTimer.Tick += CheckTokenExpiration;
// 捕获全局用户输入事件
EventManager.RegisterClassHandler(typeof(Window),
UIElement.PreviewMouseDownEvent,
new MouseButtonEventHandler(OnUserActivity));
EventManager.RegisterClassHandler(typeof(Window),
UIElement.PreviewKeyDownEvent,
new KeyEventHandler(OnUserActivity));
}
if (!_tokenCheckTimer.IsEnabled)
{
_tokenCheckTimer.Start();
}
}
// 停止定时器的方法
public static void StopTokenCheckTimer()
{
if (_tokenCheckTimer != null && _tokenCheckTimer.IsEnabled)
{
_tokenCheckTimer.Stop();
}
}
private static void OnUserActivity(object sender, EventArgs e)
{
var authService = ContainerLocator.Current.Resolve<ISysAuthService>();
authService.RefreshToken(UserContext?.Token?.AccessToken);
}
/// <summary>
///定时器检查方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CheckTokenExpiration(object? sender, EventArgs e)
{
// 确保在主线程执行
Application.Current.Dispatcher.Invoke(() =>
{
if (UserContext == null || UserContext.Token == null)
{
// 如果没有用户信息,停止定时器
StopTokenCheckTimer();
return;
}
var authService = ContainerLocator.Current.Resolve<ISysAuthService>();
if (!authService.ValidateToken(UserContext.Token.AccessToken))
{
// 停止定时器防止重复触发
StopTokenCheckTimer();
// 显示过期提示
Application.Current.Dispatcher.Invoke(() =>
{
var currentWindow = Application.Current.MainWindow;
if (currentWindow != null)
{
var viewModel = currentWindow.DataContext as BaseViewModel;
viewModel?.ForceLogout("您的登录已过期,请重新登录");
}
});
}
});
}
/// <summary>
/// 强制退出方法
/// </summary>
/// <param name="message"></param>
public async void ForceLogout(string message)
{
// 先显示对话框
await ShowAlertAsync(message);
// 发布登出事件
_eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Publish(AppSession.CurrentUser!);
Logout();
}
/// <summary>
/// 登出
/// </summary>
public void Logout()
{
// 停止定时器
StopTokenCheckTimer();
// 清除用户上下文
ClearUserContext();
// 再执行退出操作
var authService = _container.Resolve<ISysAuthService>();
authService.LogoutAsync();
// 当前窗口
var mainWindow = Application.Current.MainWindow;
// 跳转到登录页
var loginWindow = _container.Resolve<LoginWindow>();
Application.Current.MainWindow = loginWindow;
loginWindow.Show();
mainWindow.Close();
// 移除所有区域
RemoveAllRegion();
}
public static UserContext? UserContext
{
get => _userContext;
private set
{
if (_userContext != value)
{
_userContext = value;
// 通知静态属性变化(需要额外实现)
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(nameof(UserContext)));
}
}
}
// 静态属性变更通知事件
public static event EventHandler<PropertyChangedEventArgs>? StaticPropertyChanged;
/// <summary>
/// 设置用户上下文
/// </summary>
/// <param name="user"></param>
/// <param name="token"></param>
public static void SetUserContext(SysUser user, UserToken token)
{
UserContext = new UserContext
{
UserId = user.Id,
TenantId = user.TenantId!.Value,
Account = user.Account,
AccountType = user.AccountType,
RealName = user.RealName,
IsSuperAdmin = user.AccountType == AccountTypeEnum.SuperAdmin,
OrgId = user.OrgId,
Token = token
};
}
/// <summary>
/// 清除用户上下文
/// </summary>
public static void ClearUserContext()
{
UserContext = null;
}
#endregion
#region
/// <summary>
/// 导航到指定Page
/// </summary>
/// <param name="regionName">区域名称</param>
/// <param name="target">目标Page名称</param>
/// <param name="navigationCallback">导航回调函数</param>
protected void RequestNavigate(string regionName, string target, Action<NavigationResult> navigationCallback = null)
{
IRegion region = _regionManager.Regions[regionName];
if (region == null) return;
region.RemoveAll();
if (navigationCallback != null)
region.RequestNavigate(target, navigationCallback);
else
region.RequestNavigate(target);
}
protected void SetRegionManager(DependencyObject target)
{
RegionManager.SetRegionManager(target, _regionManager);
}
/// <summary>
/// 安全导航到指定视图
/// </summary>
protected void SafeNavigate(string regionName, string targetView, NavigationParameters parameters = null)
{
ExecuteWithExceptionHandling(() =>
{
// 导航页面
_regionManager.RequestNavigate(regionName, targetView, parameters);
});
}
protected void ExecuteWithExceptionHandling(Action action)
{
try
{
action();
}
catch (Exception ex)
{
HandleException(ex);
}
}
private void HandleException(Exception ex)
{
LogError("操作发生异常", ex);
// ShowErrorDialog($"操作异常: {ex.Message}");
}
#endregion
#region
/// <summary>
/// 弹框提示
/// </summary>
/// <param name="message">消息内容</param>
/// <param name="callback">回调函数</param>
protected void Alert(string message, Action<IDialogResult>? callback = null)
{
_dialogService.ShowDialog("AlertDialog", new DialogParameters($"message={message}"), callback);
}
protected Task ShowAlertAsync(string message)
{
var tcs = new TaskCompletionSource<bool>();
Alert(message,result => tcs.SetResult(true));
return tcs.Task;
}
/// <summary>
/// 弹出消息提示框1秒钟自动关闭
/// </summary>
/// <param name="message">消息内容</param>
/// <param name="messageType">消息类型</param>
/// <param name="callback">回调函数</param>
protected void AlertPopup(string message, MessageTypeEnum messageType = MessageTypeEnum.Success, Action<IDialogResult> callback = null)
{
switch (messageType)
{
case MessageTypeEnum.Success:
_dialogService.ShowDialog("SuccessDialog", new DialogParameters($"message={message}"), callback);
break;
case MessageTypeEnum.Error:
_dialogService.ShowDialog("ErrorDialog", new DialogParameters($"message={message}"), callback);
break;
case MessageTypeEnum.Warning:
_dialogService.ShowDialog("WarningDialog", new DialogParameters($"message={message}"), callback);
break;
default:
_dialogService.ShowDialog("SuccessDialog", new DialogParameters($"message={message}"), callback);
break;
}
}
/// <summary>
/// 确认框提示
/// </summary>
/// <param name="message">确认框消息</param>
/// <param name="callback">回调函数</param>
protected void Confirm(string message, Action<IDialogResult>? callback = null)
{
_dialogService.ShowDialog("ConfirmDialog", new DialogParameters($"message={message}"), callback);
}
/// <summary>
/// 异步确认框
/// </summary>
/// <param name="message">确认消息</param>
/// <returns>用户是否确认</returns>
protected Task<bool> ConfirmAsync(string message)
{
var tcs = new TaskCompletionSource<bool>();
Confirm(message, result =>
{
// 从 IDialogResult 中提取用户选择
var userConfirmed = result.Result == ButtonResult.Yes;
tcs.SetResult(userConfirmed);
});
return tcs.Task;
}
#endregion
#region
protected void LogInfo(string message, [CallerMemberName] string caller = "")
=> _logger.Information($"[{GetType().Name}.{caller}] {message}");
protected void LogWarning(string message, [CallerMemberName] string caller = "")
=> _logger.Warning($"[{GetType().Name}.{caller}] {message}");
protected void LogError(string message, Exception ex = null, [CallerMemberName] string caller = "")
=> _logger.Error($"[{GetType().Name}.{caller}] {message}", ex);
#endregion
/// <summary>
/// 异常处理封装
/// </summary>
/// <param name="asyncAction"></param>
/// <param name="onFinally"></param>
/// <returns></returns>
protected async Task ExecuteAsync(Func<Task> asyncAction, Action? onFinally = null)
{
try
{
IsLoading = true;
await asyncAction();
}
catch (Exception ex)
{
HandleException(ex);
}
finally
{
IsLoading = false;
onFinally?.Invoke();
}
}
/// <summary>
/// 窗口管理
/// </summary>
/// <typeparam name="T"></typeparam>
protected void SwitchMainWindow<T>() where T : Window
{
ExecuteWithExceptionHandling(() =>
{
var currentWindow = Application.Current.MainWindow;
var newWindow = _container.Resolve<T>();
Application.Current.MainWindow?.Hide();
Application.Current.MainWindow = newWindow;
newWindow.Show();
currentWindow?.Close();
});
}
/// <summary>
/// 资源管理
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resourceKey"></param>
/// <returns></returns>
/// <exception cref="ResourceNotFoundException"></exception>
protected T GetResource<T>(string resourceKey)
{
if (Application.Current.TryFindResource(resourceKey) is T resource)
{
return resource;
}
throw new ResourceNotFoundException($"资源未找到: {resourceKey}");
}
/// <summary>
/// 移除区域,包括清空区域内的内容,导航日志
/// </summary>
/// <param name="regionName">区域名称</param>
protected void RemoveRegion(string regionName)
{
if (string.IsNullOrEmpty(regionName))
{
return;
}
if (!_regionManager.Regions.ContainsRegionWithName(regionName))
{
return;
}
var region = _regionManager.Regions[regionName];
RemoveRegion(region);
}
/// <summary>
/// 移除区域,包括清空区域内的内容,导航日志
/// </summary>
/// <param name="region"></param>
protected void RemoveRegion(IRegion region)
{
if (region == null)
{
return;
}
// 清空区域内容
region.RemoveAll();
// 清空导航历史
region.NavigationService?.Journal?.Clear();
// 从区域管理器中移除区域
_regionManager.Regions.Remove(region.Name);
_logger.Debug($"移除区域{region.Name}");
}
/// <summary>
/// 移除所有区域
/// </summary>
protected void RemoveAllRegion()
{
foreach (var region in _regionManager.Regions.ToList())
{
RemoveRegion(region);
}
_logger.Debug($"移除所有区域");
}
/// <summary>
/// 移除区域视图
/// </summary>
/// <param name="regionName">区域名称</param>
/// <param name="viewName">视图名称</param>
protected void RemoveView(string regionName, string? viewName)
{
if (string.IsNullOrEmpty(regionName) || string.IsNullOrEmpty(viewName))
{
return;
}
if (!_regionManager.Regions.ContainsRegionWithName(regionName))
{
return;
}
var region = _regionManager.Regions[regionName];
// 查找并移除指定的视图
var viewToRemove = region.Views.FirstOrDefault(v =>
{
// 根据视图名称或类型来匹配
var viewType = v.GetType();
return viewType.Name == viewName || viewType.Name == viewName + "View";
});
if (viewToRemove != null)
{
region.Remove(viewToRemove);
_logger.Debug($"从区域{regionName}移除视图{viewName}");
}
}
/// <summary>
/// 移除区域ContentRegion视图
/// </summary>
/// <param name="viewName"></param>
protected void RemoveContentRegionView(string? viewName)
{
RemoveView(CommonConst.ContentRegion, viewName);
}
/// <summary>
/// 移除区域所有视图
/// </summary>
/// <param name="regionName">区域名称</param>
protected void RemoveAllView(string regionName)
{
if (string.IsNullOrEmpty(regionName))
{
return;
}
if (!_regionManager.Regions.ContainsRegionWithName(regionName))
{
return;
}
var region = _regionManager.Regions[regionName];
region.RemoveAll();
_logger.Debug($"移除区域{regionName}所有视图");
}
/// <summary>
/// 当 IsLoading 变化时的回调方法
/// </summary>
protected virtual void OnIsLoadingChanged()
{
RaisePropertyChanged(nameof(IsNotLoading));
}
/// <summary>
/// 清理资源
/// </summary>
protected virtual void CleanUp()
{
}
/// <summary>
/// 清空资源
/// 执行时机从Region移除View 或 导航到另一个View时
/// </summary>
public void Destroy()
{
if (_themeChangedEventToken != null)
{
_eventAggregator.GetEvent<ThemeChangedEvent>().Unsubscribe(_themeChangedEventToken);
_themeChangedEventToken = null;
}
// 清理派生类的资源
CleanUp();
}
}
// 自定义异常类型
public class ResourceNotFoundException : Exception
{
public ResourceNotFoundException(string message) : base(message) { }
}
}

View File

@@ -0,0 +1,376 @@
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Threading;
using YY.Admin.Core;
using YY.Admin.Core.Util;
using YY.Admin.Event;
using YY.Admin.Module;
using YY.Admin.Services;
using YY.Admin.Services.Service.Menu;
namespace YY.Admin.ViewModels.Control
{
/// <summary>
/// 菜单选项
/// </summary>
public class MenuItem : TabSource
{
public MenuItem? Parent { get; set; } // 父节点引用
public ObservableCollection<MenuItem> Children { get; set; } = [];
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
}
public class MenuTreeViewModel : BaseViewModel
{
private static readonly Dictionary<string, string> RouteToViewMap = new(StringComparer.OrdinalIgnoreCase)
{
// 已实现页面:仪表盘
["DashboardView"] = "DashboardView",
["/dashboard"] = "DashboardView",
["/dashboard/index"] = "DashboardView",
["/home/index"] = "DashboardView",
["dashboard"] = "DashboardView",
["home"] = "DashboardView",
// 已实现页面:账号管理
["UserManagementView"] = "UserManagementView",
["/system/user"] = "UserManagementView",
["/system/user/index"] = "UserManagementView",
["sysUser"] = "UserManagementView",
// 已实现页面:数据字典
["DataDictionaryManagementView"] = "DataDictionaryManagementView",
["/system/dict"] = "DataDictionaryManagementView",
["/system/dict/index"] = "DataDictionaryManagementView",
["/platform/dict"] = "DataDictionaryManagementView",
["sysDict"] = "DataDictionaryManagementView",
// 已实现页面:角色管理
["RoleManagementView"] = "RoleManagementView",
["/system/role"] = "RoleManagementView",
["/system/role/index"] = "RoleManagementView",
["sysRole"] = "RoleManagementView",
// 已实现页面:租户管理
["TenantManagementView"] = "TenantManagementView",
["/system/tenant"] = "TenantManagementView",
["/system/tenant/index"] = "TenantManagementView",
["/platform/tenant"] = "TenantManagementView",
["sysTenant"] = "TenantManagementView"
};
private MenuItem? _selectedMenuItem;
public MenuItem? SelectedMenuItem
{
get => _selectedMenuItem;
set => SetProperty(ref _selectedMenuItem, value);
}
private readonly ISysMenuService _sysMenuService;
public ObservableCollection<MenuItem> MenuItems { get; } = [];
public DelegateCommand<MenuItem> NavigateCommand { get; }
private SubscriptionToken? tabSelectedToken;
private SubscriptionToken? tabClosedToken;
public MenuTreeViewModel(
ISysMenuService sysMenuService,
IContainerExtension _container,
IRegionManager regionManager) : base(_container, regionManager)
{
_sysMenuService = sysMenuService;
// 异步初始化菜单
LoadMenuAsync();
NavigateCommand = new DelegateCommand<MenuItem>(OpenOrActivateTab);
// 订阅事件
tabSelectedToken = _eventAggregator.GetEvent<TabSelectedEvent>().Subscribe(OnTabSelected);
tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed);
}
public void OpenOrActivateTab(MenuItem menuItem)
{
// 发布事件
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(menuItem);
}
private void OnTabSelected(TabItemModel tab)
{
if (tab.TabSource is MenuItem menuItem)
{
// 取消上一个选中菜单选中状态
SelectedMenuItem?.IsSelected = false;
SelectedMenuItem = menuItem;
// 设置菜单选中
SelectedMenuItem.IsSelected = true;
// 展开父节点
ToggleParents(SelectedMenuItem, true);
}
}
private void OnTabClosed(TabItemModel tab)
{
if (tab.TabSource is MenuItem menuItem)
{
// 折叠父节点
ToggleParents(menuItem, false);
// 取消菜单选中
menuItem?.IsSelected = false;
}
}
/// <summary>
/// 异步加载菜单【后续使用缓存以及权限管理】
/// </summary>
private async void LoadMenuAsync()
{
try
{
// 异步获取菜单数据
var menuTree = await _sysMenuService.GetLoginMenuTree();
// 转换菜单数据
ConvertMenuTreeToViewModel(menuTree);
// 默认导航
ScheduleDefaultNavigation();
}
catch (Exception ex)
{
_logger.Error($"菜单加载失败: {ex.Message}", ex);
// 显示错误菜单项
MenuItems.Add(new MenuItem
{
Name = "菜单加载失败",
Icon = "ErrorOutline",
ViewName = "ErrorView",
Children = { new MenuItem { Name = "点击重试", Icon = "Refresh" } }
});
}
}
/// <summary>
/// 将服务层菜单树转换为视图模型
/// </summary>
private void ConvertMenuTreeToViewModel(List<MenuOutput> menuTree)
{
// 过滤并排序菜单项:只包含目录和菜单类型,排除按钮类型,并按排序号排序
var rootMenus = menuTree
.Where(m => m.Type == MenuTypeEnum.Dir || m.Type == MenuTypeEnum.Menu)
.Where(m => m.Status == StatusEnum.Enable) // 只包含启用状态的菜单
.Where(m => !(m?.IsHide ?? false)) // 排除隐藏的菜单
.OrderBy(m => m.OrderNo)
.ToList();
// 递归转换菜单项
void ConvertMenu(MenuOutput source, MenuItem target, MenuItem? parent = null)
{
target.Name = source?.Title ?? source?.Name;
target.Icon = ConvertHtmlEntityToUnicode(source?.Icon ?? "&#xe810;");
target.ViewName = ResolveViewName(source); // 将菜单路由映射到已注册的WPF视图
target.Parent = parent; // 设置父节点
// 添加子菜单(如果有)
if (source.Children != null && source.Children.Any())
{
// 过滤并排序子菜单
var childMenus = source.Children
.Where(c => c.Type == MenuTypeEnum.Dir || c.Type == MenuTypeEnum.Menu) // 子菜单支持目录和菜单两种类型
.Where(c => c.Status == StatusEnum.Enable)
.Where(c => !(c?.IsHide ?? false))
.OrderBy(c => c.OrderNo)
.ToList();
foreach (var child in childMenus)
{
var childItem = new MenuItem();
ConvertMenu(child, childItem, target);
target.Children.Add(childItem);
}
}
}
// 处理每个根菜单
foreach (var root in rootMenus)
{
var rootItem = new MenuItem();
ConvertMenu(root, rootItem);
// 如果根菜单是目录但没有子菜单,则不显示
if (root.Type == MenuTypeEnum.Dir && !rootItem.Children.Any())
continue;
MenuItems.Add(rootItem);
}
}
/// <summary>
/// 解析菜单对应的视图名称
/// </summary>
private string? ResolveViewName(MenuOutput? menu)
{
if (menu == null)
return null;
// 目录节点不参与内容区导航
if (menu.Type == MenuTypeEnum.Dir)
return null;
// 依次尝试 Path / Component / Name / Title兼容不同来源的菜单数据
var candidates = new[]
{
menu.Path,
menu.Component,
menu.Name,
menu.Title
};
foreach (var candidate in candidates)
{
if (string.IsNullOrWhiteSpace(candidate))
continue;
if (RouteToViewMap.TryGetValue(candidate.Trim(), out var viewName))
return viewName;
}
// 保留原始Path若未注册将统一展示NotFoundView
return menu.Path;
}
private string ConvertHtmlEntityToUnicode(string htmlEntity)
{
if (string.IsNullOrEmpty(htmlEntity))
return "\ue7c6"; // 默认图标
return StringUtil.ConvertHtmlEntityToUnicode(htmlEntity);
}
/// <summary>
/// 安排默认导航
/// </summary>
private void ScheduleDefaultNavigation()
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 默认菜单
var defaultMenuItem = GetFirstLeaf(MenuItems.FirstOrDefault());
if (defaultMenuItem != null)
{
// Tab不允许关闭
defaultMenuItem.IsClosable = false;
// 导航菜单
OpenOrActivateTab(defaultMenuItem);
}
}
catch (Exception ex)
{
_logger.Error($"默认导航失败: {ex.Message}", ex);
}
}), DispatcherPriority.ApplicationIdle);
}
private MenuItem? GetFirstLeaf(MenuItem? menu)
{
if (menu == null)
return null;
if (menu.Children == null || menu.Children.Count == 0)
return menu; // 自己就是叶子节点
// 递归向下找第一个叶子
return GetFirstLeaf(menu.Children.FirstOrDefault());
}
public void ToggleParents(MenuItem? item, bool IsExpanded)
{
if (item == null)
{
return;
}
var parent = item.Parent;
while (parent != null)
{
parent.IsExpanded = IsExpanded; // 展开父节点
if (!IsExpanded)
{
parent.IsSelected = IsExpanded;
}
parent = parent.Parent;
}
}
/// <summary>
/// 清空资源
/// </summary>
protected override void CleanUp()
{
if (tabSelectedToken != null)
{
_eventAggregator
.GetEvent<TabSelectedEvent>()
.Unsubscribe(tabSelectedToken);
tabSelectedToken = null;
}
if (tabClosedToken != null)
{
_eventAggregator
.GetEvent<TabClosedEvent>()
.Unsubscribe(tabClosedToken);
tabClosedToken = null;
}
}
/// <summary>
/// 根据ViewName同步选中对应的菜单项
/// </summary>
//private MenuItem? GetSelectedMenuItem(string? viewName)
//{
// if (string.IsNullOrEmpty(viewName))
// return null;
// // 递归查找匹配的菜单项
// return FindMenuItemByViewName(MenuItems, viewName);
//}
/// <summary>
/// 递归查找匹配ViewName的菜单项
/// </summary>
//private MenuItem? FindMenuItemByViewName(ObservableCollection<MenuItem> menuItems, string viewName)
//{
// foreach (var menuItem in menuItems)
// {
// // 如果当前菜单项匹配
// if (menuItem.ViewName == viewName)
// return menuItem;
// // 递归查找子菜单
// if (menuItem.Children?.Count > 0)
// {
// var found = FindMenuItemByViewName(menuItem.Children, viewName);
// if (found != null)
// return found;
// }
// }
// return null;
//}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YY.Admin.ViewModels.Control
{
public class PaginationDataGridViewModel<T> : BindableBase
{
private int _pageIndex = 1;
private int _dataCountPerPage = 10;
private int _totalCount;
private int _maxPageCount;
private ObservableCollection<T> _data;
//private string _pageInfo;
public int MyProperty { get; private set; }
public int PageIndex
{
get => _pageIndex;
set => SetProperty(ref _pageIndex, value);
}
public int DataCountPerPage
{
get => _dataCountPerPage;
set => SetProperty(ref _dataCountPerPage, value);
}
public int TotalCount
{
get => _totalCount;
set => SetProperty(ref _totalCount, value);
}
public int MaxPageCount
{
get => _maxPageCount;
set => SetProperty(ref _maxPageCount, value);
}
public ObservableCollection<T> Data
{
get => _data;
set => SetProperty(ref _data, value);
}
//public string PageInfo
//{
// get => _pageInfo;
// set => SetProperty(ref _pageInfo, value);
//}
// 页面大小选项
public Dictionary<int, string> PageSizes { get; } = new Dictionary<int, string>
{
{ 10, "10条/页" },
{ 20, "20条/页" },
{ 30, "30条/页" },
{ 40, "40条/页" },
{ 50, "50条/页" },
{ 100, "100条/页" }
};
public DelegateCommand PageUpdatedCmd { get; private set; }
public DelegateCommand PageSizeUpdatedCmd { get; private set; }
private Func<Task<(IEnumerable<T> data, int totalCount)>> _fetchData;
public PaginationDataGridViewModel(Func<Task<(IEnumerable<T> data, int totalCount)>> fetchData)
{
_fetchData = fetchData;
PageUpdatedCmd = new DelegateCommand(async () => await LoadDataAsync());
PageSizeUpdatedCmd = new DelegateCommand(async () => await LoadDataAsync());
// 初始化时加载数据
//_ = LoadData();
}
public async Task LoadDataAsync()
{
try
{
var (data, totalCount) = await _fetchData();
Data = new ObservableCollection<T>(data);
TotalCount = totalCount;
// 通知分页总数变化
//RaisePropertyChanged(nameof(MaxPageCount));
MaxPageCount = totalCount == 0 ? 1 : (int)Math.Ceiling((double)totalCount / DataCountPerPage);
// 更新分页信息
//PageInfo = $"共 {_totalCount} 条";
} catch (OperationCanceledException)
{
// 查询被取消,保持表格不变
return;
}
}
}
}

View File

@@ -0,0 +1,172 @@
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Kernel;
using System.Collections.ObjectModel;
using System.Windows.Input;
using YY.Admin.Core;
using YY.Admin.Services.Service.User;
namespace YY.Admin.ViewModels
{
public class StatisticCard
{
public string Title { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
public string Color { get; set; } = string.Empty;
public string Trend { get; set; } = string.Empty;
}
public class RecentActivity
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public DateTime Time { get; set; }
public string Icon { get; set; } = string.Empty;
}
public class DashboardViewModel : BaseViewModel
{
private readonly ISysUserService _userService;
public float[] Values1 { get; set; } = FetchVales(0);
public float[] Values2 { get; set; } = FetchVales(-0.15f);
public ICommand PointMeasuredCommand { get; }
public DashboardViewModel(
ISysUserService userService,
IContainerExtension _container,
IRegionManager regionManager
) : base(_container, regionManager)
{
// _eventAggregator.GetEvent<ThemeChangedEvent>().Subscribe(ApplyTheme);
_userService = userService;
PointMeasuredCommand = new DelegateCommand<ChartPoint>(OnPointMeasured);
_ = LoadDashboardDataAsync();
}
public ObservableCollection<StatisticCard> StatisticCards { get; } = new();
public ObservableCollection<RecentActivity> RecentActivities { get; } = new();
private async Task LoadDashboardDataAsync()
{
IsLoading = true;
try
{
// 加载统计数据
var users = await _userService.GetUsersAsync();
StatisticCards.Clear();
StatisticCards.Add(new StatisticCard
{
Title = "总用户数",
Value = users.Count.ToString(),
Icon = "UserOutlined",
Color = "#1890ff",
Trend = "+12%"
});
StatisticCards.Add(new StatisticCard
{
Title = "活跃用户",
Value = users.Count(u => u.Status== StatusEnum.Enable).ToString(),
Icon = "UserCheckOutlined",
Color = "#52c41a",
Trend = "+8%"
});
StatisticCards.Add(new StatisticCard
{
Title = "今日访问",
Value = "1,234",
Icon = "EyeOutlined",
Color = "#faad14",
Trend = "+5%"
});
StatisticCards.Add(new StatisticCard
{
Title = "系统消息",
Value = "56",
Icon = "MessageOutlined",
Color = "#f5222d",
Trend = "+2"
});
// 加载最近活动
RecentActivities.Clear();
RecentActivities.Add(new RecentActivity
{
Title = "用户登录",
Description = "管理员 admin 登录系统",
Time = DateTime.Now.AddMinutes(-5),
Icon = "LoginOutlined"
});
RecentActivities.Add(new RecentActivity
{
Title = "数据更新",
Description = "用户数据已同步更新",
Time = DateTime.Now.AddMinutes(-15),
Icon = "SyncOutlined"
});
RecentActivities.Add(new RecentActivity
{
Title = "系统备份",
Description = "数据库备份已完成",
Time = DateTime.Now.AddHours(-2),
Icon = "DatabaseOutlined"
});
}
finally
{
IsLoading = false;
}
}
private static float[] FetchVales(float offset)
{
var values = new List<float>();
// the EasingFunctions.BounceInOut, is just
// a function that looks nice!
var fx = EasingFunctions.BounceInOut;
var x = 0f;
while (x <= 1)
{
values.Add(fx(x + offset));
x += 0.025f;
}
return [.. values];
}
private void OnPointMeasured(ChartPoint point)
{
// each point will have a different delay depending on its index
var index = point.Context.Entity.MetaData!.EntityIndex; // the index of the point in the data source
var delay = index / (float)Values1.Length;
// the animation takes a function, that represents the normalized progress of the animation
// the parameter is the normalized time of the animation, it goes from 0 to 1
// the function must return a value from 0 to 1, where 0 is the initial state
// and 1 is the final state
var duration = TimeSpan.FromSeconds(3);
var animation = new Animation(t => DelayedEase(t, delay), duration);
point.Context.Visual?.SetTransition(animation);
}
private static float DelayedEase(float t, float delay)
{
if (t <= delay) return 0f;
var remappedT = (t - delay) / (1f - delay);
var baseEasing = EasingFunctions.BuildCustomElasticOut(1.5f, 0.60f);
return baseEasing(Math.Clamp(remappedT, 0f, 1f));
}
}
}

View File

@@ -0,0 +1,42 @@
using Prism.Dialogs;
using Prism.Mvvm;
using Prism.Commands;
using System;
namespace YY.Admin.ViewModels.Dialogs
{
public class AlertDialogViewModel : BindableBase, IDialogAware
{
private string _message;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
public DelegateCommand CloseCommand { get; }
public AlertDialogViewModel()
{
CloseCommand = new DelegateCommand(() =>
{
// 调用 RequestClose 属性触发关闭
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
});
}
public bool CanCloseDialog() => true;
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
public void OnDialogClosed() { }
public string Title => "提示";
// 这里实现的是属性,不是 event
public DialogCloseListener RequestClose { get; private set; }
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace YY.Admin.ViewModels.Dialogs
{
public class ConfirmDialogViewModel : BindableBase, IDialogAware
{
public string Title => "确认";
private string _message;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
// 这是 Prism.Dialogs 版本的 RequestClose
public DialogCloseListener RequestClose { get; private set; }
public DelegateCommand YesCommand { get; }
public DelegateCommand NoCommand { get; }
public ConfirmDialogViewModel()
{
YesCommand = new DelegateCommand(() => CloseDialog(ButtonResult.Yes));
NoCommand = new DelegateCommand(() => CloseDialog(ButtonResult.No));
}
private void CloseDialog(ButtonResult result)
{
// 触发关闭
RequestClose.Invoke(new DialogResult(result));
}
public bool CanCloseDialog() => true;
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
public void OnDialogClosed() { }
}
}

View File

@@ -0,0 +1,45 @@
using Prism.Dialogs;
using Prism.Mvvm;
using System;
using System.Windows.Threading;
namespace YY.Admin.ViewModels.Dialogs
{
public class ErrorDialogViewModel : BindableBase, IDialogAware
{
public string Title => "错误";
private string _message;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
// 用属性来实现,而不是 event
public DialogCloseListener RequestClose { get; private set; }
public ErrorDialogViewModel()
{
// 自动关闭定时器2秒
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
timer.Tick += (s, e) =>
{
timer.Stop();
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
};
timer.Start();
}
public bool CanCloseDialog() => true;
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
public void OnDialogClosed()
{
}
}
}

View File

@@ -0,0 +1,115 @@
using YY.Admin.Helper;
namespace YY.Admin.ViewModels.Dialogs
{
public class ServerSettingsDialogViewModel : BindableBase, IDialogAware
{
private const string DefaultWebSocketPath = "/websocket/scada-sync";
private string _loadedWebSocketUrl = string.Empty;
private string _loadedAutoWebSocketUrl = string.Empty;
public string Title => "服务器设置";
private string _ip = "127.0.0.1";
public string Ip
{
get => _ip;
set => SetProperty(ref _ip, value);
}
private int _port = 8080;
public int Port
{
get => _port;
set => SetProperty(ref _port, value);
}
private string _webSocketUrl = string.Empty;
public string WebSocketUrl
{
get => _webSocketUrl;
set => SetProperty(ref _webSocketUrl, value);
}
private string _basePath = "/jeecg-boot";
public string BasePath
{
get => _basePath;
set => SetProperty(ref _basePath, value);
}
private string _errorMessage = string.Empty;
public string ErrorMessage
{
get => _errorMessage;
set => SetProperty(ref _errorMessage, value);
}
public DelegateCommand SaveCommand { get; }
public DelegateCommand CancelCommand { get; }
public DialogCloseListener RequestClose { get; private set; }
public ServerSettingsDialogViewModel()
{
SaveCommand = new DelegateCommand(Save);
CancelCommand = new DelegateCommand(() => RequestClose.Invoke(new DialogResult(ButtonResult.Cancel)));
}
public bool CanCloseDialog() => true;
public void OnDialogClosed() { }
public void OnDialogOpened(IDialogParameters parameters)
{
var settings = ServerSettingsStore.Load();
_loadedAutoWebSocketUrl = ServerSettingsStore.BuildDefaultWebSocketUrl(settings.BaseScheme, settings.Ip, settings.Port, settings.BasePath, settings.WebSocketPath);
Ip = settings.Ip;
Port = settings.Port;
WebSocketUrl = string.IsNullOrWhiteSpace(settings.WebSocketUrl)
? _loadedAutoWebSocketUrl
: settings.WebSocketUrl;
_loadedWebSocketUrl = WebSocketUrl;
BasePath = string.IsNullOrWhiteSpace(settings.BasePath) ? "/jeecg-boot" : settings.BasePath;
ErrorMessage = string.Empty;
}
private void Save()
{
ErrorMessage = string.Empty;
if (string.IsNullOrWhiteSpace(Ip))
{
ErrorMessage = "IP 不能为空";
return;
}
if (Port <= 0 || Port > 65535)
{
ErrorMessage = "端口号必须在 1-65535 之间";
return;
}
try
{
var basePath = BasePath?.Trim() ?? "/jeecg-boot";
var shouldAutoRebuildWs = string.IsNullOrWhiteSpace(WebSocketUrl)
|| string.Equals(WebSocketUrl.Trim(), _loadedAutoWebSocketUrl, StringComparison.OrdinalIgnoreCase)
|| string.Equals(WebSocketUrl.Trim(), _loadedWebSocketUrl, StringComparison.OrdinalIgnoreCase);
var finalWsUrl = shouldAutoRebuildWs
? ServerSettingsStore.BuildDefaultWebSocketUrl("http", Ip.Trim(), Port, basePath, DefaultWebSocketPath)
: WebSocketUrl.Trim();
ServerSettingsStore.Save(new ServerSettingsStore.ServerSettingsModel
{
Ip = Ip.Trim(),
Port = Port,
BaseScheme = "http",
BasePath = basePath,
WebSocketUrl = finalWsUrl,
WebSocketPath = DefaultWebSocketPath
});
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
}
catch (Exception ex)
{
ErrorMessage = $"保存失败:{ex.Message}";
}
}
}
}

View File

@@ -0,0 +1,45 @@
using Prism.Dialogs;
using Prism.Mvvm;
using System;
using System.Windows.Threading;
namespace YY.Admin.ViewModels.Dialogs
{
public class SuccessDialogViewModel : BindableBase, IDialogAware
{
public string Title => "成功";
private string _message;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
// Prism.Dialogs 版本的 RequestClose 是属性,不是 event
public DialogCloseListener RequestClose { get; private set; }
public SuccessDialogViewModel()
{
// 自动关闭定时器
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1.5) };
timer.Tick += (s, e) =>
{
timer.Stop();
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
};
timer.Start();
}
public bool CanCloseDialog() => true;
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
public void OnDialogClosed()
{
}
}
}

View File

@@ -0,0 +1,45 @@
using Prism.Dialogs;
using Prism.Mvvm;
using Prism.Commands;
using System;
namespace YY.Admin.ViewModels.Dialogs
{
public class WarningDialogViewModel : BindableBase, IDialogAware
{
public string Title => "警告";
private string _message;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
// Prism.Dialogs 版本的 RequestClose 是属性
public DialogCloseListener RequestClose { get; private set; }
public DelegateCommand CloseCommand { get; }
public WarningDialogViewModel()
{
CloseCommand = new DelegateCommand(CloseDialog);
}
private void CloseDialog()
{
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
}
public bool CanCloseDialog() => true;
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
public void OnDialogClosed()
{
}
}
}

View File

@@ -0,0 +1,277 @@
using System.Windows;
using System.Windows.Media;
using System.Net.Http;
using Microsoft.Extensions.Configuration;
using YY.Admin.Core.Helper;
using YY.Admin.Core.Session;
using YY.Admin.FluentValidation;
using YY.Admin.Services;
using YY.Admin.Services.Service.Auth;
using YY.Admin.Services.Service.Jeecg;
using YY.Admin.Views;
namespace YY.Admin.ViewModels
{
// ViewModels/LoginViewModel.cs
public class LoginWindowViewModel : BaseViewModel
{
private readonly ISysAuthService _authService;
private readonly IDialogService _dialogService;
private readonly IJeecgLoginLogReportService _loginLogReportService;
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient = new();
private readonly CancellationTokenSource _connectivityCts = new();
private const int ConnectivityCheckIntervalSeconds = 5;
public LoginInput LoginInput { get; set; }
private string _loginMessage = string.Empty;
public DelegateCommand LoginCommand { get; }
public DelegateCommand SyncJeecgUsersCommand { get; }
public DelegateCommand OpenServerSettingsCommand { get; }
public LoginInputValidator LoginInputValidator { get; private set; }
private bool _isSyncingJeecgUsers;
/// <summary>
/// 正在从 Jeecg 同步用户到本地库
/// </summary>
public bool IsSyncingJeecgUsers
{
get => _isSyncingJeecgUsers;
set
{
if (SetProperty(ref _isSyncingJeecgUsers, value))
{
SyncJeecgUsersCommand.RaiseCanExecuteChanged();
LoginCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(SyncJeecgUsersButtonText));
RaisePropertyChanged(nameof(CanInteractWithLogin));
}
}
}
public string SyncJeecgUsersButtonText => IsSyncingJeecgUsers ? "同步中..." : "同步 Jeecg 用户";
/// <summary>登录与同步互斥,用于禁用登录按钮</summary>
public bool CanInteractWithLogin => !IsLoading && !IsSyncingJeecgUsers;
private bool _isBackendConnected;
/// <summary>
/// 后端连接状态true=连接中false=已断开
/// </summary>
public bool IsBackendConnected
{
get => _isBackendConnected;
set
{
if (SetProperty(ref _isBackendConnected, value))
{
RaisePropertyChanged(nameof(BackendConnectionStatusText));
RaisePropertyChanged(nameof(BackendConnectionStatusBrush));
}
}
}
public string BackendConnectionStatusText => IsBackendConnected ? "后端连接中" : "后端已断开";
public Brush BackendConnectionStatusBrush => IsBackendConnected ? Brushes.LimeGreen : Brushes.Red;
public LoginWindowViewModel(
ISysAuthService authService,
IDialogService dialogService,
IJeecgLoginLogReportService loginLogReportService,
IContainerExtension _container,
IRegionManager regionManager
) : base(_container, regionManager)
{
_authService = authService;
_dialogService = dialogService;
_loginLogReportService = loginLogReportService;
_configuration = _container.Resolve<IConfiguration>();
_loginLogReportService.StartBackgroundSync();
Title = "系统登录";
LoginInput = new LoginInput()
{
Username = "admin",
Password = "123456"
};
LoginInputValidator = new LoginInputValidator();
LoginCommand = new DelegateCommand(async () => await LoginAsync(), CanLogin)
.ObservesProperty(() => IsLoading)
.ObservesProperty(() => IsSyncingJeecgUsers);
SyncJeecgUsersCommand = new DelegateCommand(async () => await SyncJeecgUsersAsync(), CanSyncJeecgUsers)
.ObservesProperty(() => IsLoading)
.ObservesProperty(() => IsSyncingJeecgUsers);
OpenServerSettingsCommand = new DelegateCommand(OpenServerSettings);
_ = StartBackendConnectivityLoopAsync(_connectivityCts.Token);
}
public string LoginMessage
{
get => _loginMessage;
set => SetProperty(ref _loginMessage, value);
}
public string LoginButtonText => IsLoading ? "登录中..." : "登录";
protected override void OnIsLoadingChanged()
{
base.OnIsLoadingChanged();
RaisePropertyChanged(nameof(LoginButtonText));
RaisePropertyChanged(nameof(CanInteractWithLogin));
LoginCommand.RaiseCanExecuteChanged();
SyncJeecgUsersCommand.RaiseCanExecuteChanged();
}
private bool CanLogin()
{
return !IsLoading && !IsSyncingJeecgUsers;
}
private bool CanSyncJeecgUsers()
{
return !IsLoading && !IsSyncingJeecgUsers;
}
private async Task SyncJeecgUsersAsync()
{
LoginMessage = string.Empty;
IsSyncingJeecgUsers = true;
try
{
await UIHelper.WaitForRenderAsync();
var (success, message) = await _authService.SyncJeecgUsersToLocalFromLoginScreenAsync();
LoginMessage = message;
if (success)
{
_logger?.Information("登录页一键同步 Jeecg 用户成功");
}
else
{
_logger?.Warning($"登录页一键同步 Jeecg 用户:{message}");
}
}
catch (Exception ex)
{
LoginMessage = $"同步出错:{ex.Message}";
}
finally
{
IsSyncingJeecgUsers = false;
}
}
private async Task LoginAsync()
{
IsLoading = true;
//LoginMessage = string.Empty;
try
{
// 让出线程让UI先渲染
await UIHelper.WaitForRenderAsync();
var response = await _authService.LoginAsync(LoginInput);
if (response.Success)
{
_ = _loginLogReportService.ReportLoginAsync(LoginInput.Username, true, "登录成功");
_connectivityCts.Cancel();
//设置会话
AppSession.CurrentUser = response.User;
// 设置用户上下文
SetUserContext(response.User!, response.Token);
// 启动定时器
StartTokenCheckTimer();
// 登录成功,打开主窗口
var loginWindow = Application.Current.MainWindow;
var mainWindow = _container.Resolve<MainWindow>();
Application.Current.MainWindow = mainWindow;
//把窗口和RegionManager 绑定一下即可
SetRegionManager(mainWindow);
mainWindow.Show();
loginWindow.Close();
// 不等待异步更新用户登录信息,不阻塞主窗口打开
_ = _authService.UpdateUserLoginInfoAsync(response.User!);
}
else
{
LoginMessage = response.Message;
_ = _loginLogReportService.ReportLoginAsync(LoginInput.Username, false, response.Message);
}
}
catch (Exception ex)
{
LoginMessage = $"登录出错:{ex.Message}";
_ = _loginLogReportService.ReportLoginAsync(LoginInput.Username, false, ex.Message);
}
finally
{
IsLoading = false;
}
}
private async Task StartBackendConnectivityLoopAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
bool connected = false;
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)
{
LoginMessage = "服务器配置已保存";
}
});
}
}
}

View File

@@ -0,0 +1,691 @@
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.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;
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);
// Jeecg 用户增量同步:定时 + 可选 WebSocket工控机断网续传
_jeecgUserSyncCoordinator.Start();
// 主窗口底部连接状态圆点
_ = 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;
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
{
_logger.Error($"导航失败: {viewName}");
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;
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 => { });
}
/// <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();
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YY.Admin.Core.Const;
namespace YY.Admin.ViewModels
{
public class NotFoundViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public NotFoundViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
GoHomeCommand = new DelegateCommand(GoHome);
}
public DelegateCommand GoHomeCommand { get; }
private void GoHome()
{
_regionManager.RequestNavigate(CommonConst.ContentRegion, "DashboardView");
}
}
}

View File

@@ -0,0 +1,94 @@
using HandyControl.Controls;
using YY.Admin.Core;
using YY.Admin.Core.Extension;
using YY.Admin.Core.Helper;
using YY.Admin.Services.Service;
using YY.Admin.ViewModels.Control;
namespace YY.Admin.ViewModels.SysManage;
public class DataDictionaryManagementViewModel : BaseViewModel
{
private readonly IJeecgDictSyncService _dictSyncService;
private PaginationDataGridViewModel<JeecgDictItemOutput> _paginationDataGridViewModel;
public PaginationDataGridViewModel<JeecgDictItemOutput> PaginationDataGridViewModel
{
get => _paginationDataGridViewModel;
set => SetProperty(ref _paginationDataGridViewModel, value);
}
private PageJeecgDictItemInput _input;
public PageJeecgDictItemInput Input
{
get => _input;
set => SetProperty(ref _input, value);
}
public List<KeyValuePair<string, int>> StatusList =>
Enum.GetValues(typeof(StatusEnum))
.Cast<StatusEnum>()
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
.ToList();
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand SyncCommand { get; }
public DataDictionaryManagementViewModel(
IJeecgDictSyncService dictSyncService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_dictSyncService = dictSyncService;
_paginationDataGridViewModel = new PaginationDataGridViewModel<JeecgDictItemOutput>(FetchAsync);
_input = new PageJeecgDictItemInput();
SearchCommand = new DelegateCommand(async () => await SearchAsync());
ResetCommand = new DelegateCommand(async () => await ResetAsync());
SyncCommand = new DelegateCommand(async () => await SyncAsync());
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
await UIHelper.WaitForRenderAsync();
await PaginationDataGridViewModel.LoadDataAsync();
}
private async Task<(IEnumerable<JeecgDictItemOutput> data, int totalCount)> FetchAsync()
{
Input.Page = PaginationDataGridViewModel.PageIndex;
Input.PageSize = PaginationDataGridViewModel.DataCountPerPage;
var result = await _dictSyncService.PageAsync(Input);
return (result.Items, result.Total);
}
private async Task SearchAsync()
{
PaginationDataGridViewModel.PageIndex = 1;
await PaginationDataGridViewModel.LoadDataAsync();
}
private async Task ResetAsync()
{
Input = new PageJeecgDictItemInput();
await UIHelper.WaitForRenderAsync();
await SearchAsync();
}
private async Task SyncAsync()
{
var count = await _dictSyncService.SyncFromJeecgAsync();
if (count > 0)
{
Growl.Success($"同步完成,共处理 {count} 条数据字典项");
}
else
{
Growl.Warning("未同步到数据字典,请确认后端可访问");
}
await SearchAsync();
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YY.Admin.Services.Service.User;
using YY.Admin.Services.Service;
using YY.Admin.ViewModels.Control;
namespace YY.Admin.ViewModels.SysManage
{
public class RoleManagementViewModel : BaseViewModel
{
public DelegateCommand AlertDialogCommand { get; }
public DelegateCommand ConfirmDialogCommand { get; }
public DelegateCommand ErrorDialogCommand { get; }
public DelegateCommand SuccessDialogCommand { get; }
public DelegateCommand WarningDialogCommand { get; }
public RoleManagementViewModel(
IContainerExtension container,
IRegionManager regionManager
) : base(container, regionManager)
{
AlertDialogCommand = new DelegateCommand( () => AlertDialogAsync());
ConfirmDialogCommand = new DelegateCommand( () => ConfirmDialogAsync());
ErrorDialogCommand = new DelegateCommand( () => ErrorDialogAsync());
SuccessDialogCommand = new DelegateCommand( () => SuccessDialogAsync());
WarningDialogCommand = new DelegateCommand( () => WarningDialogAsync());
}
private void WarningDialogAsync()
{
throw new NotImplementedException();
}
private void SuccessDialogAsync()
{
throw new NotImplementedException();
}
private void ErrorDialogAsync()
{
throw new NotImplementedException();
}
private void ConfirmDialogAsync()
{
base.Confirm("测试Confirm");
}
private void AlertDialogAsync()
{
base.Alert("测试Alert");
}
}
}

View File

@@ -0,0 +1,96 @@
using HandyControl.Controls;
using YY.Admin.Core;
using YY.Admin.Core.Extension;
using YY.Admin.Core.Helper;
using YY.Admin.Services.Service;
using YY.Admin.Services.Service.Tenant;
using YY.Admin.ViewModels.Control;
namespace YY.Admin.ViewModels.SysManage
{
public class TenantManagementViewModel : BaseViewModel
{
private readonly ISysTenantSyncService _tenantSyncService;
private PaginationDataGridViewModel<TenantOutput> _paginationDataGridViewModel;
public PaginationDataGridViewModel<TenantOutput> PaginationDataGridViewModel
{
get => _paginationDataGridViewModel;
set => SetProperty(ref _paginationDataGridViewModel, value);
}
private PageTenantInput _input;
public PageTenantInput Input
{
get => _input;
set => SetProperty(ref _input, value);
}
public List<KeyValuePair<string, int>> StatusList =>
Enum.GetValues(typeof(StatusEnum))
.Cast<StatusEnum>()
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
.ToList();
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand SyncCommand { get; }
public TenantManagementViewModel(
ISysTenantSyncService tenantSyncService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_tenantSyncService = tenantSyncService;
_paginationDataGridViewModel = new PaginationDataGridViewModel<TenantOutput>(FetchTenantsAsync);
_input = new PageTenantInput();
SearchCommand = new DelegateCommand(async () => await SearchAsync());
ResetCommand = new DelegateCommand(async () => await ResetAsync());
SyncCommand = new DelegateCommand(async () => await SyncAsync());
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
await UIHelper.WaitForRenderAsync();
await PaginationDataGridViewModel.LoadDataAsync();
}
private async Task<(IEnumerable<TenantOutput> data, int totalCount)> FetchTenantsAsync()
{
Input.Page = PaginationDataGridViewModel.PageIndex;
Input.PageSize = PaginationDataGridViewModel.DataCountPerPage;
var result = await _tenantSyncService.PageAsync(Input);
return (result.Items, result.Total);
}
private async Task SearchAsync()
{
PaginationDataGridViewModel.PageIndex = 1;
await PaginationDataGridViewModel.LoadDataAsync();
}
private async Task ResetAsync()
{
Input = new PageTenantInput();
await UIHelper.WaitForRenderAsync();
await SearchAsync();
}
private async Task SyncAsync()
{
var count = await _tenantSyncService.SyncFromJeecgAsync();
if (count > 0)
{
Growl.Success($"同步完成,共处理 {count} 条租户数据");
}
else
{
Growl.Warning("未同步到租户数据请确认已使用Jeecg账号登录且后端可访问");
}
await SearchAsync();
}
}
}

View File

@@ -0,0 +1,179 @@
using HandyControl.Controls;
using HandyControl.Data;
using HandyControl.Tools.Extension;
using System.Collections.ObjectModel;
using YY.Admin.Core;
using YY.Admin.FluentValidation;
using YY.Admin.Services.Service;
using YY.Admin.Services.Service.User;
namespace YY.Admin.ViewModels.SysManage
{
public class UserEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
{
private SysUser? _sysUser;
private readonly ISysUserService _sysUserService;
public SysUserValidator SysUserValidator { get; private set; }
public SysUser? SysUser
{
get => _sysUser;
set {
SetProperty(ref _sysUser, value);
}
}
public bool IsAddMode => SysUser?.Id == 0;
public string DialogTitle => IsAddMode ? "新增用户" : "编辑用户";
// 性别枚举列表
//public List<KeyValuePair<string, int>> GenderList =>
// Enum.GetValues(typeof(GenderEnum))
// .Cast<GenderEnum>()
// .Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
// .ToList();
public ObservableCollection<GenderEnum> GenderList { get; }
= new ObservableCollection<GenderEnum>(Enum.GetValues(typeof(GenderEnum)).Cast<GenderEnum>().ToArray());
public ObservableCollection<StatusEnum> StatusOptions { get; }
= new ObservableCollection<StatusEnum>(Enum.GetValues(typeof(StatusEnum)).Cast<StatusEnum>().ToArray());
//// 状态枚举列表
//public List<KeyValuePair<string, int>> StatusList =>
// Enum.GetValues(typeof(StatusEnum))
// .Cast<StatusEnum>()
// .Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
// .ToList();
public DelegateCommand SaveCommand { get; private set; }
public DelegateCommand CancelCommand { get; private set; }
public DelegateCommand<object> StatusSelectedCommand { get; }
private bool _result;
public bool Result { get => _result; set => SetProperty(ref _result, value); }
public Action? CloseAction { get; set; }
public UserEditDialogViewModel(
ISysUserService sysUserService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_sysUserService = sysUserService;
SysUserValidator = new SysUserValidator(sysUserService);
//SysUser = new SysUser();
StatusSelectedCommand = new DelegateCommand<object>(OnStatusSelected);
SaveCommand = new DelegateCommand(async () => await SaveUserAsync());
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
}
private void OnStatusSelected(object statusObj)
{
if (statusObj is StatusEnum status)
{
SysUser?.Status = status;
}
}
public async Task SaveUserAsync()
{
try
{
if (IsAddMode)
{
var result = await _sysUserService.CreateAsync(SysUser!);
Result = result > 0;
if (Result)
{
Growl.Success(new GrowlInfo
{
Message = "新增用户成功!",
ShowDateTime = false,
WaitTime = 1
});
}
else
{
Growl.Error("新增用户失败!");
return;
}
}
else
{
var result = await _sysUserService.UpdateAsync(SysUser!);
Result = result > 0;
if (Result)
{
Growl.Success("修改用户成功!");
}
else
{
Growl.Error("修改用户失败!");
return;
}
}
// 关闭对话框
CloseAction?.Invoke();
}
catch (Exception ex)
{
Growl.Error($"操作失败:{ex.Message}");
}
}
// 初始化编辑数据
public void InitializeForEdit(UserOutput user)
{
if (user != null)
{
SysUser = new SysUser
{
Id = user.Id,
//Account = user.Account,
RealName = user.RealName,
NickName = user.NickName,
//Phone = user.Phone,
Sex = user.Sex,
Birthday = user.Birthday,
Age = user.Age,
//AccountType = user.AccountType,
Status = user.Status
};
}
}
// 初始化新增数据
public void InitializeForAdd()
{
SysUser = new SysUser();
//IsAddMode = true;
RaisePropertyChanged(nameof(IsAddMode));
RaisePropertyChanged(nameof(DialogTitle));
}
//private readonly SysUserValidator _addValidator = new SysUserValidator(true);
//private readonly SysUserValidator _editValidator = new SysUserValidator(false);
//public string this[string columnName]
//{
// get
// {
// if (SysUser == null) return string.Empty;
// var validator = IsAddMode ? _addValidator : _editValidator;
// var result = validator.Validate(SysUser);
// var error = result.Errors.FirstOrDefault(e => e.PropertyName == columnName);
// return error?.ErrorMessage;
// }
//}
}
}

View File

@@ -0,0 +1,607 @@
using HandyControl.Controls;
using HandyControl.Tools.Extension;
using SqlSugar;
using System.Diagnostics;
using System.Windows;
using YY.Admin.Core;
using YY.Admin.Core.Extension;
using YY.Admin.Core.Helper;
using YY.Admin.Services.Service;
using YY.Admin.Services.Service.User;
using YY.Admin.ViewModels.Control;
using YY.Admin.Views.SysManage;
namespace YY.Admin.ViewModels.SysManage
{
public class UserManagementViewModel : BaseViewModel
{
private PaginationDataGridViewModel<UserOutput> _paginationDataGridViewModel;
private PageUserInput _userInput;
private readonly ISysUserService _sysUserService;
private readonly IDialogService _dialogService;
private SubscriptionToken? _jeecgSyncToken;
public PaginationDataGridViewModel<UserOutput> PaginationDataGridViewModel
{
get => _paginationDataGridViewModel;
set => SetProperty(ref _paginationDataGridViewModel, value);
}
public PageUserInput UserInput
{ get => _userInput;
set => SetProperty(ref _userInput, value);
}
// 性别枚举列表属性
public List<GenderEnum> GenderList =>
[.. Enum.GetValues(typeof(GenderEnum)).Cast<GenderEnum>()];
// 状态枚举列表属性
public List<KeyValuePair<string, int>> StatusList =>
Enum.GetValues(typeof(StatusEnum))
.Cast<StatusEnum>()
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
.ToList();
// 全选
//private bool? _isAllSelected = false;
//public bool? IsAllSelected
//{
// get => _isAllSelected;
// set
// {
// if (SetProperty(ref _isAllSelected, value) && value.HasValue)
// {
// // 只有当值真正改变时才执行全选逻辑
// SelectAll(value.Value);
// }
// }
//}
// 选中用户数量
private int _selectedCount;
public int SelectedCount
{
get => _selectedCount;
set
{
if (SetProperty(ref _selectedCount, value))
{
// 当 SelectedCount 变化时,触发 HasSelectedItems 通知
RaisePropertyChanged(nameof(HasSelectedItems));
}
}
}
// 是否有选中项
public bool HasSelectedItems => SelectedCount > 0;
// 批量删除
public DelegateCommand BatchDeleteCommand { get; private set; }
// 单个删除
public DelegateCommand<long?> DeleteCommand { get; private set; }
// 查询
public DelegateCommand SearchCommand { get; private set; }
public DelegateCommand ResetCommand { get; private set; }
public DelegateCommand AddCommand { get; private set; }
public DelegateCommand<UserOutput> EditCommand { get; private set; }
public DelegateCommand<UserOutput> StatusToggleCommand { get; private set; }
// 行选择改变命令
//public DelegateCommand<UserOutput> RowSelectionChangedCommand { get; private set; }
public UserManagementViewModel(
ISysUserService sysUserService,
IContainerExtension container,
IDialogService dialogService,
IRegionManager regionManager
) : base(container, regionManager)
{
_sysUserService= sysUserService;
_dialogService = dialogService;
// 创建分页控件的 ViewModel传递一个获取数据的委托
_paginationDataGridViewModel = new PaginationDataGridViewModel<UserOutput>(FetchUsersAsync);
_userInput = new PageUserInput();
// 初始化批量删除命令
BatchDeleteCommand = new DelegateCommand(async () => await BatchDelete(),
() => HasSelectedItems);
DeleteCommand = new DelegateCommand<long?>(async (id) => {
if (id.HasValue)
{
await Delete(id.Value);
}
});
SearchCommand = new DelegateCommand(async () => await ReadAsync());
ResetCommand = new DelegateCommand(async () => await ResetFormAsync());
AddCommand = new DelegateCommand(async () => await ShowAddDialog());
EditCommand = new DelegateCommand<UserOutput>(async (user) => await ShowEditDialog(user));
StatusToggleCommand = new DelegateCommand<UserOutput>(async (user) => await ToggleStatus(user));
//RowSelectionChangedCommand = new DelegateCommand<UserOutput>(OnRowSelectionChanged);
// 监听数据变化
//PaginationDataGridViewModel.PropertyChanged += OnPaginationDataChanged;
//_ = PaginationDataGridViewModel.LoadDataAsync(); // 默认加载第一页数据
//_dataList = GetDemoDataList(10);
// 启动异步初始化,但不等待
_ = InitializeAsync();
// Jeecg 同构用户表同步完成后,自动刷新当前列表
_jeecgSyncToken = _eventAggregator
.GetEvent<SysUserEvents.JeecgMirrorUsersSyncedEvent>()
.Subscribe(async _ =>
{
await PaginationDataGridViewModel.LoadDataAsync();
}, ThreadOption.UIThread);
}
private async Task InitializeAsync()
{
try
{
// 不阻塞UI线程让UI先渲染效果就是先看到界面然后再加载表格数据提升用户体验
await UIHelper.WaitForRenderAsync();
// 然后异步加载数据
await PaginationDataGridViewModel.LoadDataAsync();
}
catch (Exception ex)
{
Debug.WriteLine($"初始化失败: {ex.Message}");
}
}
protected override void CleanUp()
{
base.CleanUp();
if (_jeecgSyncToken != null)
{
_eventAggregator.GetEvent<SysUserEvents.JeecgMirrorUsersSyncedEvent>().Unsubscribe(_jeecgSyncToken);
_jeecgSyncToken = null;
}
}
// 行选择改变事件处理
//private void OnRowSelectionChanged(UserOutput user)
//{
// if (user == null) return;
// user.IsSelected = !user.IsSelected;
// // 更新选中数量
// UpdateSelectedCount();
// // 更新全选状态
// UpdateSelectAllStatus();
// // 更新批量删除命令状态
// BatchDeleteCommand.RaiseCanExecuteChanged();
// // 调试输出
// //System.Diagnostics.Debug.WriteLine($"用户 {user.UserName} 选中状态: {user.IsSelected}");
//}
//private void OnPaginationDataChanged(object sender, PropertyChangedEventArgs e)
//{
// if (e.PropertyName == nameof(PaginationDataGridViewModel.Data))
// {
// RegisterSelectionEvents();
// UpdateSelectedCount();
// }
//}
// 注册选择事件监听
//private void RegisterSelectionEvents()
//{
// if (PaginationDataGridViewModel.Data == null) return;
// foreach (var user in PaginationDataGridViewModel.Data.OfType<UserOutput>())
// {
// user.SelectionChanged -= OnUserSelectionChanged;
// user.SelectionChanged += OnUserSelectionChanged;
// }
//}
//private void OnUserSelectionChanged(object sender, EventArgs e)
//{
// UpdateSelectedCount();
// // 更新全选状态
// UpdateSelectAllStatus();
// BatchDeleteCommand.RaiseCanExecuteChanged();
//}
public void UpdateSelectionState()
{
UpdateSelectedCount();
//UpdateSelectAllStatus();
BatchDeleteCommand.RaiseCanExecuteChanged();
}
// 更新选中数量
private void UpdateSelectedCount()
{
if (PaginationDataGridViewModel.Data == null)
{
SelectedCount = 0;
return;
}
SelectedCount = PaginationDataGridViewModel.Data.Count(user =>
user is UserOutput output && output.IsSelected);
}
// 获取选中的用户ID列表
private List<long> GetSelectedUserIds()
{
if (PaginationDataGridViewModel.Data == null)
return new List<long>();
return PaginationDataGridViewModel.Data
.Where(user => user is UserOutput output && output.IsSelected)
.Select(user => (user as UserOutput).Id)
.ToList();
}
// 批量删除执行方法
private async Task BatchDelete()
{
var selectedUserIds = GetSelectedUserIds();
if (selectedUserIds.Count == 0) return;
var messageBoxResult = HandyControl.Controls.MessageBox.Show(
$"确定要删除选中的 {selectedUserIds.Count} 个用户吗?此操作不可恢复!",
"确认删除",
MessageBoxButton.OKCancel,
MessageBoxImage.Question);
if (messageBoxResult != MessageBoxResult.OK) return;
int count = await _sysUserService.BatchDeleteAsync(selectedUserIds);
if (count > 0)
{
PaginationDataGridViewModel.PageIndex = 1;
await PaginationDataGridViewModel.LoadDataAsync();
}
// 1. 准备传递给对话框的参数
//var parameters = new DialogParameters();
//parameters.Add("Title", "操作确认");
//parameters.Add("Message", "确定要执行此操作吗?");
//parameters.Add("ConfirmButtonText", "确认"); // 可选:自定义确认按钮文本
//parameters.Add("CancelButtonText", "取消"); // 可选:自定义取消按钮文本
// 2. 显示对话框并处理返回结果
//_dialogService.ShowDialog(
// "ConfirmDialog", // 注册时使用的对话框名称
// parameters,
// result =>
// {
// // 3. 根据对话框返回结果执行后续逻辑
// if (result.Result == ButtonResult.OK)
// {
// // 用户点击了确认按钮
// //ExecuteConfirmedAction();
// }
// else if (result.Result == ButtonResult.Cancel)
// {
// // 用户点击了取消按钮
// //ExecuteCancelledAction();
// }
// }
//);
}
// 删除执行方法
private async Task Delete(long id)
{
var messageBoxResult = HandyControl.Controls.MessageBox.Show(
$"确定删除ID为 {id} 的用户吗?此操作不可恢复!",
"确认删除",
MessageBoxButton.OKCancel,
MessageBoxImage.Question,
MessageBoxResult.No);
if (messageBoxResult != MessageBoxResult.OK) return;
int count = await _sysUserService.DeleteAsync(id);
if (count > 0)
{
PaginationDataGridViewModel.PageIndex = 1;
await PaginationDataGridViewModel.LoadDataAsync();
}
}
private async Task<(IEnumerable<UserOutput> data, int totalCount)> FetchUsersAsync()
{
if (UserInput.EndTime.HasValue && UserInput.EndTime.HasValue && UserInput.EndTime < UserInput.BeginTime)
{
HandyControl.Controls.MessageBox.Error("开始时间不能大于结束时间!");
//return (PaginationDataGridViewModel.Data, PaginationDataGridViewModel.TotalCount);
throw new OperationCanceledException("时间条件不合法,取消查询");
}
UserInput.Page = PaginationDataGridViewModel.PageIndex;
UserInput.PageSize = PaginationDataGridViewModel.DataCountPerPage;
// 延迟10秒
//await Task.Delay(10000);
var rlt = await _sysUserService.PageAsync(UserInput);
return (rlt.Items, rlt.Total); // 返回分页数据和总条目数
}
private async Task ReadAsync()
{
PaginationDataGridViewModel.PageIndex = 1;
await PaginationDataGridViewModel.LoadDataAsync();
}
public async Task ResetFormAsync()
{
UserInput = new PageUserInput();
// 立即更新UI
await UIHelper.WaitForRenderAsync();
await ReadAsync();
}
// 显示新增对话框
private async Task ShowAddDialog()
{
try
{
//var vm = new UserEditDialogViewModel(_sysUserService, _container);
//var view = new UserEditDialogView { DataContext = vm };
var result = await HandyControl.Controls.Dialog.Show<UserEditDialogView>()
.Initialize<UserEditDialogViewModel>(v => v.InitializeForAdd())
.GetResultAsync<bool>();
// 使用泛型方式,通过 Initialize 方法设置数据
//var result = await HandyControl.Controls.Dialog.Show<UserEditDialogView>()
// .Initialize<UserEditDialogViewModel>(vm =>
// {
// vm.InitializeForAdd();
// })
// .GetResultAsync<bool?>();
if (result)
{
await PaginationDataGridViewModel.LoadDataAsync();
//Growl.Success("用户新增成功!");
}
}
catch (Exception ex)
{
Growl.Error($"打开对话框失败:{ex.Message}");
}
}
// 显示编辑对话框
private async Task ShowEditDialog(UserOutput user)
{
if (user == null) return;
try
{
var result = await HandyControl.Controls.Dialog.Show<UserEditDialogView>()
.Initialize<UserEditDialogViewModel>(vm =>
{
vm.InitializeForEdit(user);
})
.GetResultAsync<bool>();
if (result)
{
await PaginationDataGridViewModel.LoadDataAsync();
//Growl.Success("用户修改成功!");
}
}
catch (Exception ex)
{
Growl.Error($"打开对话框失败:{ex.Message}");
}
}
private async Task ToggleStatus(UserOutput user)
{
if (user == null) return;
// 保存原始状态以便回滚
var originalStatus = user.Status;
try
{
// 先切换本地状态(提供即时反馈)
user.Status = originalStatus == StatusEnum.Enable
? StatusEnum.Disable
: StatusEnum.Enable;
SysUser sysUser = new SysUser();
sysUser.Id = user.Id;
sysUser.Status = user.Status;
int count = await _sysUserService.ToggleStatus(sysUser);
if (count <= 0)
{
// 如果服务调用失败,回滚状态
user.Status = originalStatus;
Growl.Warning("状态切换失败");
}
} catch (Exception)
{
user.Status = originalStatus;
Growl.Warning("状态切换失败");
}
}
// 全选/取消全选方法
//private void SelectAll(bool isSelected)
//{
// if (PaginationDataGridViewModel?.Data == null) return;
// foreach (var user in PaginationDataGridViewModel.Data.OfType<UserOutput>())
// {
// user.IsSelected = isSelected;
// }
// // 更新选中数量
// UpdateSelectedCount();
//}
// 更新全选状态(根据当前选中情况)
//public void UpdateSelectAllStatus()
//{
// if (PaginationDataGridViewModel?.Data == null || !PaginationDataGridViewModel.Data.Any())
// {
// IsAllSelected = false;
// return;
// }
// var selectedCount = PaginationDataGridViewModel.Data.Count(user =>
// user is UserOutput output && output.IsSelected);
// var totalCount = PaginationDataGridViewModel.Data.Count;
// if (selectedCount == 0)
// {
// IsAllSelected = false;
// }
// else if (selectedCount == totalCount)
// {
// IsAllSelected = true;
// }
// else
// {
// IsAllSelected = null; // 部分选中状态
// }
//}
//private string _searchText;
//public string SearchText
//{
// get => _searchText;
// set
// {
// if (SetProperty(ref _searchText, value))
// {
// Debug.WriteLine($"SearchText Updated: {value}");
// FilterItems(value);
// }
// }
//}
//private DemoDataModel? _selectedItem;
//public DemoDataModel? SelectedItem
//{
// get => _selectedItem;
// set
// {
// if (SetProperty(ref _selectedItem, value))
// {
// Debug.WriteLine($"SelectedItem Updated: {value}");
// }
// }
//}
//// 修改 Items 属性为 ManualObservableCollection
//private ManualObservableCollection<DemoDataModel> _items = new();
//public ManualObservableCollection<DemoDataModel> Items
//{
// get => _items;
// set => SetProperty(ref _items, value);
//}
//private readonly List<DemoDataModel> _dataList;
//private void FilterItems(string key)
//{
// //Items.CanNotify = false;
// Items.Clear();
// foreach (var data in _dataList)
// {
// if (data.Name.ToLower().Contains(key.ToLower()))
// {
// Items.Add(data);
// }
// }
// //RaisePropertyChanged(nameof(Items));
// //Items.CanNotify = true;
//}
//List<DemoDataModel> GetDemoDataList(int count)
//{
// var list = new List<DemoDataModel>();
// for (var i = 1; i <= count; i++)
// {
// var index = i % 6 + 1;
// var model = new DemoDataModel
// {
// Index = i,
// IsSelected = i % 2 == 0,
// Name = $"Name{i}",
// Type = (DemoType)index,
// ImgPath = $"/HandyControlDemo;component/Resources/Img/Avatar/avatar{index}.png",
// Remark = new string(i.ToString()[0], 10)
// };
// list.Add(model);
// }
// return list;
//}
}
// public class DemoDataModel
//{
// public int Index { get; set; }
// public string Name { get; set; } = string.Empty;
// public bool IsSelected { get; set; }
// public string Remark { get; set; } = string.Empty;
// public DemoType Type { get; set; }
// public string ImgPath { get; set; } = string.Empty;
// public List<DemoDataModel> DataList { get; set; } = [];
//}
//public enum DemoType
//{
// Type1 = 1,
// Type2,
// Type3,
// Type4,
// Type5,
// Type6
//}
}