更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。
This commit is contained in:
78
yy-admin-master/YY.Admin/Module/CacheExtensions.cs
Normal file
78
yy-admin-master/YY.Admin/Module/CacheExtensions.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Identity.Client;
|
||||
using NewLife.Caching.Services;
|
||||
using NewLife.Caching;
|
||||
using NewLife.Log;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Option;
|
||||
using NewLife.Configuration;
|
||||
|
||||
public static class CacheExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册缓存服务
|
||||
/// </summary>
|
||||
public static void AddNewLifeCache(this IContainerRegistry containerRegistry,IConfiguration configuration)
|
||||
{
|
||||
// 读取配置
|
||||
var BaseCacheOptions = configuration.GetSection("Cache").Get<BaseCacheOptions>();
|
||||
|
||||
// 注册配置选项
|
||||
containerRegistry.RegisterInstance(BaseCacheOptions);
|
||||
|
||||
// 注册缓存提供器
|
||||
if (BaseCacheOptions.CacheType == CacheTypeEnum.Redis.ToString())
|
||||
{
|
||||
containerRegistry.RegisterSingleton<ICacheProvider>(() =>
|
||||
{
|
||||
return CreateRedisCacheProvider(BaseCacheOptions);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认使用内存缓存
|
||||
containerRegistry.RegisterSingleton<ICacheProvider, CacheProvider>();
|
||||
}
|
||||
|
||||
// 注册缓存服务
|
||||
containerRegistry.RegisterSingleton<ISysCacheService, SysCacheService>();
|
||||
}
|
||||
private static ICacheProvider CreateRedisCacheProvider(BaseCacheOptions options)
|
||||
{
|
||||
var redis = new FullRedis
|
||||
{
|
||||
Name = "RedisCache",
|
||||
Tracer = null, // 禁用跟踪器
|
||||
Log = XTrace.Log // 使用NewLife日志
|
||||
};
|
||||
|
||||
// 初始化Redis
|
||||
redis.Init(options.Redis.Configuration!);
|
||||
|
||||
// 设置前缀
|
||||
if (!string.IsNullOrEmpty(options.Redis.Prefix))
|
||||
{
|
||||
redis.Prefix = options.Redis.Prefix;
|
||||
}
|
||||
|
||||
// 设置最大消息大小
|
||||
if (options.Redis.MaxMessageSize > 0)
|
||||
{
|
||||
redis.MaxMessageSize = options.Redis.MaxMessageSize;
|
||||
}
|
||||
|
||||
//// 测试连接
|
||||
//if (!redis.Ping())
|
||||
//{
|
||||
// throw new Exception("Redis连接失败,请检查配置");
|
||||
//}
|
||||
|
||||
return new RedisCacheProvider { Cache = redis };
|
||||
}
|
||||
}
|
||||
27
yy-admin-master/YY.Admin/Module/DbExtensions.cs
Normal file
27
yy-admin-master/YY.Admin/Module/DbExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Identity.Client;
|
||||
using NewLife.Caching.Services;
|
||||
using NewLife.Caching;
|
||||
using NewLife.Log;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Option;
|
||||
using NewLife.Configuration;
|
||||
using YY.Admin.Core.SqlSugar;
|
||||
|
||||
public static class DbExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册缓存服务
|
||||
/// </summary>
|
||||
public static void AddDbContext(this IContainerRegistry containerRegistry,IConfiguration configuration)
|
||||
{
|
||||
containerRegistry.AddSqlSugar(configuration);
|
||||
}
|
||||
|
||||
}
|
||||
41
yy-admin-master/YY.Admin/Module/HttpClientExtensions.cs
Normal file
41
yy-admin-master/YY.Admin/Module/HttpClientExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Net.Http;
|
||||
|
||||
namespace YY.Admin.Module
|
||||
{
|
||||
public static class HttpClientExtensions
|
||||
{
|
||||
|
||||
public static void AddHttpClient(this IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterSingleton<HttpClient>(() =>
|
||||
{
|
||||
var handler = new SocketsHttpHandler
|
||||
{
|
||||
// 连接池大小
|
||||
MaxConnectionsPerServer = 50,
|
||||
|
||||
// 最大存活时间(绝对时间)
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
|
||||
|
||||
// 空闲连接超时时间,超时后自动回收
|
||||
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
|
||||
|
||||
// 是否自动重试
|
||||
AutomaticDecompression = System.Net.DecompressionMethods.GZip |
|
||||
System.Net.DecompressionMethods.Deflate
|
||||
};
|
||||
|
||||
var client = new HttpClient(handler)
|
||||
{
|
||||
// 默认超时时间 30秒
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("YY.Admin");
|
||||
|
||||
return client;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
29
yy-admin-master/YY.Admin/Module/NavItem.cs
Normal file
29
yy-admin-master/YY.Admin/Module/NavItem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using YY.Admin.Core;
|
||||
|
||||
namespace YY.Admin.Module
|
||||
{
|
||||
/// <summary>
|
||||
/// SideBar选项
|
||||
/// </summary>
|
||||
public class NavItem : TabSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否放到底部
|
||||
/// </summary>
|
||||
public bool AlignBottom { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持高亮选中
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 图标类型
|
||||
/// </summary>
|
||||
public override IconTypeEnum IconType { get => base.IconType; set => base.IconType = value; }
|
||||
public NavItem()
|
||||
{
|
||||
IconType = IconTypeEnum.MaterialDesign;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
yy-admin-master/YY.Admin/Module/NavigationExtensions.cs
Normal file
82
yy-admin-master/YY.Admin/Module/NavigationExtensions.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using YY.Admin.ViewModels.Control;
|
||||
using YY.Admin.ViewModels.Dialogs;
|
||||
using YY.Admin.Views;
|
||||
using YY.Admin.Views.Control;
|
||||
using YY.Admin.Views.Dialogs;
|
||||
using YY.Admin.Views.SysManage;
|
||||
|
||||
namespace YY.Admin
|
||||
{
|
||||
public static class NavigationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册导航
|
||||
/// </summary>
|
||||
public static void AddNavigation(this IContainerRegistry containerRegistry)
|
||||
{
|
||||
// 注册对话框
|
||||
containerRegistry.RegisterDialog<AlertDialogView, AlertDialogViewModel>("AlertDialog");
|
||||
containerRegistry.RegisterDialog<SuccessDialogView, SuccessDialogViewModel>("SuccessDialog");
|
||||
containerRegistry.RegisterDialog<ErrorDialogView, ErrorDialogViewModel>("ErrorDialog");
|
||||
containerRegistry.RegisterDialog<WarningDialogView, WarningDialogViewModel>("WarningDialog");
|
||||
containerRegistry.RegisterDialog<ConfirmDialogView, ConfirmDialogViewModel>("ConfirmDialog");
|
||||
containerRegistry.RegisterDialog<ServerSettingsDialogView, ServerSettingsDialogViewModel>("ServerSettingsDialog");
|
||||
|
||||
// 设置对话框样式
|
||||
containerRegistry.RegisterDialogWindow<DialogWindow>();
|
||||
|
||||
// 注册导航
|
||||
containerRegistry.RegisterForNavigation<DashboardView>("DashboardView");
|
||||
// 404视图
|
||||
containerRegistry.RegisterForNavigation<NotFoundView>("NotFoundView");
|
||||
//containerRegistry.RegisterForNavigation<RoleManagementView>("RoleManagementView");
|
||||
//containerRegistry.RegisterForNavigation<PermissionManagementView>("PermissionManagementView");
|
||||
//containerRegistry.RegisterForNavigation<OrderManagementView>("OrderManagementView");
|
||||
//containerRegistry.RegisterForNavigation<ProductManagementView>("ProductManagementView");
|
||||
//containerRegistry.RegisterForNavigation<ReportView>("ReportView");
|
||||
//containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
|
||||
|
||||
// 窗口注册
|
||||
containerRegistry.Register<LoginWindow>();
|
||||
containerRegistry.Register<MainWindow>();
|
||||
|
||||
// 注册视图(页面)
|
||||
containerRegistry.RegisterForNavigation<MenuTreeView>();
|
||||
containerRegistry.RegisterForNavigation<UserManagementView>();
|
||||
containerRegistry.RegisterForNavigation<DataDictionaryManagementView>();
|
||||
containerRegistry.RegisterForNavigation<RoleManagementView>();
|
||||
containerRegistry.RegisterForNavigation<TenantManagementView>();
|
||||
|
||||
}
|
||||
}
|
||||
public class DialogWindow : Window, IDialogWindow
|
||||
{
|
||||
public DialogWindow()
|
||||
{
|
||||
WindowStyle = WindowStyle.None;
|
||||
AllowsTransparency = true;
|
||||
Background = Brushes.Transparent; // 背景透明
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
SizeToContent = SizeToContent.WidthAndHeight;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
}
|
||||
public IDialogResult? Result { get; set; }
|
||||
}
|
||||
//public class DialogWindow : Window, IDialogWindow
|
||||
//{
|
||||
// public DialogWindow()
|
||||
// {
|
||||
// //InitializeComponent();
|
||||
|
||||
// // 去掉最大化最小化
|
||||
// ResizeMode = ResizeMode.NoResize;
|
||||
|
||||
// // 去掉右上角系统按钮
|
||||
// WindowStyle = WindowStyle.None;
|
||||
// }
|
||||
// public IDialogResult Result { get; set; }
|
||||
//}
|
||||
}
|
||||
24
yy-admin-master/YY.Admin/Module/NotificationExtensions.cs
Normal file
24
yy-admin-master/YY.Admin/Module/NotificationExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using YY.Admin.EventBus;
|
||||
using static YY.Admin.Core.SysUserEvents;
|
||||
using System.Reflection;
|
||||
using YY.Admin.Core;
|
||||
using Prism.Ioc;
|
||||
|
||||
namespace YY.Admin.Module
|
||||
{
|
||||
public static class NotificationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册通知事件
|
||||
/// </summary>
|
||||
public static void AddNotificationEventBus(this IContainerRegistry containerRegistry)
|
||||
{
|
||||
// 注册EventAggregator为单例
|
||||
containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
yy-admin-master/YY.Admin/Module/ProjectOptions.cs
Normal file
31
yy-admin-master/YY.Admin/Module/ProjectOptions.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YY.Admin.Core.Option;
|
||||
using YY.Admin.Core.SqlSugar;
|
||||
|
||||
namespace YY.Admin.Setup
|
||||
{
|
||||
public static class ProjectOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册项目配置选项到Prism容器
|
||||
/// </summary>
|
||||
public static IContainerRegistry AddProjectOptions(
|
||||
this IContainerRegistry containerRegistry,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// 绑定数据库连接配置到对象
|
||||
var dbOptions = configuration.GetSection("DbConnection").Get<DbConnectionOptions>();
|
||||
|
||||
// 注册配置实例
|
||||
containerRegistry.RegisterInstance(dbOptions);
|
||||
|
||||
return containerRegistry;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
yy-admin-master/YY.Admin/Module/ServiceExtensions.cs
Normal file
109
yy-admin-master/YY.Admin/Module/ServiceExtensions.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Reflection;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Services.Service.Auth;
|
||||
|
||||
namespace YY.Admin
|
||||
{
|
||||
public static class ServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册服务
|
||||
/// </summary>
|
||||
public static void AddService(this IContainerRegistry containerRegistry, IConfiguration configuration)
|
||||
{
|
||||
// 注册配置
|
||||
containerRegistry.RegisterInstance<IConfiguration>(configuration);
|
||||
//// 注册日志服务为单例
|
||||
containerRegistry.RegisterSingleton<ILoggerService, SerilogLoggerService>();
|
||||
// 自动扫描并注册应用层服务
|
||||
RegisterServicesByAssembly(containerRegistry, typeof(ISysAuthService).Assembly);
|
||||
}
|
||||
/// <summary>
|
||||
/// 自动注册程序集中所有服务
|
||||
/// </summary>
|
||||
private static void RegisterServicesByAssembly(IContainerRegistry containerRegistry, Assembly assembly)
|
||||
{
|
||||
// 获取所有公开的非抽象类
|
||||
var serviceTypes = assembly.GetExportedTypes()
|
||||
.Where(t => t.IsClass && !t.IsAbstract);
|
||||
|
||||
foreach (var implementationType in serviceTypes)
|
||||
{
|
||||
// 查找服务接口(名称以"I"开头,去掉首字母后匹配)
|
||||
var serviceInterface = implementationType.GetInterfaces()
|
||||
.FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
|
||||
|
||||
// 如果没有直接匹配的接口,尝试查找其他接口
|
||||
if (serviceInterface == null)
|
||||
{
|
||||
// 备选方案1:注册所有实现的接口(适合多接口实现)
|
||||
RegisterAllInterfaces(containerRegistry, implementationType);
|
||||
|
||||
// 备选方案2:只注册主接口
|
||||
// serviceInterface = implementationType.GetInterface($"I{implementationType.Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 注册找到的接口
|
||||
RegisterService(containerRegistry, serviceInterface, implementationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册服务到容器
|
||||
/// </summary>
|
||||
private static void RegisterService(
|
||||
IContainerRegistry containerRegistry,
|
||||
Type serviceType,
|
||||
Type implementationType)
|
||||
{
|
||||
// 根据命名约定判断生命周期
|
||||
bool isSingleton = implementationType.Name.EndsWith("Service") ||
|
||||
implementationType.Name.EndsWith("Repository");
|
||||
|
||||
// 根据基类判断生命周期
|
||||
bool isSingletonByBase = typeof(ISingletonDependency).IsAssignableFrom(implementationType);
|
||||
|
||||
// 根据特性判断生命周期
|
||||
var attribute = implementationType.GetCustomAttribute<LifecycleAttribute>();
|
||||
var lifecycle = attribute?.Lifecycle ?? Lifecycle.Transient;
|
||||
|
||||
// 确定注册方式
|
||||
if (isSingleton || isSingletonByBase || lifecycle == Lifecycle.Singleton)
|
||||
{
|
||||
containerRegistry.RegisterSingleton(serviceType, implementationType);
|
||||
Console.WriteLine($"注册单例服务: {serviceType.Name} -> {implementationType.Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
containerRegistry.Register(serviceType, implementationType);
|
||||
Console.WriteLine($"注册瞬时服务: {serviceType.Name} -> {implementationType.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册所有实现的接口
|
||||
/// </summary>
|
||||
private static void RegisterAllInterfaces(IContainerRegistry containerRegistry, Type implementationType)
|
||||
{
|
||||
var interfaces = implementationType.GetInterfaces()
|
||||
.Where(i => i != typeof(IDisposable) &&
|
||||
!i.Name.StartsWith("_") && // 排除某些特殊接口
|
||||
i.Assembly == implementationType.Assembly); // 只注册同程序集接口
|
||||
|
||||
foreach (var serviceType in interfaces)
|
||||
{
|
||||
RegisterService(containerRegistry, serviceType, implementationType);
|
||||
}
|
||||
|
||||
if (!interfaces.Any())
|
||||
{
|
||||
// 如果没有接口,直接注册具体类型
|
||||
containerRegistry.Register(implementationType);
|
||||
Console.WriteLine($"注册具体类型: {implementationType.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
yy-admin-master/YY.Admin/Module/SyncModule.cs
Normal file
69
yy-admin-master/YY.Admin/Module/SyncModule.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Polly;
|
||||
using Polly.Extensions.Http;
|
||||
using Prism.Ioc;
|
||||
using Prism.Modularity;
|
||||
using System.Net.Http;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Infrastructure.Hubs;
|
||||
using YY.Admin.Infrastructure.Network;
|
||||
using YY.Admin.Infrastructure.Storage;
|
||||
using YY.Admin.Infrastructure.Sync;
|
||||
|
||||
namespace YY.Admin.Module;
|
||||
|
||||
public class SyncModule : IModule
|
||||
{
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterSingleton<TokenStore>();
|
||||
containerRegistry.RegisterSingleton<HttpSyncClient>();
|
||||
containerRegistry.RegisterSingleton<OutboxProcessor>();
|
||||
containerRegistry.RegisterSingleton<IJeecgUserMirrorPullOutbox, JeecgUserMirrorPullOutbox>();
|
||||
containerRegistry.RegisterSingleton<INetworkMonitor, NetworkMonitor>();
|
||||
containerRegistry.RegisterSingleton<ISignalRService, StompWebSocketService>();
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddHttpClient("JeecgApi", (sp, client) =>
|
||||
{
|
||||
var config = containerRegistry.GetContainer().Resolve<IConfiguration>();
|
||||
var baseUrl = config.GetValue<string>("JeecgIntegration:BaseUrl")?.TrimEnd('/');
|
||||
if (!string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
client.BaseAddress = new Uri(baseUrl);
|
||||
}
|
||||
|
||||
var tokenStore = containerRegistry.GetContainer().Resolve<TokenStore>();
|
||||
var token = tokenStore.GetTokenAsync(default).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}).AddPolicyHandler(GetRetryPolicy());
|
||||
|
||||
var provider = serviceCollection.BuildServiceProvider();
|
||||
var httpClientFactory = provider.GetRequiredService<IHttpClientFactory>();
|
||||
containerRegistry.RegisterInstance(httpClientFactory);
|
||||
}
|
||||
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
var networkMonitor = containerProvider.Resolve<INetworkMonitor>();
|
||||
var outboxProcessor = containerProvider.Resolve<OutboxProcessor>();
|
||||
var signalService = containerProvider.Resolve<ISignalRService>();
|
||||
|
||||
_ = networkMonitor.StartAsync(CancellationToken.None);
|
||||
_ = outboxProcessor.StartConsumerAsync(CancellationToken.None);
|
||||
// 用户镜像 + 设备指令:统一 STOMP(/ws/device),免密与设备 Token 模式均启动
|
||||
_ = Task.Run(() => signalService.ConnectUnifiedDeviceChannelAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
|
||||
{
|
||||
return HttpPolicyExtensions
|
||||
.HandleTransientHttpError()
|
||||
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
|
||||
}
|
||||
|
||||
}
|
||||
246
yy-admin-master/YY.Admin/Module/TabItemModel.cs
Normal file
246
yy-admin-master/YY.Admin/Module/TabItemModel.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Event;
|
||||
|
||||
namespace YY.Admin.Module
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个标签的数据模型
|
||||
/// </summary>
|
||||
public class TabItemModel : BindableBase
|
||||
{
|
||||
public ObservableCollection<TabItemModel>? OpenTabs { get; set; }
|
||||
|
||||
public IEventAggregator? EventAggregator { get; set; }
|
||||
|
||||
private string? _id;
|
||||
public string? Id
|
||||
{
|
||||
get => _id;
|
||||
set => SetProperty(ref _id, value);
|
||||
}
|
||||
|
||||
private string? _header;
|
||||
public string? Header
|
||||
{
|
||||
get => _header;
|
||||
set => SetProperty(ref _header, value);
|
||||
}
|
||||
|
||||
private string? _icon = string.Empty;
|
||||
public string? Icon
|
||||
{
|
||||
get => _icon;
|
||||
set => SetProperty(ref _icon, value);
|
||||
}
|
||||
|
||||
private IconTypeEnum? _iconType;
|
||||
public IconTypeEnum IconType {
|
||||
get {
|
||||
return _iconType ?? IconTypeEnum.AntDesign;
|
||||
}
|
||||
set => SetProperty(ref _iconType, value);
|
||||
}
|
||||
|
||||
private string? _viewName = string.Empty;
|
||||
public string? ViewName
|
||||
{
|
||||
get => _viewName;
|
||||
set => SetProperty(ref _viewName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tab是否允许关闭
|
||||
/// </summary>
|
||||
private bool _isClosable = true;
|
||||
public bool IsClosable
|
||||
{
|
||||
get => _isClosable;
|
||||
set => SetProperty(ref _isClosable, value);
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
// 添加对Tab源的引用
|
||||
public TabSource? TabSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 左侧是否有可关闭Tab
|
||||
/// </summary>
|
||||
public bool HasClosableLeft => OpenTabs != null && OpenTabs
|
||||
.Take(OpenTabs.IndexOf(this))
|
||||
.Any(t => t.IsClosable);
|
||||
|
||||
/// <summary>
|
||||
/// 右侧是否有可关闭Tab
|
||||
/// </summary>
|
||||
public bool HasClosableRight => OpenTabs != null && OpenTabs
|
||||
.Skip(OpenTabs.IndexOf(this) + 1)
|
||||
.Any(t => t.IsClosable);
|
||||
|
||||
/// <summary>
|
||||
/// 其他Tab(除当前)是否有可关闭Tab
|
||||
/// </summary>
|
||||
public bool HasClosableOther => OpenTabs != null && OpenTabs
|
||||
.Where(t => t != this)
|
||||
.Any(t => t.IsClosable);
|
||||
|
||||
/// <summary>
|
||||
/// 是否有任意可关闭Tab(全部)
|
||||
///</summary>
|
||||
public bool HasClosableAny => OpenTabs != null && OpenTabs
|
||||
.Any(t => t.IsClosable);
|
||||
|
||||
public DelegateCommand<TabItemModel> RefreshTabCommand { get; }
|
||||
public DelegateCommand<TabItemModel> CloseTabCommand { get; }
|
||||
public DelegateCommand<TabItemModel> CloseLeftTabsCommand { get; }
|
||||
public DelegateCommand<TabItemModel> CloseRightTabsCommand { get; }
|
||||
public DelegateCommand<TabItemModel> CloseOtherTabsCommand { get; }
|
||||
public DelegateCommand<TabItemModel> CloseAllTabsCommand { get; }
|
||||
|
||||
public TabItemModel()
|
||||
{
|
||||
Id = $"TabRegion_{Guid.NewGuid():N}"; // 保证唯一
|
||||
|
||||
RefreshTabCommand = new DelegateCommand<TabItemModel>(RefreshTab);
|
||||
CloseTabCommand = new DelegateCommand<TabItemModel>(CloseTab);
|
||||
CloseLeftTabsCommand = new DelegateCommand<TabItemModel>(CloseLeftTabs);
|
||||
CloseRightTabsCommand = new DelegateCommand<TabItemModel>(CloseRightTabs);
|
||||
CloseOtherTabsCommand = new DelegateCommand<TabItemModel>(CloseOtherTabs);
|
||||
CloseAllTabsCommand = new DelegateCommand<TabItemModel>(CloseAllTabs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新当前Tab
|
||||
/// </summary>
|
||||
/// <param name="tabItemModel"></param>
|
||||
private void RefreshTab(TabItemModel tabItemModel)
|
||||
{
|
||||
if (tabItemModel?.TabSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 发布事件
|
||||
EventAggregator?.GetEvent<TabRefreshEvent>().Publish(tabItemModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭当前Tab
|
||||
/// </summary>
|
||||
/// <param name="tabItemModel"></param>
|
||||
private void CloseTab(TabItemModel tabItemModel)
|
||||
{
|
||||
if (tabItemModel?.IsClosable != true || OpenTabs?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 发布事件
|
||||
EventAggregator?.GetEvent<TabClosedEvent>().Publish(tabItemModel);
|
||||
|
||||
// 从集合中移除标签
|
||||
OpenTabs.Remove(tabItemModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭左侧Tab
|
||||
/// </summary>
|
||||
private void CloseLeftTabs(TabItemModel tabItemModel)
|
||||
{
|
||||
if (OpenTabs?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到当前 Tab 的索引
|
||||
int currentIndex = OpenTabs.IndexOf(tabItemModel);
|
||||
// 左侧没有 Tab
|
||||
if (currentIndex <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 找出左侧所有可关闭的 Tab
|
||||
var leftTabs = OpenTabs
|
||||
.Take(currentIndex) // 取前面所有 Tab
|
||||
.Where(t => t.IsClosable) // 只关闭可关闭的
|
||||
.ToList(); // 先 ToList 避免集合修改时报错
|
||||
|
||||
foreach (var tab in leftTabs)
|
||||
{
|
||||
CloseTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭右侧Tab
|
||||
/// </summary>
|
||||
private void CloseRightTabs(TabItemModel tabItemModel)
|
||||
{
|
||||
if (OpenTabs?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int currentIndex = OpenTabs.IndexOf(tabItemModel);
|
||||
if (currentIndex < 0 || currentIndex >= OpenTabs.Count - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rightTabs = OpenTabs
|
||||
.Skip(currentIndex + 1)
|
||||
.Where(t => t.IsClosable)
|
||||
.ToList();
|
||||
|
||||
foreach (var tab in rightTabs)
|
||||
{
|
||||
CloseTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭其他Tab(仅保留当前)
|
||||
/// </summary>
|
||||
private void CloseOtherTabs(TabItemModel tabItemModel)
|
||||
{
|
||||
if (OpenTabs?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var otherTabs = OpenTabs
|
||||
.Where(t => t != tabItemModel && t.IsClosable)
|
||||
.ToList();
|
||||
|
||||
foreach (var tab in otherTabs)
|
||||
{
|
||||
CloseTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭全部(包括当前)
|
||||
/// </summary>
|
||||
private void CloseAllTabs(TabItemModel tabItemModel)
|
||||
{
|
||||
if (OpenTabs?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var allClosableTabs = OpenTabs
|
||||
.Where(t => t.IsClosable)
|
||||
.ToList();
|
||||
|
||||
foreach (var tab in allClosableTabs)
|
||||
{
|
||||
CloseTab(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
yy-admin-master/YY.Admin/Module/TabSource.cs
Normal file
56
yy-admin-master/YY.Admin/Module/TabSource.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Windows.Input;
|
||||
using YY.Admin.Core;
|
||||
|
||||
namespace YY.Admin.Module
|
||||
{
|
||||
public class TabSource : BindableBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public virtual string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public virtual string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标类型
|
||||
/// </summary>
|
||||
public virtual IconTypeEnum IconType { get; set; } = IconTypeEnum.AntDesign;
|
||||
|
||||
/// <summary>
|
||||
/// 视图
|
||||
/// </summary>
|
||||
public virtual string? ViewName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 视图的导航参数
|
||||
/// </summary>
|
||||
public INavigationParameters? NavigationParameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否选中
|
||||
/// </summary>
|
||||
private bool _isSelected;
|
||||
|
||||
public virtual bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tab是否允许关闭
|
||||
/// </summary>
|
||||
private bool _isClosable = true;
|
||||
public bool IsClosable
|
||||
{
|
||||
get => _isClosable;
|
||||
set => SetProperty(ref _isClosable, value);
|
||||
}
|
||||
|
||||
public ICommand? Command { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user