首次提交:本地项目同步到Gitea
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using Cowain.Base.Models;
|
||||
using Ke.Bee.Localization.Providers.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Localization;
|
||||
|
||||
public abstract class LocalizationResourceContributorBase: ILocalizationResourceContributor
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件根目录
|
||||
/// </summary>
|
||||
private readonly string _pluginRoot;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="appSettings">全局配置</param>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public LocalizationResourceContributorBase(IOptions<AppSettings> appSettings, string pluginName)
|
||||
{
|
||||
_pluginRoot = Path.Combine(appSettings.Value.PluginPath, pluginName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化资源的默认实现
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Dictionary<string, string>? GetResource(CultureInfo culture)
|
||||
{
|
||||
var resource = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllBytes(
|
||||
Path.Combine(_pluginRoot, "i18n", $"{culture.IetfLanguageTag}.json")
|
||||
));
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
119
Cowain.Base/Abstractions/Navigation/DefaultViewNavigator.cs
Normal file
119
Cowain.Base/Abstractions/Navigation/DefaultViewNavigator.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Cowain.Base.Helpers;
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.Models.Navigation;
|
||||
using Cowain.Base.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// 默认视图导航器
|
||||
/// </summary>
|
||||
public class DefaultViewNavigator : IViewNavigator
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前页视图模型
|
||||
/// </summary>
|
||||
public PageViewModelBase? CurrentPage { get; private set; }
|
||||
/// <summary>
|
||||
/// 属性变化通知事件
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
/// <summary>
|
||||
/// 导航命令集合
|
||||
/// </summary>
|
||||
//private readonly IEnumerable<INavigationCommand> _commands;
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DefaultViewNavigator(IServiceProvider serviceProvider)
|
||||
{
|
||||
//_commands = commands;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导航到指定视图页面
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public void NavigateTo(string key)
|
||||
{
|
||||
ArgumentExceptionHelper.ThrowIfNullOrEmpty(key);
|
||||
string vmName = key + "ViewModel";
|
||||
//var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
|
||||
//// 加载插件程序集
|
||||
//var appSettings = _serviceProvider.GetService<IOptions<AppSettings>>();
|
||||
//var pluginPath = appSettings?.Value.PluginPath;
|
||||
//if (Directory.Exists(pluginPath))
|
||||
//{
|
||||
// var pluginFiles = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories);
|
||||
// foreach (var file in pluginFiles)
|
||||
// {
|
||||
// var assembly = Assembly.LoadFrom(file);
|
||||
// assemblies.Add(assembly);
|
||||
// }
|
||||
//}
|
||||
List<Assembly>? assemblies = GlobalData.Instance["Assemblies"] as List<Assembly>;
|
||||
if (assemblies == null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(key), $"assemblies is null.");
|
||||
}
|
||||
var vmType = assemblies.SelectMany(a => a.GetTypes()).FirstOrDefault(t => t.Name == vmName);
|
||||
if (vmType == null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(key), $"ViewModel type '{vmName}' not found.");
|
||||
}
|
||||
if (_serviceProvider.GetService(vmType) is PageViewModelBase vm)
|
||||
{
|
||||
SetCurrentPage(vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to resolve ViewModel '{vmName}' from the service provider.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前视图模型
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
public void SetCurrentPage(PageViewModelBase page)
|
||||
{
|
||||
|
||||
CurrentPage = page;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentPage)));
|
||||
}
|
||||
|
||||
public void ClearCurrentPage()
|
||||
{
|
||||
CurrentPage = null;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentPage)));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重载视图
|
||||
/// </summary>
|
||||
public void ReloadCurrentPage()
|
||||
{
|
||||
if (CurrentPage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 DI 容器创建实例 (因为视图模型都是以瞬态模式注入,所以获取的实例都是新实例)
|
||||
if (_serviceProvider.GetService(CurrentPage.GetType()) is PageViewModelBase newPage)
|
||||
{
|
||||
SetCurrentPage(newPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Cowain.Base/Abstractions/Navigation/IPageViewMenuActions.cs
Normal file
12
Cowain.Base/Abstractions/Navigation/IPageViewMenuActions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Navigation
|
||||
{
|
||||
internal class IPageViewMenuActions
|
||||
{
|
||||
}
|
||||
}
|
||||
36
Cowain.Base/Abstractions/Navigation/IViewNavigator.cs
Normal file
36
Cowain.Base/Abstractions/Navigation/IViewNavigator.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
using Cowain.Base.ViewModels;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// 视图导航器接口
|
||||
/// </summary>
|
||||
public interface IViewNavigator
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性变化通知事件
|
||||
/// </summary>
|
||||
event PropertyChangedEventHandler PropertyChanged;
|
||||
/// <summary>
|
||||
/// 当前页视图模型对象
|
||||
/// </summary>
|
||||
PageViewModelBase? CurrentPage { get; }
|
||||
/// <summary>
|
||||
/// 设置当前页
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
void SetCurrentPage(PageViewModelBase page);
|
||||
/// <summary>
|
||||
/// 导航到视图
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
void NavigateTo(string key);
|
||||
/// <summary>
|
||||
/// 重载视图
|
||||
/// </summary>
|
||||
void ReloadCurrentPage();
|
||||
|
||||
void ClearCurrentPage();
|
||||
}
|
||||
45
Cowain.Base/Abstractions/Plugin/IPlugin.cs
Normal file
45
Cowain.Base/Abstractions/Plugin/IPlugin.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.Models.Menu;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 插件接口
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
string PluginName { get; }
|
||||
/// <summary>
|
||||
/// 执行方法
|
||||
/// </summary>
|
||||
/// <param name="methodName">方法名称</param>
|
||||
/// <param name="parameters">参数</param>
|
||||
/// <returns>执行结果对象</returns>
|
||||
R? Execute<T, R>(string methodName, T? parameters) where R : ResultModel<R>;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider"></param>
|
||||
void Initialize(IServiceProvider serviceProvider);
|
||||
/// <summary>
|
||||
/// 注册服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
void RegisterServices(IServiceCollection services, List<Assembly>? _assemblies);
|
||||
/// <summary>
|
||||
/// 配置菜单
|
||||
/// </summary>
|
||||
/// <param name="menuContext"></param>
|
||||
void ConfigureMenu(MenuConfigurationContext? menuContext);
|
||||
/// <summary>
|
||||
/// 关闭插件
|
||||
/// </summary>
|
||||
void Shutdown();
|
||||
}
|
||||
|
||||
34
Cowain.Base/Abstractions/Plugin/IPluginMethodHandler.cs
Normal file
34
Cowain.Base/Abstractions/Plugin/IPluginMethodHandler.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
using Cowain.Base.Models;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 执行插件方法的处理接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">方法请求参数数据类型</typeparam>
|
||||
public interface IPluginMethodHandler<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行方法,并返回结果对象
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel> ExecuteAsync(T? parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行插件方法的处理接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">方法请求参数数据类型</typeparam>
|
||||
/// <typeparam name="R">方法返回对象的 Data 属性数据类型</typeparam>
|
||||
public interface IPluginMethodHandler<T, R>
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行方法,并返回带 R 类型数据的结果对象
|
||||
/// </summary>
|
||||
/// <typeparam name="R"></typeparam>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel<R>> ExecuteAsync(T? parameters);
|
||||
}
|
||||
105
Cowain.Base/Abstractions/Plugin/PluginBase.cs
Normal file
105
Cowain.Base/Abstractions/Plugin/PluginBase.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.Models.Menu;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Cowain.Base.Abstractions.Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 插件基类
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class PluginBase : IPlugin
|
||||
{
|
||||
|
||||
private ILogger<PluginBase>? _logger;
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public abstract string PluginName { get; }
|
||||
public string? PluginPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置插件菜单
|
||||
/// </summary>
|
||||
/// <param name="menuContext"></param>
|
||||
public virtual void ConfigureMenu(MenuConfigurationContext? menuContext)
|
||||
{
|
||||
if (menuContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(PluginPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var menuFile = Path.Combine(PluginPath, PluginName, "Configs", "menus.json");
|
||||
if (!File.Exists(menuFile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 从插件 Configs 目录下读取插件菜单配置
|
||||
var menus = JsonSerializer.Deserialize<List<MenuItem>>(File.ReadAllBytes(menuFile));
|
||||
if (menus == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var menu in menus.Where(x => x.Group == "Toolbar").ToList())
|
||||
{
|
||||
var item = menuContext.Menus.FirstOrDefault(x => x.Key == menu.Key);
|
||||
if (item != null)
|
||||
{
|
||||
//一级菜单已经存在
|
||||
if (menu.Items.Count > 0)
|
||||
{
|
||||
//有二级菜单,合并二级菜单
|
||||
foreach (var menuItem in menu.Items)
|
||||
{
|
||||
var existingMenu = item.Items.FirstOrDefault(x => x.Key == menuItem.Key);
|
||||
if (existingMenu == null)
|
||||
{
|
||||
item.Items.Add(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//无二级菜单,更新一级菜单属性
|
||||
item.CommandType = menu.CommandType;
|
||||
item.CommandParameter = menu.CommandParameter;
|
||||
item.PageActions = menu.PageActions;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//新的一级菜单
|
||||
menuContext.Menus.Add(menu);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public abstract R? Execute<T, R>(string methodName, T? parameters) where R : ResultModel<R>;
|
||||
public abstract void RegisterServices(IServiceCollection services, List<Assembly>? _assemblies);
|
||||
|
||||
public virtual void Initialize(IServiceProvider serviceProvider)
|
||||
{
|
||||
var appSettings = serviceProvider.GetRequiredService<IOptions<AppSettings>>().Value;
|
||||
var menuConfigurationContext = serviceProvider.GetRequiredService<MenuConfigurationContext>();
|
||||
PluginPath = appSettings.PluginPath;
|
||||
ConfigureMenu(menuConfigurationContext);
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<PluginBase>>();
|
||||
_logger?.LogInformation($"插件 {PluginName} 初始化");
|
||||
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
{
|
||||
_logger?.LogInformation($"插件 {PluginName} 关闭");
|
||||
}
|
||||
}
|
||||
18
Cowain.Base/Attributes/AutoRegisterAttribute.cs
Normal file
18
Cowain.Base/Attributes/AutoRegisterAttribute.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Cowain.Base.Attributes;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 服务注册特性 - 支持自定义生命周期
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class AutoRegisterAttribute : Attribute
|
||||
{
|
||||
public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Scoped;
|
||||
public AutoRegisterAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped)
|
||||
{
|
||||
Lifetime = lifetime;
|
||||
}
|
||||
}
|
||||
41
Cowain.Base/Attributes/MinLengthAttribute.cs
Normal file
41
Cowain.Base/Attributes/MinLengthAttribute.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Cowain.Base.Helpers;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Attributes
|
||||
{
|
||||
public class MinLengthAttribute : ValidationAttribute
|
||||
{
|
||||
private readonly ILocalizer _l;
|
||||
public MinLengthAttribute(int length)
|
||||
{
|
||||
Length = length;
|
||||
_l = ServiceLocator.GetRequiredService<ILocalizer>();
|
||||
}
|
||||
|
||||
public MinLengthAttribute(int length, string messageKey) : this(length)
|
||||
{
|
||||
MessageKey = messageKey;
|
||||
}
|
||||
|
||||
|
||||
public int Length { get; }
|
||||
public string? MessageKey { get; }
|
||||
|
||||
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
|
||||
{
|
||||
if (value is string stringValue && stringValue.Length < Length)
|
||||
{
|
||||
var errorMessage = string.IsNullOrEmpty(MessageKey) ? $"The length of the string should be at least {Length} characters" : _l[MessageKey] + Length;
|
||||
return new ValidationResult(errorMessage);
|
||||
}
|
||||
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Cowain.Base/Attributes/SingletonAttribute.cs
Normal file
7
Cowain.Base/Attributes/SingletonAttribute.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
namespace Cowain.Base.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public class SingletonAttribute : Attribute
|
||||
{
|
||||
}
|
||||
8
Cowain.Base/Attributes/TransientAttribute.cs
Normal file
8
Cowain.Base/Attributes/TransientAttribute.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace Cowain.Base.Attributes;
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public class TransientAttribute : Attribute
|
||||
{
|
||||
}
|
||||
32
Cowain.Base/Converters/BooleanToVisibilityConverter.cs
Normal file
32
Cowain.Base/Converters/BooleanToVisibilityConverter.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class BooleanToVisibilityConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
public bool FalseVisibility { get; set; } = false;
|
||||
public bool Negate { get; set; }
|
||||
|
||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
string? stringValue = value?.ToString();
|
||||
if (bool.TryParse(stringValue, out var b))
|
||||
{
|
||||
return (Negate ? !b : b) ? true : FalseVisibility;
|
||||
}
|
||||
else if (double.TryParse(stringValue, out var d))
|
||||
{
|
||||
return (Negate ? !(d > 0) : (d > 0)) ? true : FalseVisibility;
|
||||
}
|
||||
|
||||
bool result = value != null;
|
||||
return (Negate ? !result : result) ? true : FalseVisibility;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> value is bool v && v;
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
||||
}
|
||||
38
Cowain.Base/Converters/EnumLocalizeConverter.cs
Normal file
38
Cowain.Base/Converters/EnumLocalizeConverter.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Cowain.Base.Helpers;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举值本地化转换器
|
||||
/// </summary>
|
||||
public class EnumLocalizeConverter : IValueConverter
|
||||
{
|
||||
private readonly ILocalizer _l;
|
||||
public EnumLocalizeConverter()
|
||||
{
|
||||
_l = ServiceLocator.GetRequiredService<ILocalizer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举值转换为本地化文本
|
||||
/// </summary>
|
||||
/// <param name="value">枚举值</param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="prefix">对应的本地化前缀</param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object? Convert(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value != null)
|
||||
return _l[$"{prefix}.{value}"];
|
||||
return null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
22
Cowain.Base/Converters/FirstLetterConverter.cs
Normal file
22
Cowain.Base/Converters/FirstLetterConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class FirstLetterConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string str && !string.IsNullOrEmpty(str))
|
||||
{
|
||||
return str[0].ToString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
43
Cowain.Base/Converters/I18nLocalizeConverter.cs
Normal file
43
Cowain.Base/Converters/I18nLocalizeConverter.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Cowain.Base.Helpers;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举值本地化转换器
|
||||
/// </summary>
|
||||
public class I18nLocalizeConverter : IValueConverter
|
||||
{
|
||||
private readonly ILocalizer _l;
|
||||
public I18nLocalizeConverter()
|
||||
{
|
||||
_l = ServiceLocator.GetRequiredService<ILocalizer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转换为本地化文本
|
||||
/// </summary>
|
||||
/// <param name="value">枚举值</param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="prefix">对应的本地化前缀</param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object? Convert(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value == null)
|
||||
return "未配置";
|
||||
var key = $"{prefix}.{value}";
|
||||
var result = _l[key];
|
||||
// 如果本地化内容为空或与 key 相同,则返回原始 value 字符串
|
||||
if (string.IsNullOrEmpty(result) || result == key)
|
||||
return value.ToString();
|
||||
return result;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
49
Cowain.Base/Converters/ListCountConverter.cs
Normal file
49
Cowain.Base/Converters/ListCountConverter.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
|
||||
public class ListCountConverter : IValueConverter
|
||||
{
|
||||
public ListCountConverter()
|
||||
{
|
||||
}
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value == null)
|
||||
return 0;
|
||||
|
||||
// 处理集合类型
|
||||
if (value is IEnumerable collection)
|
||||
{
|
||||
// 优化处理实现了ICollection的集合
|
||||
if (collection is ICollection collectionWithCount)
|
||||
return collectionWithCount.Count;
|
||||
|
||||
// 否则遍历计数
|
||||
int count = 0;
|
||||
foreach (var item in collection)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
// 处理字符串
|
||||
if (value is string str)
|
||||
return str.Length;
|
||||
|
||||
// 处理数组
|
||||
if (value.GetType().IsArray)
|
||||
return ((Array)value).Length;
|
||||
|
||||
// 未知类型返回1(表示单个对象)
|
||||
return 1;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException("ConvertBack is not supported.");
|
||||
}
|
||||
}
|
||||
39
Cowain.Base/Converters/ListStringConverter.cs
Normal file
39
Cowain.Base/Converters/ListStringConverter.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Cowain.Base.Helpers;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// List和string转换器
|
||||
/// </summary>
|
||||
public class ListStringConverter : IValueConverter
|
||||
{
|
||||
public ListStringConverter()
|
||||
{
|
||||
}
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value is ObservableCollection<string> lists)
|
||||
{
|
||||
return JsonSerializer.Serialize(lists);
|
||||
//return string.Join(',', menuActions);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value is string jsonString)
|
||||
{
|
||||
return JsonSerializer.Deserialize<ObservableCollection<string>>(jsonString);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
41
Cowain.Base/Converters/MenuActionsConverter.cs
Normal file
41
Cowain.Base/Converters/MenuActionsConverter.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Cowain.Base.Helpers;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单权限转换格式显示
|
||||
/// </summary>
|
||||
public class MenuActionsConverter : IValueConverter
|
||||
{
|
||||
private readonly ILocalizer _l;
|
||||
public MenuActionsConverter()
|
||||
{
|
||||
_l = ServiceLocator.GetRequiredService<ILocalizer>();
|
||||
}
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value is ObservableCollection<string> menuActions)
|
||||
{
|
||||
return JsonSerializer.Serialize(menuActions);
|
||||
//return string.Join(',', menuActions);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? prefix, CultureInfo culture)
|
||||
{
|
||||
if (value is string jsonString)
|
||||
{
|
||||
return JsonSerializer.Deserialize<ObservableCollection<string>>(jsonString);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
39
Cowain.Base/Converters/MultiI18nLocalizeConverter.cs
Normal file
39
Cowain.Base/Converters/MultiI18nLocalizeConverter.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Cowain.Base.Helpers;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class MultiI18nLocalizeConverter : IMultiValueConverter
|
||||
{
|
||||
private readonly ILocalizer _l;
|
||||
|
||||
public MultiI18nLocalizeConverter()
|
||||
{
|
||||
_l = ServiceLocator.GetRequiredService<ILocalizer>();
|
||||
}
|
||||
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 参数说明:values[0]=项, values[1]=集合对象
|
||||
if (values.Count < 2 || values[0] is null || values[1] is null)
|
||||
return BindingOperations.DoNothing;
|
||||
|
||||
string? val = values[0] is string ss ? ss : "Null";
|
||||
IEnumerable<string>? collection = values[1] as IEnumerable<string>;
|
||||
|
||||
// 从集合中查找项
|
||||
object? item = collection?.FirstOrDefault(val);
|
||||
if (item is null) return "Not Found";
|
||||
return _l[$"{parameter}.{item}"];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Cowain.Base/Converters/MultiIdToPropertyConverter .cs
Normal file
65
Cowain.Base/Converters/MultiIdToPropertyConverter .cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// 通用ID到对象属性值转换器
|
||||
/// 支持通过ID从集合中查找对象,并返回该对象的指定属性值
|
||||
/// </summary>
|
||||
public class MultiIdToPropertyConverter : MarkupExtension, IMultiValueConverter
|
||||
{
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 参数说明:values[0]=ID值, values[1]=集合对象, values[2]=目标属性名(可选)
|
||||
if (values.Count < 2 || values[0] is null || values[1] is null)
|
||||
return BindingOperations.DoNothing;
|
||||
|
||||
object? id = values[0];
|
||||
object? collection = values[1];
|
||||
string targetProperty = (values.Count >= 3 && values[2] is string s && !string.IsNullOrEmpty(s))
|
||||
? s
|
||||
: "Name"; // 默认属性名为 "Name"
|
||||
|
||||
// 从集合中查找项
|
||||
object? item = (id is not null && collection is not null) ? FindItemById(collection, id) : null;
|
||||
if (item is null) return "Null";
|
||||
|
||||
var propertyInfo = item.GetType().GetProperty(targetProperty);
|
||||
return propertyInfo?.GetValue(item) ?? "Null";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
|
||||
private object? FindItemById(object collection, object id)
|
||||
{
|
||||
switch (collection)
|
||||
{
|
||||
case IDictionary dict:
|
||||
return id is not null && dict.Contains(id) ? dict[id] : null;
|
||||
case IEnumerable enumerable:
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
var itemId = item?.GetType().GetProperty("Id")?.GetValue(item);
|
||||
if (itemId is not null && itemId.Equals(id))
|
||||
return item;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
||||
}
|
||||
26
Cowain.Base/Converters/StringToBoolConverter.cs
Normal file
26
Cowain.Base/Converters/StringToBoolConverter.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class StringToBoolConverter : IValueConverter
|
||||
{
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null && bool.TryParse(value.ToString(), out bool v))
|
||||
{
|
||||
return v;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null && value is bool boolValue)
|
||||
{
|
||||
return boolValue.ToString();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
55
Cowain.Base/Converters/StringToBrushConverter.cs
Normal file
55
Cowain.Base/Converters/StringToBrushConverter.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class StringToBrushConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string colorStr || string.IsNullOrWhiteSpace(colorStr))
|
||||
{
|
||||
// 默认颜色(透明/黑色)
|
||||
return parameter?.ToString() == "Foreground"
|
||||
? Brushes.Black
|
||||
: Brushes.Transparent;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 处理十六进制颜色(#FF0000 或 FF0000)
|
||||
if (colorStr.StartsWith("#"))
|
||||
{
|
||||
return new SolidColorBrush(Color.Parse(colorStr));
|
||||
}
|
||||
// 处理命名颜色(Red/Green/Blue等)
|
||||
else if (Color.TryParse(colorStr, out var color))
|
||||
{
|
||||
return new SolidColorBrush(color);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 转换失败返回默认色
|
||||
return parameter?.ToString() == "Foreground"
|
||||
? Brushes.Black
|
||||
: Brushes.Transparent;
|
||||
}
|
||||
|
||||
// 兜底默认色
|
||||
return parameter?.ToString() == "Foreground"
|
||||
? Brushes.Black
|
||||
: Brushes.Transparent;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
38
Cowain.Base/Converters/StringToByteConverter.cs
Normal file
38
Cowain.Base/Converters/StringToByteConverter.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class StringToByteConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 将数据模型中的值转换为字符串,流向 UI 元素时
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户在 UI 中输入或修改了某些内容,并且这些更改需要反映到数据模型时
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null && byte.TryParse(value.ToString(), out var v))
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
38
Cowain.Base/Converters/StringToUIntConverter.cs
Normal file
38
Cowain.Base/Converters/StringToUIntConverter.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class StringToUIntConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 将数据模型中的值转换为字符串,流向 UI 元素时
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户在 UI 中输入或修改了某些内容,并且这些更改需要反映到数据模型时
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value != null && uint.TryParse(value.ToString(), out var intValue))
|
||||
{
|
||||
return intValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
17
Cowain.Base/Converters/StringVisibilityConverter.cs
Normal file
17
Cowain.Base/Converters/StringVisibilityConverter.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public static class StringVisibilityConverter
|
||||
{
|
||||
|
||||
public static readonly IValueConverter IsNullOrEmpty =
|
||||
new FuncValueConverter<string?, bool>(x => string.IsNullOrEmpty(x));
|
||||
|
||||
/// <summary>
|
||||
/// A value converter that returns true if the input object is not null.
|
||||
/// </summary>
|
||||
public static readonly IValueConverter IsNotNullOrEmpty =
|
||||
new FuncValueConverter<string?, bool>(x => !string.IsNullOrEmpty(x));
|
||||
|
||||
}
|
||||
27
Cowain.Base/Converters/ToastrIconConverter.cs
Normal file
27
Cowain.Base/Converters/ToastrIconConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using Cowain.Base.Models.Controls;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Cowain.Base.Converters;
|
||||
|
||||
public class ToastrIconConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ToastrType toastrType)
|
||||
{
|
||||
return toastrType switch
|
||||
{
|
||||
ToastrType.Success => StreamGeometry.Parse("M512 21.333333C782.976 21.333333 1002.666667 241.024 1002.666667 512S782.976 1002.666667 512 1002.666667 21.333333 782.976 21.333333 512 241.024 21.333333 512 21.333333z m211.925333 337.408a35.029333 35.029333 0 0 0-49.536 0l-222.805333 222.72-90.026667-90.026666a35.072 35.072 0 1 0-49.578666 49.621333l111.530666 111.573333a35.413333 35.413333 0 0 0 11.605334 9.770667 35.029333 35.029333 0 0 0 47.573333-12.8l241.237333-241.322667a35.029333 35.029333 0 0 0 0-49.578666z"),
|
||||
_ => StreamGeometry.Parse("M512 128a384 384 0 1 1-0.042667 768.042667A384 384 0 0 1 512 128z m-19.2 553.258667l76.501333-248.362667-16-1.408-91.52 11.690667-4.693333 13.994666c3.754667 0.085333 6.954667 0.256 9.6 0.512a17.066667 17.066667 0 0 1 13.226667 7.253334c2.986667 4.266667 4.266667 9.088 3.84 14.293333-0.725333 8.277333-3.584 20.48-8.533334 36.565333l-51.754666 166.954667c-4.181333 13.312-6.656 24.277333-7.424 33.109333-1.152 13.226667 1.28 24.533333 7.338666 33.792a33.28 33.28 0 0 0 26.282667 15.402667c30.08 2.645333 61.098667-26.112 93.269333-86.229333l-8.362666-7.936c-13.141333 22.016-24.106667 37.077333-32.853334 45.184-3.285333 3.328-6.4 4.864-9.429333 4.565333-1.834667-0.128-3.413333-1.365333-4.778667-3.797333a12.714667 12.714667 0 0 1-1.706666-7.509334c0.384-4.437333 2.688-13.866667 6.954666-28.074666zM554.496 384a41.301333 41.301333 0 0 0 30.293333-12.458667A41.301333 41.301333 0 0 0 597.333333 341.333333a41.429333 41.429333 0 0 0-12.373333-30.208 41.344 41.344 0 0 0-30.421333-12.458666 40.96 40.96 0 0 0-30.165334 12.458666A41.429333 41.429333 0 0 0 512 341.333333c0 11.776 4.138667 21.845333 12.501333 30.208 8.405333 8.277333 18.346667 12.458667 30.037334 12.458667z")
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
44
Cowain.Base/Cowain.Base.csproj
Normal file
44
Cowain.Base/Cowain.Base.csproj
Normal file
@@ -0,0 +1,44 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Company>Cowain Zhusenlin</Company>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspectInjector" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="Dapper" />
|
||||
<PackageReference Include="Fody">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Irihi.Ursa" />
|
||||
<PackageReference Include="Ke.Bee.Localization" />
|
||||
<PackageReference Include="MethodBoundaryAspect.Fody" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="NPOI" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Xaml.Behaviors.Avalonia" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Themes\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
13
Cowain.Base/DBContext/IDataSeeding.cs
Normal file
13
Cowain.Base/DBContext/IDataSeeding.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.DBContext;
|
||||
|
||||
public interface IDataSeeding
|
||||
{
|
||||
void DataSeeding(ModelBuilder modelBuilder);
|
||||
}
|
||||
107
Cowain.Base/DBContext/SqlDbContext.cs
Normal file
107
Cowain.Base/DBContext/SqlDbContext.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Cowain.Base.Helpers;
|
||||
using Cowain.Base.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Cowain.Base.DBContext;
|
||||
|
||||
public class SqlDbContext : DbContext
|
||||
{
|
||||
private List<Assembly>? _assemblies;
|
||||
/// <summary>
|
||||
/// dbcontext
|
||||
/// </summary>
|
||||
/// <param name="options">options</param>
|
||||
public SqlDbContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
_assemblies = [.. GetAssembliesFromPluginDirectory()];
|
||||
}
|
||||
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
}
|
||||
|
||||
override protected void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
MapModelEntities(modelBuilder);
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// over write EnsureCreated
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool EnsureCreated()
|
||||
{
|
||||
return Database.EnsureCreated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// over write EnsureCreated
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> EnsureCreatedAsync()
|
||||
{
|
||||
return await Database.EnsureCreatedAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto Mapping Entity
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">ModelBuilder</param>
|
||||
private void MapModelEntities(ModelBuilder modelBuilder)
|
||||
{
|
||||
var baseModelType = typeof(BaseModel);
|
||||
var entityTypes = _assemblies?
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(baseModelType));
|
||||
if (entityTypes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var type in entityTypes)
|
||||
{
|
||||
modelBuilder.Model.AddEntityType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all assemblies including those in the plugin directory
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable<Assembly></returns>
|
||||
private IEnumerable<Assembly> GetAssembliesFromPluginDirectory()
|
||||
{
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
|
||||
var pluginPath = Path.Combine(AppContext.BaseDirectory, "Plugins");
|
||||
if (Directory.Exists(pluginPath))
|
||||
{
|
||||
var pluginAssemblies = Directory.GetFiles(pluginPath, "Plugin.*.dll")
|
||||
.Select(Assembly.LoadFrom);
|
||||
assemblies.AddRange(pluginAssemblies);
|
||||
}
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// create DbSet
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Entity</typeparam>
|
||||
/// <returns></returns>
|
||||
public virtual DbSet<T> GetDbSet<T>() where T : class
|
||||
{
|
||||
if (Model.FindEntityType(typeof(T)) != null)
|
||||
{
|
||||
return Set<T>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"type {typeof(T).Name} is not add into DbContext ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
47
Cowain.Base/Extensions/GlobalDataExtensions.cs
Normal file
47
Cowain.Base/Extensions/GlobalDataExtensions.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Markup.Xaml.MarkupExtensions;
|
||||
using Cowain.Base.Helpers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Cowain.Base.Extensions;
|
||||
|
||||
public class GlobalDataExtensions : MarkupExtension
|
||||
{
|
||||
private string _key;
|
||||
private string? _path;
|
||||
public GlobalDataExtensions(string key)
|
||||
{
|
||||
_key = key;
|
||||
}
|
||||
public GlobalDataExtensions(string key, string path) : this(key)
|
||||
{
|
||||
_path = path;
|
||||
}
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
var binding = new ReflectionBindingExtension("[" + _key + "]")
|
||||
{
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = GlobalData.Instance
|
||||
};
|
||||
return binding.ProvideValue(serviceProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
//绑定复杂的对象表达式
|
||||
var bindingPath = $"[{_key}].{_path}";
|
||||
var binding = new ReflectionBindingExtension(bindingPath)
|
||||
{
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = GlobalData.Instance
|
||||
};
|
||||
return binding.ProvideValue(serviceProvider);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
41
Cowain.Base/Extensions/MenuEnableExtension.cs
Normal file
41
Cowain.Base/Extensions/MenuEnableExtension.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Cowain.Base.Helpers;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Cowain.Base.Extensions;
|
||||
|
||||
public class MenuEnableExtension : MarkupExtension
|
||||
{
|
||||
private string _pageName;
|
||||
private string _menuKey;
|
||||
public MenuEnableExtension(string pageName, string menuKey)
|
||||
{
|
||||
_pageName = pageName;
|
||||
_menuKey = menuKey;
|
||||
}
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
var _executes = GetMenuActions(_pageName);
|
||||
if (_executes == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _executes.Any(action => action.Equals(_menuKey, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public ObservableCollection<string>? GetMenuActions(string? pageName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pageName) || !pageName.EndsWith("View"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var menuKey = pageName.Substring(0, pageName.Length - "View".Length);
|
||||
var menu = GlobalData.Instance.CurrentUser?.Menus?.FirstOrDefault(menu => menu.MenuKey == menuKey);
|
||||
if (menu == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return menu.MenuActions;
|
||||
}
|
||||
|
||||
}
|
||||
27
Cowain.Base/Extensions/ShortArrayExtensions.cs
Normal file
27
Cowain.Base/Extensions/ShortArrayExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace Cowain.Base.Extensions;
|
||||
|
||||
public static class ShortArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 short[] 转换为 byte[],并可选择大小端。
|
||||
/// </summary>
|
||||
/// <param name="src">源数组</param>
|
||||
/// <param name="asLittleEndian">true 为小端,false 为大端</param>
|
||||
public static byte[] ToByteArray(this short[] src, bool asLittleEndian = true)
|
||||
{
|
||||
if (src == null) throw new ArgumentNullException(nameof(src));
|
||||
|
||||
var dst = new byte[src.Length * sizeof(short)];
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
var span = dst.AsSpan(i * sizeof(short));
|
||||
if (asLittleEndian)
|
||||
BinaryPrimitives.WriteInt16LittleEndian(span, src[i]);
|
||||
else
|
||||
BinaryPrimitives.WriteInt16BigEndian(span, src[i]);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
3
Cowain.Base/FodyWeavers.xml
Normal file
3
Cowain.Base/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<MethodBoundaryAspect />
|
||||
</Weavers>
|
||||
26
Cowain.Base/Helpers/ArgumentExceptionHelper.cs
Normal file
26
Cowain.Base/Helpers/ArgumentExceptionHelper.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
using Cowain.Base.Abstractions.Navigation;
|
||||
using Cowain.Base.Models.Navigation;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 参数异常助手类
|
||||
/// </summary>
|
||||
public class ArgumentExceptionHelper
|
||||
{
|
||||
public static void ThrowIfNullOrEmpty(string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(argument))
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
|
||||
public static void ThrowIfOutOfRange(bool argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
|
||||
{
|
||||
if (argument)
|
||||
throw new ArgumentOutOfRangeException(paramName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
49
Cowain.Base/Helpers/BackgroundHostedService.cs
Normal file
49
Cowain.Base/Helpers/BackgroundHostedService.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public abstract class BackgroundHostedService : IHostedService, IDisposable
|
||||
{
|
||||
private Task? _executeTask;
|
||||
|
||||
private CancellationTokenSource? _stoppingCts;
|
||||
|
||||
public virtual Task? ExecuteTask => _executeTask;
|
||||
|
||||
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
|
||||
|
||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_executeTask = ExecuteAsync(_stoppingCts.Token);
|
||||
if (_executeTask.IsCompleted)
|
||||
{
|
||||
return _executeTask;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_executeTask != null)
|
||||
{
|
||||
var stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
try
|
||||
{
|
||||
stoppingCts.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _executeTask.WaitAsync(stoppingCts.Token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_stoppingCts?.Cancel();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
15
Cowain.Base/Helpers/BindingProxy.cs
Normal file
15
Cowain.Base/Helpers/BindingProxy.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Avalonia;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class BindingProxy : AvaloniaObject
|
||||
{
|
||||
public static readonly StyledProperty<object?> DataContextProperty =
|
||||
AvaloniaProperty.Register<BindingProxy, object?>(nameof(DataContext));
|
||||
|
||||
public object? DataContext
|
||||
{
|
||||
get => GetValue(DataContextProperty);
|
||||
set => SetValue(DataContextProperty, value);
|
||||
}
|
||||
}
|
||||
75
Cowain.Base/Helpers/DESHelper.cs
Normal file
75
Cowain.Base/Helpers/DESHelper.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class DESHelper
|
||||
{
|
||||
|
||||
public static string Encrypt(string password, string key)
|
||||
{
|
||||
if (key.Length != 8)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
using (DES des = DES.Create())
|
||||
{
|
||||
byte[] bytes = Encoding.Default.GetBytes(password);
|
||||
des.Key = Encoding.ASCII.GetBytes(key);
|
||||
des.IV = Encoding.ASCII.GetBytes(key);
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, des.CreateEncryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cryptoStream.Write(bytes, 0, bytes.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
byte[] array = memoryStream.ToArray();
|
||||
foreach (byte b in array)
|
||||
{
|
||||
stringBuilder.AppendFormat("{0:X2}", b);
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string Decrypt(string password, string key)
|
||||
{
|
||||
if (key.Length != 8)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
using (DES des = DES.Create())
|
||||
{
|
||||
byte[] array = new byte[password.Length / 2];
|
||||
for (int i = 0; i < password.Length / 2; i++)
|
||||
{
|
||||
int num = Convert.ToInt32(password.Substring(i * 2, 2), 16);
|
||||
array[i] = (byte)num;
|
||||
}
|
||||
|
||||
des.Key = Encoding.ASCII.GetBytes(key);
|
||||
des.IV = Encoding.ASCII.GetBytes(key);
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, des.CreateDecryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cryptoStream.Write(array, 0, array.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
return Encoding.Default.GetString(memoryStream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Cowain.Base/Helpers/Debounce.cs
Normal file
25
Cowain.Base/Helpers/Debounce.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
/// <summary>
|
||||
/// 防抖器
|
||||
/// </summary>
|
||||
public class Debounce
|
||||
{
|
||||
private readonly int _delay;
|
||||
private Timer? _timer;
|
||||
|
||||
public Debounce(int delay)
|
||||
{
|
||||
_delay = delay;
|
||||
}
|
||||
|
||||
public void Trigger(Action action)
|
||||
{
|
||||
// 清除之前的定时器
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
|
||||
// 创建新的定时器
|
||||
_timer = new Timer(_ => action(), null, _delay, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
155
Cowain.Base/Helpers/ExcelHelper.cs
Normal file
155
Cowain.Base/Helpers/ExcelHelper.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using Cowain.Base.Models;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class ExcelHelper<T> where T : class
|
||||
{
|
||||
public static ResultModel ExportExcel(List<T> data, string filePath)
|
||||
{
|
||||
if (data == null || data.Count == 0)
|
||||
return ResultModel.Error("数据不能为空");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return ResultModel.Error("路径不能为空");
|
||||
try
|
||||
{
|
||||
// 创建Excel工作簿
|
||||
IWorkbook workbook = new XSSFWorkbook();
|
||||
ISheet sheet = workbook.CreateSheet("Sheet1");
|
||||
|
||||
// 获取对象属性作为表头
|
||||
var properties = typeof(T).GetProperties();
|
||||
|
||||
// 创建表头行
|
||||
IRow headerRow = sheet.CreateRow(0);
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
{
|
||||
headerRow.CreateCell(i).SetCellValue(properties[i].Name);
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
IRow row = sheet.CreateRow(i + 1);
|
||||
var item = data[i];
|
||||
|
||||
for (int j = 0; j < properties.Length; j++)
|
||||
{
|
||||
var value = properties[j].GetValue(item)?.ToString() ?? "";
|
||||
row.CreateCell(j).SetCellValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
workbook.Write(fs);
|
||||
}
|
||||
return ResultModel.Success("导出成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ResultModel.Error($"错误:{ex.Message}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static async Task<ResultModel> ExportExcelAsync(List<T> data, string filePath)
|
||||
{
|
||||
if (data == null || data.Count == 0)
|
||||
return ResultModel.Error("数据不能为空");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return ResultModel.Error("路径不能为空");
|
||||
try
|
||||
{
|
||||
// 创建Excel工作簿
|
||||
IWorkbook workbook = new XSSFWorkbook();
|
||||
ISheet sheet = workbook.CreateSheet("Sheet1");
|
||||
|
||||
// 获取对象属性作为表头
|
||||
var properties = typeof(T).GetProperties();
|
||||
|
||||
// 创建表头行
|
||||
IRow headerRow = sheet.CreateRow(0);
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
{
|
||||
headerRow.CreateCell(i).SetCellValue(properties[i].Name);
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
IRow row = sheet.CreateRow(i + 1);
|
||||
var item = data[i];
|
||||
|
||||
for (int j = 0; j < properties.Length; j++)
|
||||
{
|
||||
var value = properties[j].GetValue(item)?.ToString() ?? "";
|
||||
row.CreateCell(j).SetCellValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await Task.Run(() => workbook.Write(fs));
|
||||
}
|
||||
return ResultModel.Success("导出成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ResultModel.Error($"错误:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static ResultModel<List<T>> ImportExcel(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
return ResultModel<List<T>>.Error("路径不能为空");
|
||||
|
||||
try
|
||||
{
|
||||
List<T> data = new List<T>();
|
||||
IWorkbook workbook;
|
||||
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
workbook = new XSSFWorkbook(fs);
|
||||
}
|
||||
|
||||
ISheet sheet = workbook.GetSheetAt(0);
|
||||
if (sheet == null)
|
||||
return ResultModel<List<T>>.Error("未找到工作表");
|
||||
|
||||
var properties = typeof(T).GetProperties();
|
||||
for (int i = 1; i <= sheet.LastRowNum; i++)
|
||||
{
|
||||
IRow row = sheet.GetRow(i);
|
||||
if (row == null) continue;
|
||||
|
||||
T item = Activator.CreateInstance<T>();
|
||||
for (int j = 0; j < properties.Length; j++)
|
||||
{
|
||||
var cell = row.GetCell(j);
|
||||
if (cell != null)
|
||||
{
|
||||
var value = cell.ToString();
|
||||
properties[j].SetValue(item, Convert.ChangeType(value, properties[j].PropertyType));
|
||||
}
|
||||
}
|
||||
data.Add(item);
|
||||
}
|
||||
|
||||
return ResultModel<List<T>>.Success(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ResultModel<List<T>>.Error($"错误:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
63
Cowain.Base/Helpers/FileDialogHelper.cs
Normal file
63
Cowain.Base/Helpers/FileDialogHelper.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Avalonia.Platform.Storage;
|
||||
using Cowain.Base.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public static class FileDialogHelper
|
||||
{
|
||||
public static IStorageProvider? Provider { get; set; }
|
||||
|
||||
public static async Task<ResultModel<IStorageFile>> OpenFileDialogAsync()
|
||||
{
|
||||
var files = await OpenFileDialogAsync(new List<FilePickerFileType>() { FilePickerFileTypes.All }, false);
|
||||
if (!files.IsSuccess || files.Data is null || !files.Data.Any())
|
||||
return ResultModel<IStorageFile>.Error(files.ErrorMessage);
|
||||
return ResultModel<IStorageFile>.Success(files.Data.First());
|
||||
}
|
||||
|
||||
public static async Task<ResultModel<IStorageFile>> OpenFileDialogAsync(List<FilePickerFileType>? fileTypes)
|
||||
{
|
||||
var files = await OpenFileDialogAsync(fileTypes, false);
|
||||
if (!files.IsSuccess || files.Data is null || !files.Data.Any())
|
||||
return ResultModel<IStorageFile>.Error(files.ErrorMessage);
|
||||
return ResultModel<IStorageFile>.Success(files.Data.First());
|
||||
}
|
||||
|
||||
public static async Task<ResultModel<IReadOnlyList<IStorageFile>>> OpenFileDialogAsync(List<FilePickerFileType>? fileTypes, bool multiple = false)
|
||||
{
|
||||
if (Provider is null) return ResultModel<IReadOnlyList<IStorageFile>>.Error("Provider is null");
|
||||
var result = await Provider.OpenFilePickerAsync(new FilePickerOpenOptions()
|
||||
{
|
||||
Title = "Open File",
|
||||
FileTypeFilter = fileTypes,
|
||||
AllowMultiple = multiple,
|
||||
});
|
||||
if (result is null) return ResultModel<IReadOnlyList<IStorageFile>>.Error("file is null");
|
||||
return ResultModel<IReadOnlyList<IStorageFile>>.Success(result);
|
||||
}
|
||||
|
||||
public static async Task<ResultModel<IStorageFile>> SaveFileDialogAsync()
|
||||
{
|
||||
return await SaveFileDialogAsync(new List<FilePickerFileType>() { FilePickerFileTypes.All });
|
||||
}
|
||||
|
||||
|
||||
public static async Task<ResultModel<IStorageFile>> SaveFileDialogAsync(List<FilePickerFileType>? fileTypes)
|
||||
{
|
||||
if (Provider is null) return ResultModel<IStorageFile>.Error("Provider is null");
|
||||
|
||||
var result = await Provider.SaveFilePickerAsync(new FilePickerSaveOptions()
|
||||
{
|
||||
Title = "Save File",
|
||||
FileTypeChoices = fileTypes,
|
||||
});
|
||||
if (result is null) return ResultModel<IStorageFile>.Error("file is null");
|
||||
return ResultModel<IStorageFile>.Success(result);
|
||||
}
|
||||
|
||||
}
|
||||
72
Cowain.Base/Helpers/GlobalData.cs
Normal file
72
Cowain.Base/Helpers/GlobalData.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.ViewModels;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class GlobalData : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
public void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
private static GlobalData? _instance;
|
||||
private static readonly object _lock = new object();
|
||||
private GlobalData()
|
||||
{
|
||||
}
|
||||
|
||||
public static GlobalData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new GlobalData();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<string, object> _activeDictionary = new Dictionary<string, object>();
|
||||
|
||||
public LoginUserViewModel? CurrentUser { get; set; }
|
||||
public void AddOrUpdate(string key, object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_activeDictionary.ContainsKey(key))
|
||||
{
|
||||
_activeDictionary[key] = value;
|
||||
//字典中的值发生变化时,触发PropertyChanged事件
|
||||
RaisePropertyChanged("Item");
|
||||
}
|
||||
else
|
||||
{
|
||||
_activeDictionary.Add(key, value);
|
||||
// 添加新键时通知整个索引器
|
||||
RaisePropertyChanged("Item[]");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public object? this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _activeDictionary.TryGetValue(key, out var value) ? value : null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
98
Cowain.Base/Helpers/GridDefinitionsHelper.cs
Normal file
98
Cowain.Base/Helpers/GridDefinitionsHelper.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class GridDefinitionsHelper
|
||||
{
|
||||
// 定义Rows附加属性
|
||||
public static readonly AttachedProperty<int> RowsProperty =
|
||||
AvaloniaProperty.RegisterAttached<GridDefinitionsHelper, Grid, int>("Rows", 0);
|
||||
|
||||
// 定义Columns附加属性
|
||||
public static readonly AttachedProperty<int> ColumnsProperty =
|
||||
AvaloniaProperty.RegisterAttached<GridDefinitionsHelper, Grid, int>("Columns", 0);
|
||||
|
||||
// 定义RowHeight附加属性
|
||||
public static readonly AttachedProperty<string> RowHeightProperty =
|
||||
AvaloniaProperty.RegisterAttached<GridDefinitionsHelper, Grid, string>("RowHeight", "Auto");
|
||||
|
||||
// 定义ColumnWidth附加属性
|
||||
public static readonly AttachedProperty<string> ColumnWidthProperty =
|
||||
AvaloniaProperty.RegisterAttached<GridDefinitionsHelper, Grid, string>("ColumnWidth", "Auto");
|
||||
|
||||
// Getter和Setter方法
|
||||
public static int GetRows(AvaloniaObject element) => element.GetValue(RowsProperty);
|
||||
public static void SetRows(AvaloniaObject element, int value) => element.SetValue(RowsProperty, value);
|
||||
|
||||
public static int GetColumns(AvaloniaObject element) => element.GetValue(ColumnsProperty);
|
||||
public static void SetColumns(AvaloniaObject element, int value) => element.SetValue(ColumnsProperty, value);
|
||||
|
||||
public static string GetRowHeight(AvaloniaObject element) => element.GetValue(RowHeightProperty);
|
||||
public static void SetRowHeight(AvaloniaObject element, string value) => element.SetValue(RowHeightProperty, value);
|
||||
|
||||
public static string GetColumnWidth(AvaloniaObject element) => element.GetValue(ColumnWidthProperty);
|
||||
public static void SetColumnWidth(AvaloniaObject element, string value) => element.SetValue(ColumnWidthProperty, value);
|
||||
|
||||
static GridDefinitionsHelper()
|
||||
{
|
||||
// 监听Rows属性变化
|
||||
RowsProperty.Changed.AddClassHandler<Grid>((grid, e) => UpdateGridDefinitions(grid));
|
||||
|
||||
// 监听Columns属性变化
|
||||
ColumnsProperty.Changed.AddClassHandler<Grid>((grid, e) => UpdateGridDefinitions(grid));
|
||||
|
||||
// 监听RowHeight属性变化
|
||||
RowHeightProperty.Changed.AddClassHandler<Grid>((grid, e) => UpdateGridDefinitions(grid));
|
||||
|
||||
// 监听ColumnWidth属性变化
|
||||
ColumnWidthProperty.Changed.AddClassHandler<Grid>((grid, e) => UpdateGridDefinitions(grid));
|
||||
}
|
||||
|
||||
private static void UpdateGridDefinitions(Grid grid)
|
||||
{
|
||||
int rows = GetRows(grid);
|
||||
int columns = GetColumns(grid);
|
||||
string rowHeight = GetRowHeight(grid);
|
||||
string columnWidth = GetColumnWidth(grid);
|
||||
|
||||
// 更新行定义
|
||||
grid.RowDefinitions.Clear();
|
||||
var height = ParseDimension(rowHeight);
|
||||
for (int i = 0; i < rows; i++)
|
||||
{
|
||||
grid.RowDefinitions.Add(new RowDefinition(height));
|
||||
}
|
||||
|
||||
// 更新列定义
|
||||
grid.ColumnDefinitions.Clear();
|
||||
var width = ParseDimension(columnWidth);
|
||||
for (int i = 0; i < columns; i++)
|
||||
{
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition(width));
|
||||
}
|
||||
}
|
||||
|
||||
private static GridLength ParseDimension(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return GridLength.Auto;
|
||||
|
||||
value = value.Trim();
|
||||
if (value.EndsWith("*"))
|
||||
{
|
||||
if (value.Length == 1)
|
||||
return new GridLength(1, GridUnitType.Star);
|
||||
|
||||
if (double.TryParse(value.Substring(0, value.Length - 1), out double starValue))
|
||||
return new GridLength(starValue, GridUnitType.Star);
|
||||
}
|
||||
else if (double.TryParse(value, out double pixelValue))
|
||||
{
|
||||
return new GridLength(pixelValue, GridUnitType.Pixel);
|
||||
}
|
||||
|
||||
return GridLength.Auto;
|
||||
}
|
||||
}
|
||||
82
Cowain.Base/Helpers/GridHelper.cs
Normal file
82
Cowain.Base/Helpers/GridHelper.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class GridHelper
|
||||
{
|
||||
// 定义RowDefinitions附加属性
|
||||
public static readonly AttachedProperty<ObservableCollection<RowDefinition>> RowDefinitionsProperty =
|
||||
AvaloniaProperty.RegisterAttached<Grid, ObservableCollection<RowDefinition>>(
|
||||
"RowDefinitions", typeof(GridHelper));
|
||||
|
||||
// 定义ColumnDefinitions附加属性
|
||||
public static readonly AttachedProperty<ObservableCollection<ColumnDefinition>> ColumnDefinitionsProperty =
|
||||
AvaloniaProperty.RegisterAttached<Grid, ObservableCollection<ColumnDefinition>>(
|
||||
"ColumnDefinitions", typeof(GridHelper));
|
||||
|
||||
// RowDefinitions属性的Get方法
|
||||
public static ObservableCollection<RowDefinition> GetRowDefinitions(AvaloniaObject element)
|
||||
{
|
||||
return element.GetValue(RowDefinitionsProperty);
|
||||
}
|
||||
|
||||
// RowDefinitions属性的Set方法
|
||||
public static void SetRowDefinitions(AvaloniaObject element, ObservableCollection<RowDefinition> value)
|
||||
{
|
||||
element.SetValue(RowDefinitionsProperty, value);
|
||||
|
||||
// 当属性值变化时,更新Grid的RowDefinitions
|
||||
if (element is Grid grid && value != null)
|
||||
{
|
||||
grid.RowDefinitions.Clear();
|
||||
foreach (var definition in value)
|
||||
{
|
||||
grid.RowDefinitions.Add(definition);
|
||||
}
|
||||
|
||||
// 监听集合变化,动态更新Grid
|
||||
value.CollectionChanged += (sender, e) =>
|
||||
{
|
||||
grid.RowDefinitions.Clear();
|
||||
foreach (var definition in value)
|
||||
{
|
||||
grid.RowDefinitions.Add(definition);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnDefinitions属性的Get方法
|
||||
public static ObservableCollection<ColumnDefinition> GetColumnDefinitions(AvaloniaObject element)
|
||||
{
|
||||
return element.GetValue(ColumnDefinitionsProperty);
|
||||
}
|
||||
|
||||
// ColumnDefinitions属性的Set方法
|
||||
public static void SetColumnDefinitions(AvaloniaObject element, ObservableCollection<ColumnDefinition> value)
|
||||
{
|
||||
element.SetValue(ColumnDefinitionsProperty, value);
|
||||
|
||||
// 当属性值变化时,更新Grid的ColumnDefinitions
|
||||
if (element is Grid grid && value != null)
|
||||
{
|
||||
grid.ColumnDefinitions.Clear();
|
||||
foreach (var definition in value)
|
||||
{
|
||||
grid.ColumnDefinitions.Add(definition);
|
||||
}
|
||||
|
||||
// 监听集合变化,动态更新Grid
|
||||
value.CollectionChanged += (sender, e) =>
|
||||
{
|
||||
grid.ColumnDefinitions.Clear();
|
||||
foreach (var definition in value)
|
||||
{
|
||||
grid.ColumnDefinitions.Add(definition);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
105
Cowain.Base/Helpers/GridPositionAnimator.cs
Normal file
105
Cowain.Base/Helpers/GridPositionAnimator.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Avalonia.Animation;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Xaml.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class GridPositionAnimator : Behavior<Control>
|
||||
{
|
||||
|
||||
public static readonly StyledProperty<int> RowProperty =
|
||||
AvaloniaProperty.Register<GridPositionAnimator, int>("Row");
|
||||
|
||||
public static readonly StyledProperty<int> ColumnProperty =
|
||||
AvaloniaProperty.Register<GridPositionAnimator, int>("Column");
|
||||
|
||||
public int Row
|
||||
{
|
||||
get => GetValue(RowProperty);
|
||||
set => SetValue(RowProperty, value);
|
||||
}
|
||||
|
||||
public int Column
|
||||
{
|
||||
get => GetValue(ColumnProperty);
|
||||
set => SetValue(ColumnProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
if (AssociatedObject != null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
// 初始设置位置
|
||||
Grid.SetRow(AssociatedObject, Row);
|
||||
Grid.SetColumn(AssociatedObject, Column);
|
||||
|
||||
// 新的事件订阅方式
|
||||
RowProperty.Changed.AddClassHandler<GridPositionAnimator>((b, e) => b.OnRowChanged(e));
|
||||
ColumnProperty.Changed.AddClassHandler<GridPositionAnimator>((b, e) => b.OnColumnChanged(e));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRowChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (AssociatedObject != null && e.NewValue is int newRow)
|
||||
{
|
||||
// 创建行变化动画
|
||||
var animation = new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
FillMode = FillMode.Both,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame
|
||||
{
|
||||
Cue = new Cue(0d),
|
||||
Setters = { new Setter { Property = Grid.RowProperty, Value = e.OldValue } }
|
||||
},
|
||||
new KeyFrame
|
||||
{
|
||||
Cue = new Cue(1d),
|
||||
Setters = { new Setter { Property = Grid.RowProperty, Value = newRow } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animation.RunAsync(AssociatedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnColumnChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (AssociatedObject != null && e.NewValue is int newColumn)
|
||||
{
|
||||
// 创建列变化动画
|
||||
var animation = new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
FillMode = FillMode.Both,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame
|
||||
{
|
||||
Cue = new Cue(0d),
|
||||
Setters = { new Setter { Property = Grid.ColumnProperty, Value = e.OldValue } }
|
||||
},
|
||||
new KeyFrame
|
||||
{
|
||||
Cue = new Cue(1d),
|
||||
Setters = { new Setter { Property = Grid.ColumnProperty, Value = newColumn } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
animation.RunAsync(AssociatedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Cowain.Base/Helpers/LogAndSwallowAttribute.cs
Normal file
14
Cowain.Base/Helpers/LogAndSwallowAttribute.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using MethodBoundaryAspect.Fody.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
|
||||
public sealed class LogAndSwallowAttribute : OnMethodBoundaryAspect
|
||||
{
|
||||
// 可全局统一处理,也能自定义
|
||||
public static Action<Exception>? OnExceptionAction { get; set; }
|
||||
|
||||
public override void OnException(MethodExecutionArgs args)
|
||||
{
|
||||
OnExceptionAction?.Invoke(args.Exception);
|
||||
args.FlowBehavior = FlowBehavior.Return; // 吃掉异常
|
||||
}
|
||||
}
|
||||
32
Cowain.Base/Helpers/Md5Helper.cs
Normal file
32
Cowain.Base/Helpers/Md5Helper.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public static class Md5Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 32bit UTF8 MD5 Encrypt
|
||||
/// </summary>
|
||||
/// <param name="plaintext">明文</param>
|
||||
/// <returns></returns>
|
||||
public static string Md5Encrypt32(string plaintext)
|
||||
{
|
||||
string pwd = string.Empty;
|
||||
if (!string.IsNullOrEmpty(plaintext) && !string.IsNullOrWhiteSpace(plaintext))
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(plaintext));
|
||||
foreach (var item in s)
|
||||
{
|
||||
pwd = string.Concat(pwd, item.ToString("x2"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return pwd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
190
Cowain.Base/Helpers/NodifyObservableCollection.cs
Normal file
190
Cowain.Base/Helpers/NodifyObservableCollection.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public interface INodifyObservableCollection<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a new item is added
|
||||
/// </summary>
|
||||
/// <param name="added">The callback to execute when an item is added</param>
|
||||
/// <returns>Returns self</returns>
|
||||
INodifyObservableCollection<T> WhenAdded(Action<T> added);
|
||||
|
||||
/// <summary>
|
||||
/// Called when an existing item is removed
|
||||
/// Note: It is not called when items are cleared if <see cref="WhenCleared(Action{IList{T}})"/> is used
|
||||
/// </summary>
|
||||
/// <param name="added">The callback to execute when an item is removed</param>
|
||||
/// <returns>Returns self</returns>
|
||||
INodifyObservableCollection<T> WhenRemoved(Action<T> removed);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the collection is cleared
|
||||
/// NOTE: It does not call <see cref="WhenRemoved(Action{T})"/> on each item
|
||||
/// </summary>
|
||||
/// <param name="added">The callback to execute when the collection is cleared</param>
|
||||
/// <returns>Returns self</returns>
|
||||
INodifyObservableCollection<T> WhenCleared(Action<IList<T>> cleared);
|
||||
}
|
||||
|
||||
public class NodifyObservableCollection<T> : Collection<T>, INodifyObservableCollection<T>, INotifyPropertyChanged, INotifyCollectionChanged
|
||||
{
|
||||
protected static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]");
|
||||
protected static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
|
||||
protected static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
|
||||
|
||||
private readonly List<Action<T>> _added = new List<Action<T>>();
|
||||
private readonly List<Action<T>> _removed = new List<Action<T>>();
|
||||
private readonly List<Action<IList<T>>> _cleared = new List<Action<IList<T>>>();
|
||||
|
||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public NodifyObservableCollection()
|
||||
{
|
||||
}
|
||||
|
||||
public NodifyObservableCollection(IEnumerable<T> collection)
|
||||
: base(new List<T>(collection))
|
||||
{
|
||||
}
|
||||
|
||||
#region Collection Events
|
||||
|
||||
public INodifyObservableCollection<T> WhenAdded(Action<T> added)
|
||||
{
|
||||
if (added != null)
|
||||
{
|
||||
_added.Add(added);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public INodifyObservableCollection<T> WhenRemoved(Action<T> removed)
|
||||
{
|
||||
if (removed != null)
|
||||
{
|
||||
_removed.Add(removed);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public INodifyObservableCollection<T> WhenCleared(Action<IList<T>> cleared)
|
||||
{
|
||||
if (cleared != null)
|
||||
{
|
||||
_cleared.Add(cleared);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected virtual void NotifyOnItemAdded(T item)
|
||||
{
|
||||
for (int i = 0; i < _added.Count; i++)
|
||||
{
|
||||
_added[i](item);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void NotifyOnItemRemoved(T item)
|
||||
{
|
||||
for (int i = 0; i < _removed.Count; i++)
|
||||
{
|
||||
_removed[i](item);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void NotifyOnItemsCleared(IList<T> items)
|
||||
{
|
||||
for (int i = 0; i < _cleared.Count; i++)
|
||||
{
|
||||
_cleared[i](items);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Collection Handlers
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
var items = _cleared.Count > 0 || _removed.Count > 0 ? new List<T>(this) : new List<T>();
|
||||
base.ClearItems();
|
||||
|
||||
if (_cleared.Count > 0)
|
||||
{
|
||||
NotifyOnItemsCleared(items);
|
||||
}
|
||||
else if (_removed.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
NotifyOnItemRemoved(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged(CountPropertyChanged);
|
||||
OnPropertyChanged(IndexerPropertyChanged);
|
||||
OnCollectionChanged(ResetCollectionChanged);
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, T item)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
|
||||
OnPropertyChanged(CountPropertyChanged);
|
||||
OnPropertyChanged(IndexerPropertyChanged);
|
||||
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
|
||||
NotifyOnItemAdded(item);
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
var item = base[index];
|
||||
base.RemoveItem(index);
|
||||
|
||||
OnPropertyChanged(CountPropertyChanged);
|
||||
OnPropertyChanged(IndexerPropertyChanged);
|
||||
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
|
||||
NotifyOnItemRemoved(item);
|
||||
}
|
||||
|
||||
protected override void SetItem(int index, T item)
|
||||
{
|
||||
T prev = base[index];
|
||||
base.SetItem(index, item);
|
||||
OnPropertyChanged(IndexerPropertyChanged);
|
||||
OnCollectionChanged(NotifyCollectionChangedAction.Replace, prev, item, index);
|
||||
NotifyOnItemRemoved(prev);
|
||||
NotifyOnItemAdded(item);
|
||||
}
|
||||
|
||||
public void Move(int oldIndex, int newIndex)
|
||||
{
|
||||
T prev = base[oldIndex];
|
||||
base.RemoveItem(oldIndex);
|
||||
base.InsertItem(newIndex, prev);
|
||||
OnPropertyChanged(IndexerPropertyChanged);
|
||||
OnCollectionChanged(NotifyCollectionChangedAction.Move, prev, newIndex, oldIndex);
|
||||
}
|
||||
|
||||
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
=> CollectionChanged?.Invoke(this, e);
|
||||
|
||||
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
|
||||
=> PropertyChanged?.Invoke(this, args);
|
||||
|
||||
private void OnCollectionChanged(NotifyCollectionChangedAction action, object? item, int index)
|
||||
=> OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
|
||||
|
||||
private void OnCollectionChanged(NotifyCollectionChangedAction action, object? item, int index, int oldIndex)
|
||||
=> OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
|
||||
|
||||
private void OnCollectionChanged(NotifyCollectionChangedAction action, object? oldItem, object? newItem, int index)
|
||||
=> OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
|
||||
|
||||
#endregion
|
||||
}
|
||||
30
Cowain.Base/Helpers/NotificationHelper.cs
Normal file
30
Cowain.Base/Helpers/NotificationHelper.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Notification = Ursa.Controls.Notification;
|
||||
using WindowNotificationManager = Ursa.Controls.WindowNotificationManager;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public static class NotificationHelper
|
||||
{
|
||||
public static WindowNotificationManager? NotificationManager { get; set; }
|
||||
|
||||
|
||||
public static void ShowNormal(NotificationType notificationType, string message)
|
||||
{
|
||||
NotificationManager?.Show(
|
||||
new Notification(notificationType.ToString(), message),
|
||||
showIcon: true,
|
||||
showClose: true,
|
||||
type: notificationType);
|
||||
}
|
||||
|
||||
public static void ShowLight(NotificationType notificationType, string message)
|
||||
{
|
||||
NotificationManager?.Show(
|
||||
new Notification(notificationType.ToString(), message),
|
||||
showIcon: true,
|
||||
showClose: true,
|
||||
type: notificationType,
|
||||
classes: ["Light"]);
|
||||
}
|
||||
}
|
||||
171
Cowain.Base/Helpers/PageViewMenuHelper.cs
Normal file
171
Cowain.Base/Helpers/PageViewMenuHelper.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Avalonia.Media;
|
||||
using Cowain.Base.Attributes;
|
||||
using Cowain.Base.Models.Menu;
|
||||
using Cowain.Base.ViewModels;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
[Singleton]
|
||||
public class PageViewMenuHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单数据
|
||||
/// </summary>
|
||||
private readonly List<MenuItem> _menuItems;
|
||||
|
||||
private readonly ILocalizer _l;
|
||||
public PageViewMenuHelper(MenuConfigurationContext menuContext, ILocalizer localizer)
|
||||
{
|
||||
_menuItems = menuContext.Menus;
|
||||
_l = localizer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MenuItem 到 MenuItemViewModel 的转换器
|
||||
/// </summary>
|
||||
public Func<MenuItem, MenuItemViewModel> MenuItemToViewModel => x => new MenuItemViewModel(_l[x.LocaleKey])
|
||||
{
|
||||
Key = x.Key,
|
||||
IsActive = x.IsActive == true,
|
||||
Icon = string.IsNullOrWhiteSpace(x.Icon) ? null : StreamGeometry.Parse(x.Icon),
|
||||
CommandParameter = x.CommandParameter,
|
||||
PageActions = x.PageActions,
|
||||
Items = x.Items.Select(MenuItemToViewModel).ToList(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 返回组菜单列表
|
||||
/// </summary>
|
||||
public IEnumerable<MenuItemViewModel> GetGroupMenus(string group)
|
||||
{
|
||||
return _menuItems.Where(x => x.Group == group).Select(MenuItemToViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有菜单的Active状态
|
||||
/// </summary>
|
||||
public void ClearMenuActives(IEnumerable<MenuItemViewModel>? menus)
|
||||
{
|
||||
if (menus is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var menu in menus)
|
||||
{
|
||||
menu.IsActive = false;
|
||||
if (menu.Items != null && menu.Items.Any())
|
||||
{
|
||||
ClearMenuActives(menu.Items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据菜单keys列表,设置Active状态
|
||||
/// </summary>
|
||||
public void SetMenuActiveByKeys(IEnumerable<MenuItemViewModel>? menus, List<string>? menuKeys)
|
||||
{
|
||||
if (menuKeys is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (menus is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var menu in menus)
|
||||
{
|
||||
if (menuKeys.Contains(menu.Key))
|
||||
{
|
||||
menu.IsActive = true;
|
||||
}
|
||||
if (menu.Items != null && menu.Items.Any())
|
||||
{
|
||||
SetMenuActiveByKeys(menu.Items, menuKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回所有激活的菜单
|
||||
/// </summary>
|
||||
public IEnumerable<UserRoleMenuViewModel>? GetActiveMenus(IEnumerable<MenuItemViewModel>? menus)
|
||||
{
|
||||
if (menus is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//递归返回所有选中的菜单
|
||||
return menus.Where(menu => menu.IsActive)
|
||||
.Select(menu => new UserRoleMenuViewModel
|
||||
{
|
||||
Id = 0, // 这里可以根据需要设置实际的 Id
|
||||
RoleId = 0, // 这里可以根据需要设置实际的 RoleId
|
||||
MenuKey = menu.Key,
|
||||
MenuName = menu.Text,
|
||||
MenuActions = new ObservableCollection<string>(menu.PageActions ?? Array.Empty<string>())
|
||||
})
|
||||
.Concat(menus.SelectMany(menu => GetActiveMenus(menu.Items) ?? Enumerable.Empty<UserRoleMenuViewModel>()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据菜单的key,获取菜单页面权限
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<string>? GetMenuActionsByKey(string? key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var menuItem = FindMenuItem(_menuItems, key);
|
||||
return menuItem?.PageActions?.ToList();
|
||||
}
|
||||
|
||||
public string? GetMenuName(string? menuKey)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(menuKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var menuItem = FindMenuItem(_menuItems, menuKey);
|
||||
return menuItem != null ? _l[menuItem.LocaleKey] : null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在菜单集合中查找指定Key的菜单项
|
||||
/// </summary>
|
||||
/// <param name="menus">菜单集合</param>
|
||||
/// <param name="key">菜单项的Key</param>
|
||||
/// <returns>找到的菜单项</returns>
|
||||
public MenuItem? FindMenuItem(IEnumerable<MenuItem>? menus, string? key)
|
||||
{
|
||||
if (menus == null || key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var menu in menus)
|
||||
{
|
||||
if (menu.Key == key)
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
|
||||
var found = FindMenuItem(menu.Items, key);
|
||||
if (found != null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
38
Cowain.Base/Helpers/PathHelper.cs
Normal file
38
Cowain.Base/Helpers/PathHelper.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
public class PathHelper
|
||||
{
|
||||
private static string _localFolder = string.Empty;
|
||||
|
||||
private static string LocalFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_localFolder))
|
||||
{
|
||||
return _localFolder;
|
||||
}
|
||||
_localFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
AppDomain.CurrentDomain.FriendlyName
|
||||
);
|
||||
if (!Directory.Exists(_localFolder))
|
||||
{
|
||||
Directory.CreateDirectory(_localFolder);
|
||||
}
|
||||
return _localFolder;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLocalFilePath(string flieName)
|
||||
{
|
||||
return Path.Combine(LocalFolder, flieName);
|
||||
}
|
||||
|
||||
}
|
||||
39
Cowain.Base/Helpers/ServiceLocator.cs
Normal file
39
Cowain.Base/Helpers/ServiceLocator.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 服务定位器
|
||||
/// </summary>
|
||||
public class ServiceLocator
|
||||
{
|
||||
private static IServiceProvider? _current;
|
||||
|
||||
public static IServiceProvider Current
|
||||
{
|
||||
get => _current!;
|
||||
set
|
||||
{
|
||||
if (_current != null)
|
||||
{
|
||||
throw new InvalidOperationException("The service locator has already been set.");
|
||||
}
|
||||
_current = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetRequiredService<T>() where T : class
|
||||
{
|
||||
return (T)Current.GetRequiredService(typeof(T));
|
||||
}
|
||||
|
||||
public static object GetRequiredService(Type T)
|
||||
{
|
||||
return Current.GetRequiredService(T);
|
||||
}
|
||||
}
|
||||
267
Cowain.Base/Helpers/UtilConvert.cs
Normal file
267
Cowain.Base/Helpers/UtilConvert.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
namespace Cowain.Base.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// convert utility
|
||||
/// </summary>
|
||||
public static class UtilConvert
|
||||
{
|
||||
#region object convert function
|
||||
/// <summary>
|
||||
/// object convert to int
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <returns></returns>
|
||||
public static int ObjToInt(this object thisValue)
|
||||
{
|
||||
int reval = 0;
|
||||
if (thisValue == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (string.IsNullOrEmpty(thisValue.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to int
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <param name="errorValue">value when error occured</param>
|
||||
/// <returns></returns>
|
||||
public static int ObjToInt(this object thisValue, int errorValue)
|
||||
{
|
||||
|
||||
if (thisValue == null)
|
||||
{
|
||||
return errorValue;
|
||||
}
|
||||
if (string.IsNullOrEmpty(thisValue.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out int reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to double
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <returns></returns>
|
||||
public static double ObjToDouble(this object thisValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(thisValue.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out double reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to double
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <param name="errorValue">value when error occured</param>
|
||||
/// <returns></returns>
|
||||
public static double ObjToDouble(this object thisValue, double errorValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(thisValue.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out double reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// object convert to string
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <returns></returns>
|
||||
public static string? ObjToString(this object thisValue)
|
||||
{
|
||||
return thisValue?.ToString()?.Trim();
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to decimal
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <returns></returns>
|
||||
public static decimal ObjToDecimal(this object thisValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(thisValue.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out decimal reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to decimal
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <param name="errorValue">value when error occured</param>
|
||||
/// <returns></returns>
|
||||
public static decimal ObjToDecimal(this object thisValue, decimal errorValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(thisValue.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out decimal reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to date
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ObjToDate(this object thisValue)
|
||||
{
|
||||
DateTime reval = Convert.ToDateTime("1900-01-01");
|
||||
if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
reval = Convert.ToDateTime(thisValue);
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
/// <summary>
|
||||
/// object convert to date
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <param name="errorValue">value when error occured</param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ObjToDate(this object thisValue, DateTime errorValue)
|
||||
{
|
||||
DateTime reval;
|
||||
if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return errorValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// object convert to bool
|
||||
/// </summary>
|
||||
/// <param name="thisValue">value</param>
|
||||
/// <returns></returns>
|
||||
public static bool ObjToBool(this object thisValue)
|
||||
{
|
||||
bool reval = false;
|
||||
if (thisValue != null && thisValue != DBNull.Value && bool.TryParse(thisValue.ToString(), out reval))
|
||||
{
|
||||
return reval;
|
||||
}
|
||||
return reval;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region compare function
|
||||
/// <summary>
|
||||
/// less than
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLessThan(this object thisValue, double compareValue)
|
||||
{
|
||||
return Convert.ToDouble(thisValue) < compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// less than or equal
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLessThanOrEqual(this object thisValue, double compareValue)
|
||||
{
|
||||
return Convert.ToDouble(thisValue) <= compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// greater than
|
||||
/// </summary>
|
||||
/// <param name="thisValue">当前值</param>
|
||||
/// <param name="compareValue">比较值</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsGreaterThan(this object thisValue, double compareValue)
|
||||
{
|
||||
return Convert.ToDouble(thisValue) > compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// greater than or equal
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsGreaterThanOrEqual(this object thisValue, double compareValue)
|
||||
{
|
||||
return Convert.ToDouble(thisValue) >= compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// less then
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLessThan(this object thisValue, DateTime compareValue)
|
||||
{
|
||||
return Convert.ToDateTime(thisValue) < compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// less than or equal
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLessThanOrEqual(this object thisValue, DateTime compareValue)
|
||||
{
|
||||
return Convert.ToDateTime(thisValue) <= compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// less then
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsGreaterThan(this object thisValue, DateTime compareValue)
|
||||
{
|
||||
return Convert.ToDateTime(thisValue) > compareValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// ess than or equal
|
||||
/// </summary>
|
||||
/// <param name="thisValue">thisValue</param>
|
||||
/// <param name="compareValue">compareValue</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsGreaterThanOrEqual(this object thisValue, DateTime compareValue)
|
||||
{
|
||||
return Convert.ToDateTime(thisValue) >= compareValue;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// default min date
|
||||
/// </summary>
|
||||
public static DateTime MinDate => Convert.ToDateTime("1900-01-01");
|
||||
}
|
||||
27
Cowain.Base/IServices/IAccountService.cs
Normal file
27
Cowain.Base/IServices/IAccountService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.ViewModels;
|
||||
|
||||
namespace Cowain.Base.IServices;
|
||||
|
||||
public interface IAccountService : IBaseService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有用户
|
||||
/// </summary>
|
||||
Task<List<UserViewModel>> GetAllAsync();
|
||||
|
||||
Task<(List<UserViewModel>, int totals)> GetAllAsync(int pageIndex, int pageSize);
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
/// <param name="userName">用户名</param>
|
||||
/// <param name="password">密码</param>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel<LoginUserViewModel>> LoginAsync(string userName, string password);
|
||||
|
||||
Task<ResultModel> CheckHealthAsync(CancellationToken cancellationToken = default);
|
||||
Task<ResultModel> AddUserAsync(UserViewModel? user);
|
||||
|
||||
Task<ResultModel> EditUserAsync(UserViewModel? user);
|
||||
Task<ResultModel> DelUserAsync(UserViewModel? user);
|
||||
}
|
||||
41
Cowain.Base/IServices/IBaseService.cs
Normal file
41
Cowain.Base/IServices/IBaseService.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using MySqlConnector;
|
||||
using System.Data;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Cowain.Base.IServices
|
||||
{
|
||||
public interface IBaseService
|
||||
{
|
||||
int Delete<T>(int Id) where T : class;
|
||||
int Delete<T>(T t) where T : class;
|
||||
Task<int> DeleteAsync<T>(int Id, CancellationToken cancellationToken = default) where T : class;
|
||||
Task<int> DeleteAsync<T>(T t, CancellationToken cancellationToken = default) where T : class;
|
||||
int ExecuteByDapper(string sql, object? param = null);
|
||||
Task<int> ExecuteByDapperAsync(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||
List<T> Find<T>() where T : class;
|
||||
T? Find<T>(int id) where T : class;
|
||||
Task<List<T>> FindAsync<T>(CancellationToken cancellationToken = default) where T : class;
|
||||
Task<T?> FindAsync<T>(int id, CancellationToken cancellationToken = default) where T : class;
|
||||
Task<T?> FirstOrDefaultAsync<T>(Expression<Func<T, bool>> funcWhere, CancellationToken cancellationToken = default) where T : class;
|
||||
DataTable GetDataTable(string sql, IEnumerable<MySqlParameter>? parameters = null);
|
||||
Task<DataTable> GetDataTableAsync(string sql, IEnumerable<MySqlParameter>? parameters = null, CancellationToken cancellationToken = default);
|
||||
int Insert<T>(IEnumerable<T> tList) where T : class;
|
||||
int Insert<T>(T t) where T : class;
|
||||
Task<int> InsertAsync<T>(IEnumerable<T> tList, CancellationToken cancellationToken = default) where T : class;
|
||||
Task<int> InsertAsync<T>(T t, CancellationToken cancellationToken = default) where T : class;
|
||||
List<T> Query<T>(Expression<Func<T, bool>>? funcWhere = null) where T : class;
|
||||
Task<(List<TViewModel> Data, int TotalCount)> QueryAsync<T, TKey, TViewModel>(Expression<Func<T, TViewModel>> selectExpression, Expression<Func<T, bool>>? funcWhere = null, Expression<Func<T, TKey>>? orderBy = null, bool isAscending = true, int pageIndex = 1, int pageSize = 20, CancellationToken cancellationToken = default) where T : class;
|
||||
Task<(List<T> Data, int TotalCount)> QueryAsync<T, TKey>(Expression<Func<T, bool>>? funcWhere = null, Expression<Func<T, TKey>>? orderBy = null, bool isAscending = true, int pageIndex = 1, int pageSize = 20, CancellationToken cancellationToken = default) where T : class;
|
||||
Task<List<T>> QueryAsync<T>(Expression<Func<T, bool>>? funcWhere = null, CancellationToken cancellationToken = default) where T : class;
|
||||
IEnumerable<T> QueryByDapper<T>(string sql, object? param = null);
|
||||
Task<IEnumerable<T>> QueryByDapperAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||
T? QueryFirstOrDefaultByDapper<T>(string sql, object? param = null);
|
||||
Task<T?> QueryFirstOrDefaultByDapperAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||
DataTable QueryToDataTableByDapper(string sql, object? param = null);
|
||||
Task<DataTable> QueryToDataTableByDapperAsync(string sql, object? param = null, CancellationToken cancellationToken = default);
|
||||
int Update<T>(IEnumerable<T> tList) where T : class;
|
||||
int Update<T>(T t) where T : class;
|
||||
Task<int> UpdateAsync<T>(IEnumerable<T> tList, CancellationToken cancellationToken = default) where T : class;
|
||||
Task<int> UpdateAsync<T>(T t, CancellationToken cancellationToken = default) where T : class;
|
||||
}
|
||||
}
|
||||
18
Cowain.Base/IServices/ILogService.cs
Normal file
18
Cowain.Base/IServices/ILogService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Cowain.Base.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.IServices;
|
||||
|
||||
public interface ILogService : IBaseService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有用户
|
||||
/// </summary>
|
||||
Task<List<SerilogViewModel>> GetAllAsync();
|
||||
|
||||
Task<(List<SerilogViewModel>, int totals)> GetAllAsync(int pageIndex, int pageSize);
|
||||
}
|
||||
24
Cowain.Base/IServices/IUserRoleMenuService.cs
Normal file
24
Cowain.Base/IServices/IUserRoleMenuService.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.ViewModels;
|
||||
|
||||
namespace Cowain.Base.IServices;
|
||||
|
||||
public interface IUserRoleMenuService : IBaseService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有菜单
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserRoleViewModel>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 修改角色菜单
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel> EditUserRoleMenuAsync(UserRoleViewModel? userRole);
|
||||
|
||||
Task<ResultModel> EditMenuActionsAsync(UserRoleMenuViewModel? userRoleMenu);
|
||||
|
||||
Task<ResultModel> DeleteMenuActionsAsync(UserRoleMenuViewModel? userRoleMenu);
|
||||
}
|
||||
39
Cowain.Base/IServices/IUserRoleService.cs
Normal file
39
Cowain.Base/IServices/IUserRoleService.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.ViewModels;
|
||||
|
||||
namespace Cowain.Base.IServices;
|
||||
|
||||
public interface IUserRoleService : IBaseService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有用户角色
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserRoleViewModel>> GetAllAsync();
|
||||
/// <summary>
|
||||
/// 获取所有用户角色,分页查询
|
||||
/// </summary>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <returns></returns>
|
||||
Task<(List<UserRoleViewModel>, int totals)> GetAllAsync(int pageIndex, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// 增加角色
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel> AddUserRoleAsync(UserRoleViewModel? userRole);
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel> DelUserRoleAsync(UserRoleViewModel? userRole);
|
||||
|
||||
/// <summary>
|
||||
/// 修改角色
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<ResultModel> EditUserRoleAsync(UserRoleViewModel? userRole);
|
||||
}
|
||||
77
Cowain.Base/Models/Admins/UserDto.cs
Normal file
77
Cowain.Base/Models/Admins/UserDto.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Cowain.Base.Models.Admins;
|
||||
|
||||
[Table("user")]
|
||||
public class UserDto : BaseModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 员工编号
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string UserNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 电话
|
||||
/// </summary>
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public int RoleId { get; set; }
|
||||
/// <summary>
|
||||
/// 性别
|
||||
/// </summary>
|
||||
public string Sex { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public bool IsValid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// 最后一次更新时间
|
||||
/// </summary>
|
||||
public DateTime UpdateTime { get; set; } = DateTime.MinValue;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class UserSeed : IDataSeeding
|
||||
{
|
||||
public void DataSeeding(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<UserDto>().HasData(
|
||||
new UserDto
|
||||
{
|
||||
Id = 1,
|
||||
Name = "admin",
|
||||
Sex = SexMode.Male.ToString(),
|
||||
RoleId = 1,
|
||||
UserNumber = "CWA4483",
|
||||
Phone = "17625760609",
|
||||
IsValid = true,
|
||||
Password = DESHelper.Encrypt("12345", "ZSL12345")
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
48
Cowain.Base/Models/Admins/UserRoleDto.cs
Normal file
48
Cowain.Base/Models/Admins/UserRoleDto.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Cowain.Base.Models.Admins;
|
||||
|
||||
[Table("user_role")]
|
||||
public class UserRoleDto : BaseModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string RoleName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// 最后一次更新时间
|
||||
/// </summary>
|
||||
public DateTime UpdateTime { get; set; } = DateTime.MinValue;
|
||||
public bool IsValid { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class UserRoleSeed : IDataSeeding
|
||||
{
|
||||
public void DataSeeding(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<UserRoleDto>().HasData(
|
||||
new UserRoleDto
|
||||
{
|
||||
Id = 1,
|
||||
RoleName = "管理员",
|
||||
IsValid = true
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
58
Cowain.Base/Models/Admins/UserRoleMenuDto.cs
Normal file
58
Cowain.Base/Models/Admins/UserRoleMenuDto.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Cowain.Base.Models.Admins;
|
||||
|
||||
|
||||
[Table("user_role_menu")]
|
||||
public class UserRoleMenuDto : BaseModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public int RoleId { get; set; }
|
||||
public string? MenuKey { get; set; }
|
||||
public string? MenuActions { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class UserRoleMenuSeed : IDataSeeding
|
||||
{
|
||||
public void DataSeeding(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<UserRoleMenuDto>().HasData(
|
||||
new UserRoleMenuDto
|
||||
{
|
||||
Id = 1,
|
||||
RoleId = 1,
|
||||
MenuKey = "Home",
|
||||
MenuActions = "[]"
|
||||
},
|
||||
new UserRoleMenuDto
|
||||
{
|
||||
Id = 2,
|
||||
RoleId = 1,
|
||||
MenuKey = "UserRoleSetting",
|
||||
MenuActions = "[ \"add\", \"edit\", \"delete\"]"
|
||||
},
|
||||
new UserRoleMenuDto
|
||||
{
|
||||
Id = 3,
|
||||
RoleId = 1,
|
||||
MenuKey = "RoleMenuSetting",
|
||||
MenuActions = "[ \"edit\", \"delete\"]"
|
||||
},
|
||||
new UserRoleMenuDto
|
||||
{
|
||||
Id = 4,
|
||||
RoleId = 1,
|
||||
MenuKey = "UserManagement",
|
||||
MenuActions = "[ \"add\", \"edit\", \"delete\"]"
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
14
Cowain.Base/Models/AlarmChangedMessage .cs
Normal file
14
Cowain.Base/Models/AlarmChangedMessage .cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Cowain.Base.ViewModels;
|
||||
|
||||
namespace Plugin.Cowain.Base.Models;
|
||||
public class AlarmChangedMessage : ValueChangedMessage<List<AlarmViewModel>>
|
||||
{
|
||||
// 构造函数:传递变更类型+告警实例
|
||||
public AlarmChangedMessage(List<AlarmViewModel> alarm)
|
||||
: base(alarm)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
15
Cowain.Base/Models/AppSettings.cs
Normal file
15
Cowain.Base/Models/AppSettings.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局输出目录
|
||||
/// </summary>
|
||||
public string OutputPath { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 全局插件目录
|
||||
/// </summary>
|
||||
public string PluginPath { get; set; } = string.Empty;
|
||||
}
|
||||
17
Cowain.Base/Models/ApplicationException.cs
Normal file
17
Cowain.Base/Models/ApplicationException.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 应用异常基类
|
||||
/// </summary>
|
||||
public class ApplicationException : Exception
|
||||
{
|
||||
public ApplicationException() { }
|
||||
|
||||
public ApplicationException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public ApplicationException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
18
Cowain.Base/Models/BaseModel.cs
Normal file
18
Cowain.Base/Models/BaseModel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
public abstract class BaseModel
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 菜单按钮点击命令类型
|
||||
/// </summary>
|
||||
public enum DataBaseType
|
||||
{
|
||||
|
||||
MYSQL,
|
||||
SQLITE,
|
||||
SQLSERVER,
|
||||
POSTGRES
|
||||
}
|
||||
47
Cowain.Base/Models/Controls/ToastrItem.cs
Normal file
47
Cowain.Base/Models/Controls/ToastrItem.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Cowain.Base.Models.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 消息提示项
|
||||
/// </summary>
|
||||
public partial class ToastrItem : ObservableObject, IDisposable
|
||||
{
|
||||
[ObservableProperty]
|
||||
private bool _isVisible = true;
|
||||
/// <summary>
|
||||
/// 消息内容
|
||||
/// </summary>
|
||||
public string Message { get; }
|
||||
/// <summary>
|
||||
/// 消息类型
|
||||
/// </summary>
|
||||
public ToastrType ToastrType { get; }
|
||||
private DispatcherTimer? _timer;
|
||||
|
||||
public event EventHandler? RequestRemove;
|
||||
|
||||
public ToastrItem(string message, ToastrType toastrType = ToastrType.Danger, int duration = 3000)
|
||||
{
|
||||
IsVisible = true;
|
||||
Message = message;
|
||||
ToastrType = toastrType;
|
||||
|
||||
// 使用 DispatcherTimer 确保回调在主线程上执行
|
||||
_timer = new DispatcherTimer(TimeSpan.FromMilliseconds(duration), DispatcherPriority.Normal, (s, e) =>
|
||||
{
|
||||
IsVisible = false;
|
||||
RequestRemove?.Invoke(this, EventArgs.Empty);
|
||||
Dispose(); // 自动调用 Dispose 释放资源
|
||||
});
|
||||
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer?.Stop();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
9
Cowain.Base/Models/Controls/ToastrType.cs
Normal file
9
Cowain.Base/Models/Controls/ToastrType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace Cowain.Base.Models.Controls;
|
||||
|
||||
public enum ToastrType
|
||||
{
|
||||
None = 0,
|
||||
Success,
|
||||
Danger
|
||||
}
|
||||
17
Cowain.Base/Models/CurrentUser.cs
Normal file
17
Cowain.Base/Models/CurrentUser.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Models
|
||||
{
|
||||
public class CurrentUser
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string? UserName { get; set; }
|
||||
public string? UserRole { get; set; }
|
||||
public int UserRoleId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
33
Cowain.Base/Models/Menu/MenuClickCommandType.cs
Normal file
33
Cowain.Base/Models/Menu/MenuClickCommandType.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
namespace Cowain.Base.Models.Menu;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单按钮点击命令类型
|
||||
/// </summary>
|
||||
public enum MenuClickCommandType
|
||||
{
|
||||
/// <summary>
|
||||
/// 无效
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 激活菜单命令
|
||||
/// </summary>
|
||||
Active,
|
||||
/// <summary>
|
||||
/// 切换主题命令
|
||||
/// </summary>
|
||||
SwitchTheme,
|
||||
/// <summary>
|
||||
/// 超链接
|
||||
/// </summary>
|
||||
Link,
|
||||
/// <summary>
|
||||
/// 导航命令
|
||||
/// </summary>
|
||||
Navigate,
|
||||
/// <summary>
|
||||
/// 切换语言命令
|
||||
/// </summary>
|
||||
SwitchLanguage
|
||||
}
|
||||
15
Cowain.Base/Models/Menu/MenuConfigurationContext.cs
Normal file
15
Cowain.Base/Models/Menu/MenuConfigurationContext.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace Cowain.Base.Models.Menu;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单配置上下文对象
|
||||
/// </summary>
|
||||
public class MenuConfigurationContext
|
||||
{
|
||||
public List<MenuItem> Menus { get; set; }
|
||||
|
||||
public MenuConfigurationContext(List<MenuItem>? menuItems = null)
|
||||
{
|
||||
Menus = menuItems ?? [];
|
||||
}
|
||||
}
|
||||
45
Cowain.Base/Models/Menu/MenuItem.cs
Normal file
45
Cowain.Base/Models/Menu/MenuItem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
namespace Cowain.Base.Models.Menu;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单项实体
|
||||
/// </summary>
|
||||
public class MenuItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 唯一标识
|
||||
/// </summary>
|
||||
public string Key { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 图标 Path 值
|
||||
/// </summary>
|
||||
public string Icon { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 菜单是否激活状态
|
||||
/// </summary>
|
||||
public bool? IsActive { get; set; }
|
||||
/// <summary>
|
||||
/// 本地化键
|
||||
/// </summary>
|
||||
public string LocaleKey { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 子菜单
|
||||
/// </summary>
|
||||
public ICollection<MenuItem> Items { get; set; } = [];
|
||||
/// <summary>
|
||||
/// 菜单分组
|
||||
/// </summary>
|
||||
public string? Group { get; set; }
|
||||
/// <summary>
|
||||
/// 命令类型
|
||||
/// </summary>
|
||||
public string? CommandType { get; set; }
|
||||
/// <summary>
|
||||
/// 命令参数
|
||||
/// </summary>
|
||||
public string? CommandParameter { get; set; }
|
||||
/// <summary>
|
||||
/// 页面命令
|
||||
/// </summary>
|
||||
public string[]? PageActions { get; set; }
|
||||
}
|
||||
21
Cowain.Base/Models/Navigation/NavigationCommandContext.cs
Normal file
21
Cowain.Base/Models/Navigation/NavigationCommandContext.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
using Cowain.Base.Abstractions.Navigation;
|
||||
|
||||
namespace Cowain.Base.Models.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// 视图导航命令上下文对象
|
||||
/// </summary>
|
||||
public class NavigationCommandContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 视图导航器对象
|
||||
/// </summary>
|
||||
public IViewNavigator? Navigator { get; set; }
|
||||
|
||||
public NavigationCommandContext(IViewNavigator? navigator)
|
||||
{
|
||||
Navigator = navigator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Cowain.Base.Models.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// 导航命令不存在异常
|
||||
/// </summary>
|
||||
public class NavigationCommandNotFoundException : ApplicationException
|
||||
{
|
||||
public NavigationCommandNotFoundException() : base("Navigation command not found")
|
||||
{
|
||||
}
|
||||
|
||||
public NavigationCommandNotFoundException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public NavigationCommandNotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
11
Cowain.Base/Models/NotificationMessage.cs
Normal file
11
Cowain.Base/Models/NotificationMessage.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
public class NotificationMessage(NotificationModel model) : ValueChangedMessage<NotificationModel>(model);
|
||||
|
||||
33
Cowain.Base/Models/NotificationModel.cs
Normal file
33
Cowain.Base/Models/NotificationModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Avalonia.Controls.Notifications;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
public class NotificationModel
|
||||
{
|
||||
public NotificationModel(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public NotificationModel(NotificationType notificationType, string message)
|
||||
{
|
||||
NotificationType = notificationType;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public NotificationModel(NotificationType notificationType, string message, string classes)
|
||||
{
|
||||
NotificationType = notificationType;
|
||||
Message = message;
|
||||
Classes = classes;
|
||||
}
|
||||
|
||||
public NotificationType NotificationType { get; set; } = NotificationType.Information;
|
||||
public string Message { get; set; } = "This is message";
|
||||
public string Classes { get; set; } = "Light";
|
||||
}
|
||||
18
Cowain.Base/Models/Plugin/PluginMethodNotFoundException.cs
Normal file
18
Cowain.Base/Models/Plugin/PluginMethodNotFoundException.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace Cowain.Base.Models.Plugin;
|
||||
/// <summary>
|
||||
/// 插件方法未找到异常
|
||||
/// </summary>
|
||||
public class PluginMethodNotFoundException : ApplicationException
|
||||
{
|
||||
public PluginMethodNotFoundException() : base("插件方法不存在")
|
||||
{
|
||||
}
|
||||
|
||||
public PluginMethodNotFoundException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public PluginMethodNotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
}
|
||||
16
Cowain.Base/Models/Plugin/PluginNotFoundException.cs
Normal file
16
Cowain.Base/Models/Plugin/PluginNotFoundException.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Cowain.Base.Models.Plugin;
|
||||
/// <summary>
|
||||
/// 插件不存在异常
|
||||
/// </summary>
|
||||
public class PluginNotFoundException : ApplicationException
|
||||
{
|
||||
public PluginNotFoundException() : base("插件不存在")
|
||||
{
|
||||
}
|
||||
|
||||
public PluginNotFoundException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public PluginNotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
73
Cowain.Base/Models/ResultModel.cs
Normal file
73
Cowain.Base/Models/ResultModel.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
public class ResultModel<T> where T : notnull
|
||||
{
|
||||
private ResultModel(T data, string errorMessage, bool isSuccess, int code)
|
||||
{
|
||||
Data = data;
|
||||
ErrorMessage = errorMessage;
|
||||
IsSuccess = isSuccess;
|
||||
Code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// is request success
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; }
|
||||
/// <summary>
|
||||
/// status code
|
||||
/// </summary>
|
||||
public int Code { get; }
|
||||
/// <summary>
|
||||
/// error message
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; } = "";
|
||||
/// <summary>
|
||||
/// data
|
||||
/// </summary>
|
||||
public T Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// success
|
||||
/// </summary>
|
||||
public static ResultModel<T> Success(T data, string errMsg = "")
|
||||
{
|
||||
if (data is null)
|
||||
throw new ArgumentNullException(nameof(data), "Success时data不能为null");
|
||||
return new ResultModel<T>(data, errMsg, true, 200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// faild
|
||||
/// </summary>
|
||||
public static ResultModel<T> Error(string str, int code = 400)
|
||||
{
|
||||
// 错误时Data用默认值
|
||||
return new ResultModel<T>(default!, str, false, code);
|
||||
}
|
||||
}
|
||||
|
||||
public class ResultModel
|
||||
{
|
||||
/// <summary>
|
||||
/// is request success
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; set; }
|
||||
/// <summary>
|
||||
/// status code
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
/// <summary>
|
||||
/// error message
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; } = "";
|
||||
public static ResultModel Success(string errMsg = "")
|
||||
{
|
||||
return new ResultModel() { ErrorMessage = errMsg, IsSuccess = true, Code = 200 };
|
||||
}
|
||||
public static ResultModel Error(string str, int code = 400)
|
||||
{
|
||||
var ret = new ResultModel { ErrorMessage = str, IsSuccess = false, Code = code };
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
26
Cowain.Base/Models/SerilogDto.cs
Normal file
26
Cowain.Base/Models/SerilogDto.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
[Table("logs")]
|
||||
public class SerilogDto : BaseModel
|
||||
{
|
||||
[Key]
|
||||
public int id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Timestamp { get; set; } = string.Empty;
|
||||
[Required]
|
||||
public DateTime _ts { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Level { get; set; } = string.Empty;
|
||||
|
||||
public string? Template { get; set; } = string.Empty;
|
||||
|
||||
public string? Message { get; set; }
|
||||
public string? Exception { get; set; }
|
||||
|
||||
public string? Properties { get; set; }
|
||||
}
|
||||
14
Cowain.Base/Models/SexMode.cs
Normal file
14
Cowain.Base/Models/SexMode.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Models;
|
||||
|
||||
public enum SexMode
|
||||
{
|
||||
Other,
|
||||
Male,
|
||||
Female
|
||||
}
|
||||
17
Cowain.Base/Models/Task/InvalidTaskStatusException.cs
Normal file
17
Cowain.Base/Models/Task/InvalidTaskStatusException.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
namespace Cowain.Base.Models.Task;
|
||||
/// <summary>
|
||||
/// 错误的任务状态
|
||||
/// </summary>
|
||||
public class InvalidTaskStatusException : ApplicationException
|
||||
{
|
||||
public InvalidTaskStatusException() : base("错误的任务状态")
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidTaskStatusException(string message)
|
||||
: base(message) { }
|
||||
|
||||
public InvalidTaskStatusException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
22
Cowain.Base/Models/Task/TaskStatusEnum.cs
Normal file
22
Cowain.Base/Models/Task/TaskStatusEnum.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
namespace Cowain.Base.Models.Task;
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态枚举对象
|
||||
/// </summary>
|
||||
public enum TaskStatusEnum
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 准备
|
||||
/// </summary>
|
||||
Pending,
|
||||
/// <summary>
|
||||
/// 执行中
|
||||
/// </summary>
|
||||
Running,
|
||||
/// <summary>
|
||||
/// 完成
|
||||
/// </summary>
|
||||
Completed,
|
||||
}
|
||||
235
Cowain.Base/Services/AccountService.cs
Normal file
235
Cowain.Base/Services/AccountService.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.Helpers;
|
||||
using Cowain.Base.IServices;
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.Models.Admins;
|
||||
using Cowain.Base.ViewModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Cowain.Base.Services;
|
||||
|
||||
public class AccountService : BaseService, IAccountService
|
||||
{
|
||||
private readonly PageViewMenuHelper _pageMenuHelper;
|
||||
public AccountService(IDbContextFactory<SqlDbContext> dbContextFactory, PageViewMenuHelper pageMenuHelper) : base(dbContextFactory)
|
||||
{
|
||||
_pageMenuHelper = pageMenuHelper;
|
||||
}
|
||||
|
||||
public async Task<ResultModel<LoginUserViewModel>> LoginAsync(string userName, string password)
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userDbSet = dbContext.GetDbSet<UserDto>();
|
||||
var userRoleDbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
|
||||
var data = await (from us in userDbSet
|
||||
join ur in userRoleDbSet on us.RoleId equals ur.Id
|
||||
where us.Name == userName && us.Password == DESHelper.Encrypt(password, "ZSL12345")
|
||||
select new UserViewModel
|
||||
{
|
||||
Id = us.Id,
|
||||
RoleId = us.RoleId,
|
||||
IsValid = us.IsValid,
|
||||
Name = us.Name,
|
||||
Phone = us.Phone,
|
||||
Sex = us.Sex,
|
||||
UserNumber = us.UserNumber,
|
||||
RoleName = ur.RoleName
|
||||
}).FirstOrDefaultAsync();
|
||||
//登录成功,需要获取用户菜单
|
||||
if (data != null)
|
||||
{
|
||||
var jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
LoginUserViewModel loginUser = new LoginUserViewModel();
|
||||
loginUser.User = data;
|
||||
var userRoleMenus = dbContext.GetDbSet<UserRoleMenuDto>();
|
||||
var menus = userRoleMenus.Where(x => x.RoleId == data.RoleId);
|
||||
loginUser.Menus = new ObservableCollection<UserRoleMenuViewModel>(menus.Select(x => new UserRoleMenuViewModel
|
||||
{
|
||||
Id = x.Id,
|
||||
RoleId = x.RoleId,
|
||||
MenuKey = x.MenuKey,
|
||||
MenuName = _pageMenuHelper.GetMenuName(x.MenuKey),
|
||||
MenuActions = new ObservableCollection<string>(JsonSerializer.Deserialize<List<string>>(x.MenuActions ?? "[]", jsonSerializerOptions) ?? new List<string>())
|
||||
}));
|
||||
return ResultModel<LoginUserViewModel>.Success(loginUser);
|
||||
}
|
||||
return ResultModel<LoginUserViewModel>.Error("login err");
|
||||
}
|
||||
|
||||
public async Task<ResultModel> CheckHealthAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
try
|
||||
{
|
||||
await dbContext.EnsureCreatedAsync();
|
||||
var DbSet = dbContext.GetDbSet<UserDto>();
|
||||
var isHealthy = await DbSet.ToListAsync(cancellationToken);
|
||||
return ResultModel.Success("An healthy result.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ResultModel.Error("An unhealthy result.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<UserViewModel>> GetAllAsync()
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userDbSet = dbContext.GetDbSet<UserDto>();
|
||||
var userRoleDbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var data = await (from us in userDbSet
|
||||
join ur in userRoleDbSet on us.RoleId equals ur.Id
|
||||
select new UserViewModel
|
||||
{
|
||||
Id = us.Id,
|
||||
RoleId = us.RoleId,
|
||||
IsValid = us.IsValid,
|
||||
Name = us.Name,
|
||||
Password = DESHelper.Decrypt(us.Password, "ZSL12345"),
|
||||
Phone = us.Phone,
|
||||
Sex = us.Sex,
|
||||
UserNumber = us.UserNumber,
|
||||
RoleName = ur.RoleName
|
||||
}).ToListAsync();
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<(List<UserViewModel>, int totals)> GetAllAsync(int pageIndex, int pageSize)
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userDbSet = dbContext.GetDbSet<UserDto>();
|
||||
var userRoleDbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var data = (from us in userDbSet
|
||||
join ur in userRoleDbSet on us.RoleId equals ur.Id
|
||||
select new UserViewModel
|
||||
{
|
||||
Id = us.Id,
|
||||
RoleId = us.RoleId,
|
||||
IsValid = us.IsValid,
|
||||
Name = us.Name,
|
||||
Password = DESHelper.Decrypt(us.Password, "ZSL12345"),
|
||||
Phone = us.Phone,
|
||||
Sex = us.Sex,
|
||||
UserNumber = us.UserNumber,
|
||||
RoleName = ur.RoleName
|
||||
});
|
||||
var list = await data.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
return (list, data.Count());
|
||||
}
|
||||
|
||||
public async Task<ResultModel> AddUserAsync(UserViewModel? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return ResultModel.Error("User cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(user.Name))
|
||||
{
|
||||
return ResultModel.Error("UserName cannot be null");
|
||||
}
|
||||
if (user.RoleId == 0)
|
||||
{
|
||||
return ResultModel.Error("UserRole cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(user.UserNumber))
|
||||
{
|
||||
return ResultModel.Error("UserNumber cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var users = dbContext.GetDbSet<UserDto>();
|
||||
var existingUser = await users.FirstOrDefaultAsync(x => x.UserNumber == user.UserNumber);
|
||||
if (existingUser != null)
|
||||
{
|
||||
return ResultModel.Error("UserNumber already exists");
|
||||
}
|
||||
var entity = new UserDto
|
||||
{
|
||||
RoleId = user.RoleId,
|
||||
Name = user.Name,
|
||||
Phone = user.Phone ?? string.Empty,
|
||||
UserNumber = user.UserNumber,
|
||||
Sex = user.Sex ?? SexMode.Other.ToString(),
|
||||
Password = DESHelper.Encrypt(user.Password ?? "12345", "ZSL12345"),
|
||||
IsValid = user.IsValid
|
||||
};
|
||||
await users.AddAsync(entity);
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success("User added successfully");
|
||||
}
|
||||
|
||||
public async Task<ResultModel> EditUserAsync(UserViewModel? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return ResultModel.Error("User cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(user.Name))
|
||||
{
|
||||
return ResultModel.Error("UserName cannot be null");
|
||||
}
|
||||
if (user.RoleId == 0)
|
||||
{
|
||||
return ResultModel.Error("UserRole cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(user.UserNumber))
|
||||
{
|
||||
return ResultModel.Error("UserNumber cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var users = dbContext.GetDbSet<UserDto>();
|
||||
var existingUser = await users.FirstOrDefaultAsync(x => x.Id == user.Id);
|
||||
if (existingUser == null)
|
||||
{
|
||||
return ResultModel.Error("User-id not find");
|
||||
}
|
||||
var duplicateUser = await users.FirstOrDefaultAsync(x => x.UserNumber == user.UserNumber && x.Id != user.Id);
|
||||
if (duplicateUser != null)
|
||||
{
|
||||
return ResultModel.Error("UserNumber already exists");
|
||||
}
|
||||
existingUser.RoleId = user.RoleId;
|
||||
existingUser.Name = user.Name;
|
||||
existingUser.Phone = user.Phone ?? string.Empty;
|
||||
existingUser.UserNumber = user.UserNumber;
|
||||
existingUser.Sex = user.Sex ?? SexMode.Other.ToString();
|
||||
existingUser.Password = DESHelper.Encrypt(user.Password ?? "12345", "ZSL12345");
|
||||
existingUser.IsValid = user.IsValid;
|
||||
existingUser.UpdateTime = DateTime.Now;
|
||||
await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success("User edit successfully");
|
||||
}
|
||||
|
||||
public async Task<ResultModel> DelUserAsync(UserViewModel? user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
return ResultModel.Error("User cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(user.Name))
|
||||
{
|
||||
return ResultModel.Error("UserName cannot be null");
|
||||
}
|
||||
if (user.Id == 0)
|
||||
{
|
||||
return ResultModel.Error("User-id cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var users = dbContext.GetDbSet<UserDto>();
|
||||
var existingUser = await users.FirstOrDefaultAsync(x => x.Id == user.Id);
|
||||
if (existingUser == null)
|
||||
{
|
||||
return ResultModel.Error("User-id not find");
|
||||
}
|
||||
users.Remove(existingUser);
|
||||
await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success("User deleted successfully");
|
||||
}
|
||||
}
|
||||
492
Cowain.Base/Services/BaseService.cs
Normal file
492
Cowain.Base/Services/BaseService.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MySqlConnector;
|
||||
using System.Data;
|
||||
using System.Linq.Expressions;
|
||||
using Dapper;
|
||||
using Cowain.Base.IServices;
|
||||
|
||||
namespace Cowain.Base.Services;
|
||||
|
||||
public class BaseService : IBaseService
|
||||
{
|
||||
// 替换为 IDbContextFactory<SqlDbContext>
|
||||
protected readonly IDbContextFactory<SqlDbContext> _dbContextFactory;
|
||||
|
||||
public BaseService(IDbContextFactory<SqlDbContext> dbContextFactory)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID删除实体
|
||||
/// </summary>
|
||||
public int Delete<T>(int Id) where T : class
|
||||
{
|
||||
// 每次操作创建独立上下文,using 自动释放
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
T? t = dbContext.Set<T>().Find(Id);
|
||||
if (t == null) throw new KeyNotFoundException($"未找到类型 {typeof(T).Name},Id={Id}");
|
||||
dbContext.Set<T>().Remove(t);
|
||||
return dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID异步删除实体(支持取消)
|
||||
/// </summary>
|
||||
public async Task<int> DeleteAsync<T>(int Id, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
var t = await dbContext.Set<T>().FindAsync(Id, cancellationToken).ConfigureAwait(false);
|
||||
if (t == null) throw new KeyNotFoundException($"未找到类型 {typeof(T).Name},Id={Id}");
|
||||
dbContext.Set<T>().Remove(t);
|
||||
return await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除实体
|
||||
/// </summary>
|
||||
public int Delete<T>(T t) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(t);
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var entry = dbContext.Entry(t);
|
||||
if (entry.State == EntityState.Detached)
|
||||
{
|
||||
dbContext.Set<T>().Attach(t);
|
||||
}
|
||||
dbContext.Set<T>().Remove(t);
|
||||
return dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除实体(异步,支持取消)
|
||||
/// </summary>
|
||||
public async Task<int> DeleteAsync<T>(T t, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(t);
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
var entry = dbContext.Entry(t);
|
||||
if (entry.State == EntityState.Detached)
|
||||
{
|
||||
dbContext.Set<T>().Attach(t);
|
||||
}
|
||||
dbContext.Set<T>().Remove(t);
|
||||
return await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量读取实体
|
||||
/// </summary>
|
||||
public List<T> Find<T>() where T : class
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
return dbContext.Set<T>().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步、安全的批量读取(支持取消)
|
||||
/// </summary>
|
||||
public async Task<List<T>> FindAsync<T>(CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
return await dbContext.Set<T>().ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID查询实体
|
||||
/// </summary>
|
||||
public T? Find<T>(int id) where T : class
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
return dbContext.Set<T>().Find(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID异步查询实体(支持取消)
|
||||
/// </summary>
|
||||
public async Task<T?> FindAsync<T>(int id, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
return await dbContext.Set<T>().FindAsync(new object[] { id }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带表达式的异步查询(支持取消)
|
||||
/// </summary>
|
||||
public async Task<T?> FirstOrDefaultAsync<T>(Expression<Func<T, bool>> funcWhere, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
return await dbContext.Set<T>().Where(funcWhere).FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入实体
|
||||
/// </summary>
|
||||
public int Insert<T>(T t) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(t);
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
dbContext.Set<T>().Add(t);
|
||||
return dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步插入实体
|
||||
/// </summary>
|
||||
public async Task<int> InsertAsync<T>(T t, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(t);
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
dbContext.Set<T>().Add(t);
|
||||
return await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入实体
|
||||
/// </summary>
|
||||
public int Insert<T>(IEnumerable<T> tList) where T : class
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
dbContext.Set<T>().AddRange(tList);
|
||||
return dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量异步插入实体
|
||||
/// </summary>
|
||||
public async Task<int> InsertAsync<T>(IEnumerable<T> tList, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
dbContext.Set<T>().AddRange(tList);
|
||||
return await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带表达式的查询
|
||||
/// </summary>
|
||||
public List<T> Query<T>(Expression<Func<T, bool>>? funcWhere = null) where T : class
|
||||
{
|
||||
// 注意:Query 返回 IQueryable 时,上下文生命周期需由调用方管理,或改为立即执行(ToList/First 等)
|
||||
var dbContext = _dbContextFactory.CreateDbContext();
|
||||
IQueryable<T> query = dbContext.Set<T>();
|
||||
if (funcWhere is not null)
|
||||
{
|
||||
query = query.Where(funcWhere);
|
||||
}
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 带表达式的异步查询(支持取消、空条件查全部)
|
||||
/// </summary>
|
||||
public async Task<List<T>> QueryAsync<T>(
|
||||
Expression<Func<T, bool>>? funcWhere = null, // 设置默认值,调用更便捷
|
||||
CancellationToken cancellationToken = default)
|
||||
where T : class
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
IQueryable<T> query = dbContext.Set<T>();
|
||||
|
||||
if (funcWhere is not null)
|
||||
{
|
||||
query = query.Where(funcWhere);
|
||||
}
|
||||
|
||||
return await query.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基础版:返回实体列表+总条数(无投影)
|
||||
/// </summary>
|
||||
public async Task<(List<T> Data, int TotalCount)> QueryAsync<T, TKey>(
|
||||
Expression<Func<T, bool>>? funcWhere = null,
|
||||
Expression<Func<T, TKey>>? orderBy = null,
|
||||
bool isAscending = true,
|
||||
int pageIndex = 1,
|
||||
int pageSize = 20,
|
||||
CancellationToken cancellationToken = default)
|
||||
where T : class
|
||||
{
|
||||
pageIndex = pageIndex < 1 ? 1 : pageIndex;
|
||||
pageSize = pageSize < 0 ? 0 : pageSize;
|
||||
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
IQueryable<T> query = dbContext.Set<T>();
|
||||
|
||||
if (funcWhere is not null) query = query.Where(funcWhere);
|
||||
if (orderBy is not null) query = isAscending ? query.OrderBy(orderBy) : query.OrderByDescending(orderBy);
|
||||
|
||||
if (pageSize > 0)
|
||||
{
|
||||
long totalCountLong = await query.LongCountAsync(cancellationToken);
|
||||
int totalCount = (int)Math.Min(totalCountLong, int.MaxValue);
|
||||
var data = await query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync(cancellationToken);
|
||||
return (data, totalCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = await query.ToListAsync(cancellationToken);
|
||||
return (data, data.Count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增强版:支持投影(Select映射),直接返回ViewModel列表+总条数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">原实体类型</typeparam>
|
||||
/// <typeparam name="TKey">排序字段类型</typeparam>
|
||||
/// <typeparam name="TViewModel">目标ViewModel类型</typeparam>
|
||||
/// <param name="selectExpression">投影表达式(EF会解析为SQL的SELECT字段)</param>
|
||||
public async Task<(List<TViewModel> Data, int TotalCount)> QueryAsync<T, TKey, TViewModel>(
|
||||
Expression<Func<T, TViewModel>> selectExpression, // 新增:投影映射表达式
|
||||
Expression<Func<T, bool>>? funcWhere = null,
|
||||
Expression<Func<T, TKey>>? orderBy = null,
|
||||
bool isAscending = true,
|
||||
int pageIndex = 1,
|
||||
int pageSize = 20,
|
||||
CancellationToken cancellationToken = default)
|
||||
where T : class
|
||||
{
|
||||
pageIndex = pageIndex < 1 ? 1 : pageIndex;
|
||||
pageSize = pageSize < 0 ? 0 : pageSize;
|
||||
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
IQueryable<T> query = dbContext.Set<T>();
|
||||
|
||||
// 1. 过滤
|
||||
if (funcWhere is not null) query = query.Where(funcWhere);
|
||||
// 2. 排序
|
||||
if (orderBy is not null) query = isAscending ? query.OrderBy(orderBy) : query.OrderByDescending(orderBy);
|
||||
|
||||
// 3. 先查总条数(注意:总条数要在投影前查,避免投影影响计数)
|
||||
long totalCountLong = await query.LongCountAsync(cancellationToken);
|
||||
int totalCount = (int)Math.Min(totalCountLong, int.MaxValue);
|
||||
|
||||
// 4. 分页+投影(EF会转换为SQL:SELECT Id, Name, LayOutX, LayOutY FROM ... LIMIT ...)
|
||||
IQueryable<TViewModel> viewModelQuery = query
|
||||
.Skip((pageIndex - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(selectExpression); // 内置投影,替代手动Select
|
||||
|
||||
// 5. 转为List
|
||||
var data = await viewModelQuery.ToListAsync(cancellationToken);
|
||||
|
||||
return (data, totalCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体
|
||||
/// </summary>
|
||||
public int Update<T>(T t) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(t);
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var entry = dbContext.Entry(t);
|
||||
if (entry.State == EntityState.Detached)
|
||||
{
|
||||
dbContext.Set<T>().Attach(t);
|
||||
}
|
||||
dbContext.Entry(t).State = EntityState.Modified;
|
||||
return dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步更新实体(支持取消)
|
||||
/// </summary>
|
||||
public async Task<int> UpdateAsync<T>(T t, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(t);
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
var entry = dbContext.Entry(t);
|
||||
if (entry.State == EntityState.Detached)
|
||||
{
|
||||
dbContext.Set<T>().Attach(t);
|
||||
}
|
||||
dbContext.Entry(t).State = EntityState.Modified;
|
||||
return await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量更新实体
|
||||
/// </summary>
|
||||
public int Update<T>(IEnumerable<T> tList) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tList);
|
||||
var list = tList as IList<T> ?? tList.ToList();
|
||||
if (list.Count == 0) return 0;
|
||||
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
dbContext.Set<T>().UpdateRange(list);
|
||||
return dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步批量更新实体(支持取消)
|
||||
/// </summary>
|
||||
public async Task<int> UpdateAsync<T>(IEnumerable<T> tList, CancellationToken cancellationToken = default) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tList);
|
||||
var list = tList as IList<T> ?? tList.ToList();
|
||||
if (list.Count == 0) return 0;
|
||||
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
dbContext.Set<T>().UpdateRange(list);
|
||||
return await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用sql语句获取DataTable
|
||||
/// </summary>
|
||||
public DataTable GetDataTable(string sql, IEnumerable<MySqlParameter>? parameters = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
|
||||
// 从工厂创建上下文获取连接字符串
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
using var cmd = new MySqlCommand(sql, conn)
|
||||
{
|
||||
CommandType = CommandType.Text
|
||||
};
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var p in parameters) cmd.Parameters.Add(p);
|
||||
}
|
||||
using var adapter = new MySqlDataAdapter(cmd);
|
||||
var table = new DataTable();
|
||||
adapter.Fill(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步使用sql语句获取DataTable(支持取消)
|
||||
/// </summary>
|
||||
public async Task<DataTable> GetDataTableAsync(string sql, IEnumerable<MySqlParameter>? parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var cmd = new MySqlCommand(sql, conn) { CommandType = CommandType.Text };
|
||||
if (parameters != null) cmd.Parameters.AddRange(parameters.ToArray());
|
||||
|
||||
using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
var table = new DataTable();
|
||||
table.Load(reader);
|
||||
return table;
|
||||
}
|
||||
|
||||
// ================= Dapper 辅助方法 =================
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 查询并映射到 POCO(同步)
|
||||
/// </summary>
|
||||
public IEnumerable<T> QueryByDapper<T>(string sql, object? param = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
conn.Open();
|
||||
return conn.Query<T>(sql, param);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 查询并映射到 POCO(异步,支持 CancellationToken)
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<T>> QueryByDapperAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
var command = new CommandDefinition(sql, param, cancellationToken: cancellationToken);
|
||||
var result = await conn.QueryAsync<T>(command).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 查询单条记录(同步)
|
||||
/// </summary>
|
||||
public T? QueryFirstOrDefaultByDapper<T>(string sql, object? param = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
conn.Open();
|
||||
return conn.QueryFirstOrDefault<T>(sql, param);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 查询单条记录(异步)
|
||||
/// </summary>
|
||||
public async Task<T?> QueryFirstOrDefaultByDapperAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
var command = new CommandDefinition(sql, param, cancellationToken: cancellationToken);
|
||||
return await conn.QueryFirstOrDefaultAsync<T>(command).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 执行非查询 SQL(同步),返回受影响行数
|
||||
/// </summary>
|
||||
public int ExecuteByDapper(string sql, object? param = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
conn.Open();
|
||||
return conn.Execute(sql, param);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 执行非查询 SQL(异步),返回受影响行数
|
||||
/// </summary>
|
||||
public async Task<int> ExecuteByDapperAsync(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
var command = new CommandDefinition(sql, param, cancellationToken: cancellationToken);
|
||||
return await conn.ExecuteAsync(command).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 执行 SQL 并返回 DataTable(异步,支持 CancellationToken)
|
||||
/// </summary>
|
||||
public async Task<DataTable> QueryToDataTableByDapperAsync(string sql, object? param = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
var command = new CommandDefinition(sql, param, cancellationToken: cancellationToken, flags: CommandFlags.Buffered);
|
||||
await using var reader = await conn.ExecuteReaderAsync(command).ConfigureAwait(false);
|
||||
var table = new DataTable();
|
||||
table.Load(reader);
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Dapper 执行 SQL 并返回 DataTable(同步)
|
||||
/// </summary>
|
||||
public DataTable QueryToDataTableByDapper(string sql, object? param = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new ArgumentException("sql 不能为空", nameof(sql));
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
using var conn = new MySqlConnection(dbContext.Database.GetConnectionString());
|
||||
conn.Open();
|
||||
using var reader = conn.ExecuteReader(sql, param);
|
||||
var table = new DataTable();
|
||||
table.Load(reader);
|
||||
return table;
|
||||
}
|
||||
}
|
||||
57
Cowain.Base/Services/LogService.cs
Normal file
57
Cowain.Base/Services/LogService.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.IServices;
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.ViewModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Cowain.Base.Services;
|
||||
|
||||
public class LogService : BaseService, ILogService
|
||||
{
|
||||
public LogService(IDbContextFactory<SqlDbContext> dbContextFactory) : base(dbContextFactory)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<List<SerilogViewModel>> GetAllAsync()
|
||||
{
|
||||
var data = await FindAsync<SerilogDto>();
|
||||
return new List<SerilogViewModel>(data.Select(x => new SerilogViewModel
|
||||
{
|
||||
Id = x.id,
|
||||
Template = x.Template,
|
||||
Message = x.Message,
|
||||
Exception = x.Exception,
|
||||
Properties = x.Properties,
|
||||
Level = x.Level,
|
||||
Timestamp = x.Timestamp
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页获取日志(复用BaseService的FindAsync+内存分页)
|
||||
/// </summary>
|
||||
public async Task<(List<SerilogViewModel>, int totals)> GetAllAsync(int pageIndex, int pageSize)
|
||||
{
|
||||
// 复用BaseService的异步查询方法
|
||||
var allData = await FindAsync<SerilogDto>();
|
||||
|
||||
// 分页处理
|
||||
var paginatedData = allData
|
||||
.OrderByDescending(x => x.id)
|
||||
.Skip((pageIndex - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(x => new SerilogViewModel
|
||||
{
|
||||
Id = x.id,
|
||||
Template = x.Template,
|
||||
Message = x.Message,
|
||||
Exception = x.Exception,
|
||||
Properties = x.Properties,
|
||||
Level = x.Level,
|
||||
Timestamp = x.Timestamp
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return (paginatedData, allData.Count);
|
||||
}
|
||||
}
|
||||
152
Cowain.Base/Services/UserRoleMenuService.cs
Normal file
152
Cowain.Base/Services/UserRoleMenuService.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using Avalonia;
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.Helpers;
|
||||
using Cowain.Base.IServices;
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.Models.Admins;
|
||||
using Cowain.Base.Models.Menu;
|
||||
using Cowain.Base.ViewModels;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
using MenuItem = Cowain.Base.Models.Menu.MenuItem;
|
||||
|
||||
namespace Cowain.Base.Services;
|
||||
|
||||
public class UserRoleMenuService : BaseService, IUserRoleMenuService
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单数据
|
||||
/// </summary>
|
||||
private readonly List<MenuItem> _menuItems;
|
||||
private readonly PageViewMenuHelper _pageMenuHelper;
|
||||
public UserRoleMenuService(IDbContextFactory<SqlDbContext> dbContextFactory, PageViewMenuHelper pageMenuHelper, MenuConfigurationContext menuContext) : base(dbContextFactory)
|
||||
{
|
||||
_menuItems = menuContext.Menus;
|
||||
_pageMenuHelper = pageMenuHelper;
|
||||
}
|
||||
|
||||
public async Task<List<UserRoleViewModel>> GetAllAsync()
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userRoles = dbContext.GetDbSet<UserRoleDto>();
|
||||
var userRoleMenus = dbContext.GetDbSet<UserRoleMenuDto>();
|
||||
var jsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
var result = await (from ur in userRoles
|
||||
join urm in userRoleMenus on ur.Id equals urm.RoleId into urms
|
||||
select new UserRoleViewModel
|
||||
{
|
||||
RoleId = ur.Id,
|
||||
RoleName = ur.RoleName,
|
||||
IsValid = ur.IsValid,
|
||||
Menus = new ObservableCollection<UserRoleMenuViewModel>(urms.Select(urm => new UserRoleMenuViewModel
|
||||
{
|
||||
Id = urm.Id,
|
||||
RoleId = urm.RoleId,
|
||||
MenuKey = urm.MenuKey,
|
||||
MenuName = _pageMenuHelper.GetMenuName(urm.MenuKey),
|
||||
MenuActions = new ObservableCollection<string>(JsonSerializer.Deserialize<List<string>>(urm.MenuActions ?? "[]", jsonSerializerOptions) ?? new List<string>())
|
||||
}).ToList())
|
||||
}).ToListAsync();
|
||||
|
||||
return result ?? new List<UserRoleViewModel>();
|
||||
}
|
||||
|
||||
public async Task<ResultModel> EditUserRoleMenuAsync(UserRoleViewModel? userRole)
|
||||
{
|
||||
if (userRole == null)
|
||||
{
|
||||
return ResultModel.Error("UserRole cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(userRole.RoleName))
|
||||
{
|
||||
return ResultModel.Error("UserRoleName cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userRoleMenus = dbContext.GetDbSet<UserRoleMenuDto>();
|
||||
|
||||
// 删除旧的菜单
|
||||
var oldMenus = userRoleMenus.Where(urm => urm.RoleId == userRole.RoleId);
|
||||
dbContext.RemoveRange(oldMenus);
|
||||
|
||||
// 添加新的菜单
|
||||
var newMenus = userRole.Menus?.Select(menu => new UserRoleMenuDto
|
||||
{
|
||||
RoleId = userRole.RoleId,
|
||||
MenuKey = menu.MenuKey,
|
||||
MenuActions = JsonSerializer.Serialize(menu.MenuActions)
|
||||
}).ToList();
|
||||
|
||||
if (newMenus != null)
|
||||
{
|
||||
await userRoleMenus.AddRangeAsync(newMenus);
|
||||
var ret = await dbContext.SaveChangesAsync();
|
||||
// 添加完新的数据后,id需要告诉userRole的Menus
|
||||
var addedMenus = await userRoleMenus.Where(urm => urm.RoleId == userRole.RoleId).ToListAsync();
|
||||
if (userRole.Menus != null)
|
||||
{
|
||||
for (int i = 0; i < userRole.Menus.Count; i++)
|
||||
{
|
||||
userRole.Menus[i].Id = addedMenus[i].Id;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultModel.Success($"UserRoleMenu updated successfully,count is {ret}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var ret = await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success($"UserRoleMenu updated successfully,count is {ret}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<ResultModel> EditMenuActionsAsync(UserRoleMenuViewModel? userRoleMenu)
|
||||
{
|
||||
if (userRoleMenu == null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleMenu cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(userRoleMenu.MenuKey))
|
||||
{
|
||||
return ResultModel.Error("MenuKey cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userRoleMenus = dbContext.GetDbSet<UserRoleMenuDto>();
|
||||
var menu = await userRoleMenus.FirstOrDefaultAsync(urm => urm.Id == userRoleMenu.Id);
|
||||
if (menu == null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleMenu Actions not found");
|
||||
}
|
||||
menu.MenuActions = JsonSerializer.Serialize(userRoleMenu.MenuActions);
|
||||
dbContext.Update(menu);
|
||||
var ret = await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success($"UserRoleMenu Actions updated successfully,count is {ret}");
|
||||
}
|
||||
|
||||
public async Task<ResultModel> DeleteMenuActionsAsync(UserRoleMenuViewModel? userRoleMenu)
|
||||
{
|
||||
if (userRoleMenu == null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleMenu cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(userRoleMenu.MenuKey))
|
||||
{
|
||||
return ResultModel.Error("MenuKey cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var userRoleMenus = dbContext.GetDbSet<UserRoleMenuDto>();
|
||||
var menu = await userRoleMenus.FirstOrDefaultAsync(urm => urm.Id == userRoleMenu.Id);
|
||||
if (menu == null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleMenu Actions not found");
|
||||
}
|
||||
var ret = await DeleteAsync<UserRoleMenuDto>(menu);
|
||||
return ResultModel.Success($"UserRoleMenu Actions remove successfully,count is {ret}");
|
||||
}
|
||||
}
|
||||
133
Cowain.Base/Services/UserRoleService.cs
Normal file
133
Cowain.Base/Services/UserRoleService.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Cowain.Base.DBContext;
|
||||
using Cowain.Base.IServices;
|
||||
using Cowain.Base.Models;
|
||||
using Cowain.Base.Models.Admins;
|
||||
using Cowain.Base.ViewModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.Services;
|
||||
|
||||
public class UserRoleService : BaseService, IUserRoleService
|
||||
{
|
||||
public UserRoleService(IDbContextFactory<SqlDbContext> dbContextFactory) : base(dbContextFactory)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<ResultModel> AddUserRoleAsync(UserRoleViewModel? userRole)
|
||||
{
|
||||
if (userRole == null)
|
||||
{
|
||||
return ResultModel.Error("UserRole cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(userRole.RoleName))
|
||||
{
|
||||
return ResultModel.Error("UserRoleName cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var DbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var existingRole = await DbSet.FirstOrDefaultAsync(x => x.RoleName == userRole.RoleName);
|
||||
if (existingRole != null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleName already exists");
|
||||
}
|
||||
var entity = new UserRoleDto
|
||||
{
|
||||
Id = userRole.RoleId,
|
||||
RoleName = userRole.RoleName,
|
||||
IsValid = userRole.IsValid
|
||||
};
|
||||
await DbSet.AddAsync(entity);
|
||||
await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success("UserRole added successfully");
|
||||
}
|
||||
|
||||
public async Task<ResultModel> EditUserRoleAsync(UserRoleViewModel? userRole)
|
||||
{
|
||||
if (userRole == null)
|
||||
{
|
||||
return ResultModel.Error("UserRole cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(userRole.RoleName))
|
||||
{
|
||||
return ResultModel.Error("UserRoleName cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var DbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var existingRole = await DbSet.FirstOrDefaultAsync(x => x.Id == userRole.RoleId);
|
||||
if (existingRole == null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleName no exists");
|
||||
}
|
||||
var duplicateRole = await DbSet.FirstOrDefaultAsync(x => x.RoleName == userRole.RoleName && x.Id != userRole.RoleId);
|
||||
if (duplicateRole != null)
|
||||
{
|
||||
return ResultModel.Error("UserRoleName already exists");
|
||||
}
|
||||
existingRole.IsValid = userRole.IsValid;
|
||||
existingRole.RoleName = userRole.RoleName;
|
||||
existingRole.UpdateTime = DateTime.Now;
|
||||
await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success("UserRole updated successfully");
|
||||
}
|
||||
|
||||
|
||||
public async Task<ResultModel> DelUserRoleAsync(UserRoleViewModel? userRole)
|
||||
{
|
||||
if (userRole == null)
|
||||
{
|
||||
return ResultModel.Error("UserRole cannot be null");
|
||||
}
|
||||
if (string.IsNullOrEmpty(userRole.RoleName))
|
||||
{
|
||||
return ResultModel.Error("UserRoleName cannot be null");
|
||||
}
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var DbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var entity = await DbSet.FirstOrDefaultAsync(x => x.Id == userRole.RoleId);
|
||||
if (entity == null)
|
||||
{
|
||||
return ResultModel.Error("UserRole not found");
|
||||
}
|
||||
// 删除UserRole还要将UserRoleMenu表中RoleId相同的项也同时删除
|
||||
var userRoleMenuDbSet = dbContext.GetDbSet<UserRoleMenuDto>();
|
||||
var userRoleMenus = await userRoleMenuDbSet.Where(x => x.RoleId == userRole.RoleId).ToListAsync();
|
||||
userRoleMenuDbSet.RemoveRange(userRoleMenus);
|
||||
DbSet.Remove(entity);
|
||||
await dbContext.SaveChangesAsync();
|
||||
return ResultModel.Success("UserRole and related UserRoleMenus deleted successfully");
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<UserRoleViewModel>> GetAllAsync()
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var DbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var data = await DbSet.ToListAsync();
|
||||
return new List<UserRoleViewModel>(data.Select(x => new UserRoleViewModel
|
||||
{
|
||||
RoleId = x.Id,
|
||||
RoleName = x.RoleName,
|
||||
IsValid = x.IsValid
|
||||
}));
|
||||
}
|
||||
|
||||
public async Task<(List<UserRoleViewModel>, int totals)> GetAllAsync(int pageIndex, int pageSize)
|
||||
{
|
||||
using var dbContext = _dbContextFactory.CreateDbContext();
|
||||
var DbSet = dbContext.GetDbSet<UserRoleDto>();
|
||||
var data = await DbSet.ToListAsync();
|
||||
var list = data.Skip((pageIndex - 1) * pageSize).Take(pageSize);
|
||||
return (new List<UserRoleViewModel>(list.Select(x => new UserRoleViewModel
|
||||
{
|
||||
RoleId = x.Id,
|
||||
RoleName = x.RoleName,
|
||||
IsValid = x.IsValid
|
||||
})), data.Count());
|
||||
}
|
||||
}
|
||||
3
Cowain.Base/UPGRADING.md
Normal file
3
Cowain.Base/UPGRADING.md
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
## 首次发布 1.0.0
|
||||
* 第一版
|
||||
217
Cowain.Base/UndoRedo/ActionsHistory.cs
Normal file
217
Cowain.Base/UndoRedo/ActionsHistory.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.UndoRedo;
|
||||
|
||||
public interface IActionsHistory : INotifyPropertyChanged
|
||||
{
|
||||
int MaxSize { get; set; }
|
||||
bool CanUndo { get; }
|
||||
bool CanRedo { get; }
|
||||
bool IsEnabled { get; set; }
|
||||
IAction? Current { get; }
|
||||
|
||||
void Undo();
|
||||
void Redo();
|
||||
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// All future modifications will be merged together to create a single history item until batch is disposed.
|
||||
/// </summary>
|
||||
IDisposable Batch(string? label = default);
|
||||
|
||||
void Record(IAction action);
|
||||
|
||||
/// <summary>
|
||||
/// All future modifications will be merged together to create a single history item until history is resumed.
|
||||
/// </summary>
|
||||
void Pause(string? label = default);
|
||||
|
||||
/// <summary>Each future modifications will create a new history item.</summary>
|
||||
void Resume();
|
||||
}
|
||||
|
||||
public interface IAction
|
||||
{
|
||||
string? Label { get; }
|
||||
|
||||
void Execute();
|
||||
void Undo();
|
||||
}
|
||||
|
||||
public static class ActionsHistoryExtensions
|
||||
{
|
||||
public static void Record(this IActionsHistory history, Action execute, Action unexecute, string? label = default)
|
||||
=> history.Record(new DelegateAction(execute, unexecute, label));
|
||||
|
||||
public static void ExecuteAction(this IActionsHistory history, IAction action)
|
||||
{
|
||||
history.Record(action);
|
||||
action.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
public class ActionsHistory : IActionsHistory
|
||||
{
|
||||
private readonly List<IAction> _history = new List<IAction>();
|
||||
private readonly List<IAction> _batchHistory = new List<IAction>();
|
||||
private int _position = -1;
|
||||
private bool _isApplyingOperation = false;
|
||||
private string? _batchLabel;
|
||||
private int _batchDepth;
|
||||
|
||||
private static readonly PropertyChangedEventArgs _canRedoArgs = new PropertyChangedEventArgs(nameof(CanRedo));
|
||||
private static readonly PropertyChangedEventArgs _canUndoArgs = new PropertyChangedEventArgs(nameof(CanUndo));
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
public static readonly ActionsHistory Global = new ActionsHistory();
|
||||
|
||||
public bool IsBatching { get; private set; }
|
||||
|
||||
public int MaxSize { get; set; } = 50;
|
||||
|
||||
public bool CanRedo => _history.Count > 0 && _position < _history.Count - 1;
|
||||
|
||||
public bool CanUndo => _position > -1;
|
||||
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
public IAction? Current => CanUndo ? _history[_position] : null;
|
||||
|
||||
public IDisposable Batch(string? label = default)
|
||||
=> new BatchOperation(label, this);
|
||||
|
||||
public void Record(IAction op)
|
||||
{
|
||||
// Prevent recording the undo or redo operation
|
||||
if (_isApplyingOperation || !IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsBatching)
|
||||
{
|
||||
_batchHistory.Add(op);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToUndoStack(op);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToUndoStack(IAction op)
|
||||
{
|
||||
if (_position < _history.Count - 1)
|
||||
{
|
||||
_history.RemoveRange(_position + 1, _history.Count - _position - 1);
|
||||
}
|
||||
|
||||
if (_history.Count >= MaxSize)
|
||||
{
|
||||
_history.RemoveAt(0);
|
||||
_position--;
|
||||
}
|
||||
|
||||
_history.Add(op);
|
||||
_position++;
|
||||
|
||||
PropertyChanged?.Invoke(this, _canRedoArgs);
|
||||
PropertyChanged?.Invoke(this, _canUndoArgs);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
if (IsBatching)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(Undo)} is not allowed during a batch.");
|
||||
}
|
||||
|
||||
if (CanUndo)
|
||||
{
|
||||
var op = _history[_position];
|
||||
_isApplyingOperation = true;
|
||||
op.Undo();
|
||||
_isApplyingOperation = false;
|
||||
_position--;
|
||||
}
|
||||
}
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
if (IsBatching)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(Redo)} is not allowed during a batch.");
|
||||
}
|
||||
|
||||
if (CanRedo)
|
||||
{
|
||||
_position++;
|
||||
var op = _history[_position];
|
||||
_isApplyingOperation = true;
|
||||
op.Execute();
|
||||
_isApplyingOperation = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_history.Clear();
|
||||
_batchHistory.Clear();
|
||||
}
|
||||
|
||||
public void Pause(string? label = default)
|
||||
{
|
||||
if (_batchDepth > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_batchLabel = label;
|
||||
IsBatching = true;
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (_batchDepth > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_batchHistory.Count > 0)
|
||||
{
|
||||
AddToUndoStack(new BatchAction(_batchLabel, _batchHistory));
|
||||
_batchHistory.Clear();
|
||||
}
|
||||
|
||||
_batchLabel = null;
|
||||
IsBatching = false;
|
||||
}
|
||||
|
||||
private class BatchOperation : IDisposable
|
||||
{
|
||||
private readonly ActionsHistory _history;
|
||||
private bool _disposed;
|
||||
|
||||
public BatchOperation(string? label, ActionsHistory history)
|
||||
{
|
||||
_history = history;
|
||||
_history.Pause(label);
|
||||
_history._batchDepth++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
_history._batchDepth--;
|
||||
_history.Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Cowain.Base/UndoRedo/BatchAction.cs
Normal file
33
Cowain.Base/UndoRedo/BatchAction.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Cowain.Base.UndoRedo;
|
||||
|
||||
public class BatchAction : IAction
|
||||
{
|
||||
public BatchAction(string? label, IEnumerable<IAction> history)
|
||||
{
|
||||
History = history.Reverse().ToList();
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IAction> History { get; }
|
||||
|
||||
public string? Label { get; }
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
for (int i = History.Count - 1; i >= 0; i--)
|
||||
{
|
||||
History[i].Execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
for (int i = 0; i < History.Count; i++)
|
||||
{
|
||||
History[i].Undo();
|
||||
}
|
||||
}
|
||||
|
||||
public override string? ToString()
|
||||
=> Label;
|
||||
}
|
||||
22
Cowain.Base/UndoRedo/DelegateAction.cs
Normal file
22
Cowain.Base/UndoRedo/DelegateAction.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Cowain.Base.UndoRedo;
|
||||
|
||||
public class DelegateAction : IAction
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private readonly Action _undo;
|
||||
|
||||
public string? Label { get; }
|
||||
|
||||
public DelegateAction(Action apply, Action unapply, string? label)
|
||||
{
|
||||
_execute = apply;
|
||||
_undo = unapply;
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public void Execute() => _execute();
|
||||
public void Undo() => _undo();
|
||||
|
||||
public override string? ToString()
|
||||
=> Label;
|
||||
}
|
||||
86
Cowain.Base/UndoRedo/PropertyCache.cs
Normal file
86
Cowain.Base/UndoRedo/PropertyCache.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.UndoRedo;
|
||||
|
||||
public interface IPropertyAccessor
|
||||
{
|
||||
object? GetValue(object instance);
|
||||
void SetValue(object instance, object? value);
|
||||
bool CanRead { get; }
|
||||
bool CanWrite { get; }
|
||||
}
|
||||
|
||||
public sealed class PropertyAccessor<TInstanceType, TPropertyType> : IPropertyAccessor where TInstanceType : class
|
||||
{
|
||||
private readonly Func<TInstanceType, TPropertyType> _getter;
|
||||
private readonly Action<TInstanceType, TPropertyType> _setter;
|
||||
|
||||
public bool CanRead { get; }
|
||||
public bool CanWrite { get; }
|
||||
|
||||
public PropertyAccessor(Func<TInstanceType, TPropertyType> getter, Action<TInstanceType, TPropertyType> setter)
|
||||
{
|
||||
_getter = getter;
|
||||
_setter = setter;
|
||||
|
||||
CanRead = getter != null;
|
||||
CanWrite = setter != null;
|
||||
}
|
||||
|
||||
public object? GetValue(object instance)
|
||||
=> _getter((TInstanceType)instance);
|
||||
|
||||
public void SetValue(object instance, object? value)
|
||||
=> _setter((TInstanceType)instance, (TPropertyType)value!);
|
||||
}
|
||||
|
||||
public class PropertyCache
|
||||
{
|
||||
private static readonly Dictionary<string, IPropertyAccessor> _properties = new Dictionary<string, IPropertyAccessor>();
|
||||
|
||||
public static IPropertyAccessor Get(Type type, string name)
|
||||
{
|
||||
string propKey = $"{type.FullName}.{name}";
|
||||
if (!_properties.TryGetValue(propKey, out var result))
|
||||
{
|
||||
var prop = type.GetProperty(name);
|
||||
result = Create(type, prop!);
|
||||
|
||||
_properties.Add(propKey, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IPropertyAccessor Get<T>(string name)
|
||||
=> Get(typeof(T), name);
|
||||
|
||||
private static IPropertyAccessor Create(Type type, PropertyInfo property)
|
||||
{
|
||||
Delegate? getterInvocation = default;
|
||||
Delegate? setterInvocation = default;
|
||||
|
||||
if (property.CanRead)
|
||||
{
|
||||
MethodInfo getMethod = property.GetGetMethod(true)!;
|
||||
Type getterType = typeof(Func<,>).MakeGenericType(type, property.PropertyType);
|
||||
getterInvocation = Delegate.CreateDelegate(getterType, getMethod);
|
||||
}
|
||||
|
||||
if (property.CanWrite)
|
||||
{
|
||||
MethodInfo setMethod = property.GetSetMethod(true)!;
|
||||
Type setterType = typeof(Action<,>).MakeGenericType(type, property.PropertyType);
|
||||
setterInvocation = Delegate.CreateDelegate(setterType, setMethod);
|
||||
}
|
||||
|
||||
Type adapterType = typeof(PropertyAccessor<,>).MakeGenericType(type, property.PropertyType);
|
||||
|
||||
return (IPropertyAccessor)Activator.CreateInstance(adapterType, getterInvocation, setterInvocation)!;
|
||||
}
|
||||
}
|
||||
86
Cowain.Base/UndoRedo/Undoable.cs
Normal file
86
Cowain.Base/UndoRedo/Undoable.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.UndoRedo;
|
||||
|
||||
[Flags]
|
||||
public enum PropertyFlags
|
||||
{
|
||||
Disable = 0,
|
||||
Enable = 1
|
||||
}
|
||||
|
||||
|
||||
public abstract class Undoable : ObservableObject
|
||||
{
|
||||
private readonly HashSet<string> _trackedProperties = new HashSet<string>();
|
||||
|
||||
public IActionsHistory History { get; }
|
||||
|
||||
private void RecordHistory<TPropType>(string propName, TPropType previous, TPropType current)
|
||||
{
|
||||
if (_trackedProperties.Contains(propName))
|
||||
{
|
||||
var prop = PropertyCache.Get(GetType(), propName);
|
||||
History.Record(() => prop.SetValue(this, current), () => prop.SetValue(this, previous), propName);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RecordProperty(string propName, PropertyFlags flags = PropertyFlags.Enable)
|
||||
{
|
||||
if (flags == PropertyFlags.Disable)
|
||||
{
|
||||
_trackedProperties.Remove(propName);
|
||||
}
|
||||
else if (flags.HasFlag(PropertyFlags.Enable))
|
||||
{
|
||||
_trackedProperties.Add(propName);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RecordProperty<TType>(Expression<Func<TType, object?>> selector, PropertyFlags flags = PropertyFlags.Enable)
|
||||
{
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return;
|
||||
|
||||
string name = GetPropertyName(selector);
|
||||
RecordProperty(name, flags);
|
||||
}
|
||||
|
||||
private static string GetPropertyName(Expression memberAccess)
|
||||
=> memberAccess switch
|
||||
{
|
||||
LambdaExpression lambda => GetPropertyName(lambda.Body),
|
||||
MemberExpression mbr => mbr.Member.Name,
|
||||
UnaryExpression unary => GetPropertyName(unary.Operand),
|
||||
_ => throw new Exception($"Member name could not be extracted from {memberAccess}.")
|
||||
};
|
||||
|
||||
//protected override bool SetProperty<TPropType>(ref TPropType field, TPropType value, [CallerMemberName] string propertyName = "")
|
||||
//{
|
||||
// TPropType prev = field;
|
||||
// if (base.SetProperty(ref field, value, propertyName))
|
||||
// {
|
||||
// RecordHistory(propertyName, prev, value);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// return false;
|
||||
//}
|
||||
|
||||
public Undoable()
|
||||
{
|
||||
History = ActionsHistory.Global;
|
||||
}
|
||||
|
||||
public Undoable(IActionsHistory history)
|
||||
{
|
||||
History = history;
|
||||
}
|
||||
}
|
||||
38
Cowain.Base/ViewModels/AlarmViewModel.cs
Normal file
38
Cowain.Base/ViewModels/AlarmViewModel.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Cowain.Base.ViewModels;
|
||||
|
||||
public partial class AlarmViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private int _id;
|
||||
[ObservableProperty]
|
||||
private int _tagId;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _status;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _desc = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _group;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _level;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _groupName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _levelName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _color = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private DateTime _startTime = DateTime.MinValue;
|
||||
|
||||
[ObservableProperty]
|
||||
private DateTime? _stopTime;
|
||||
}
|
||||
20
Cowain.Base/ViewModels/LoginUserViewModel.cs
Normal file
20
Cowain.Base/ViewModels/LoginUserViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.ViewModels;
|
||||
|
||||
public partial class LoginUserViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private UserViewModel? _user;
|
||||
/// <summary>
|
||||
/// 菜单列表
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<UserRoleMenuViewModel>? _menus;
|
||||
}
|
||||
56
Cowain.Base/ViewModels/MenuItemViewModel.cs
Normal file
56
Cowain.Base/ViewModels/MenuItemViewModel.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Cowain.Base.ViewModels;
|
||||
|
||||
public partial class MenuItemViewModel : ObservableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 唯一标识
|
||||
/// </summary>
|
||||
public string Key { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 按钮文本
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public string _text;
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public StreamGeometry? Icon { get; set; }
|
||||
/// <summary>
|
||||
/// 子菜单
|
||||
/// </summary>
|
||||
public ICollection<MenuItemViewModel>? Items { get; set; }
|
||||
/// <summary>
|
||||
/// 菜单是否选中
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private bool _isActive;
|
||||
/// <summary>
|
||||
/// 菜单点击命令
|
||||
/// </summary>
|
||||
public ICommand? MenuClickCommand { get; set; }
|
||||
/// <summary>
|
||||
/// 命令参数
|
||||
/// </summary>
|
||||
public string? CommandParameter { get; set; }
|
||||
/// <summary>
|
||||
/// 页面命令
|
||||
/// </summary>
|
||||
public string[]? PageActions { get; set; }
|
||||
|
||||
public MenuItemViewModel(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public void AddItem(MenuItemViewModel item)
|
||||
{
|
||||
Items ??= [];
|
||||
Items?.Add(item);
|
||||
}
|
||||
|
||||
}
|
||||
44
Cowain.Base/ViewModels/PageViewModelBase.cs
Normal file
44
Cowain.Base/ViewModels/PageViewModelBase.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Cowain.Base.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 视图页面模型基类
|
||||
/// </summary>
|
||||
public partial class PageViewModelBase : ObservableObject, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 页面标题
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
public bool Disposed => _disposed;
|
||||
private bool _disposed;
|
||||
|
||||
// 公开的 Dispose 方法(IDisposable 接口要求)
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// 虚方法:允许子类重写,清理自身资源
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 基类自身的托管资源清理(如命令、定时器等)
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 析构函数(非托管资源兜底)
|
||||
~PageViewModelBase()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
27
Cowain.Base/ViewModels/SerilogViewModel.cs
Normal file
27
Cowain.Base/ViewModels/SerilogViewModel.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.ViewModels;
|
||||
|
||||
public partial class SerilogViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private int _id;
|
||||
[ObservableProperty]
|
||||
private string? _template;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _message;
|
||||
[ObservableProperty]
|
||||
private string? _exception;
|
||||
[ObservableProperty]
|
||||
private string? _properties;
|
||||
[ObservableProperty]
|
||||
private string? _level;
|
||||
[ObservableProperty]
|
||||
private string? _timestamp;
|
||||
}
|
||||
65
Cowain.Base/ViewModels/TaskPageViewModelBase.cs
Normal file
65
Cowain.Base/ViewModels/TaskPageViewModelBase.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Cowain.Base.Models.Task;
|
||||
using Ke.Bee.Localization.Localizer.Abstractions;
|
||||
|
||||
namespace Cowain.Base.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 任务类页面视图模型基类
|
||||
/// </summary>
|
||||
public partial class TaskPageViewModelBase : PageViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化资源
|
||||
/// </summary>
|
||||
protected ILocalizer L { get; }
|
||||
/// <summary>
|
||||
/// 任务状态文本
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string? _taskStatusText;
|
||||
|
||||
public TaskPageViewModelBase(ILocalizer localizer)
|
||||
{
|
||||
L = localizer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置任务准备状态
|
||||
/// </summary>
|
||||
/// <param name="tasksTotalCount">任务总数</param>
|
||||
protected void SetPendingStatus(int tasksTotalCount)
|
||||
{
|
||||
SetTaskStatusText(TaskStatusEnum.Pending, tasksTotalCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置运行中状态
|
||||
/// </summary>
|
||||
/// <param name="currentTaskIndex"></param>
|
||||
/// <param name="tasksTotalCount"></param>
|
||||
protected void SetRunningStatus(int currentTaskIndex, int tasksTotalCount)
|
||||
{
|
||||
SetTaskStatusText(TaskStatusEnum.Running, currentTaskIndex, tasksTotalCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置任务完成状态
|
||||
/// </summary>
|
||||
protected void SetCompletedStatus()
|
||||
{
|
||||
SetTaskStatusText(TaskStatusEnum.Completed);
|
||||
}
|
||||
|
||||
private void SetTaskStatusText(TaskStatusEnum taskStatusEnum, params object[] argments)
|
||||
{
|
||||
TaskStatusText = taskStatusEnum switch
|
||||
{
|
||||
TaskStatusEnum.Pending => string.Format(L["Task.TaskPendingStatusText"], argments),
|
||||
TaskStatusEnum.Running => string.Format(L["Task.TaskRunningStatusText"], argments),
|
||||
TaskStatusEnum.Completed => L["Task.TaskCompletedStatusText"],
|
||||
_ => throw new InvalidTaskStatusException(nameof(taskStatusEnum))
|
||||
};
|
||||
}
|
||||
}
|
||||
37
Cowain.Base/ViewModels/UserRoleMenuViewModel.cs
Normal file
37
Cowain.Base/ViewModels/UserRoleMenuViewModel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cowain.Base.ViewModels
|
||||
{
|
||||
public partial class UserRoleMenuViewModel : ObservableValidator
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// 角色id
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private int _roleId;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单key
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string? _menuKey;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单名称
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string? _menuName;
|
||||
/// <summary>
|
||||
/// 菜单命令
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string>? _menuActions;
|
||||
}
|
||||
}
|
||||
36
Cowain.Base/ViewModels/UserRoleViewModel.cs
Normal file
36
Cowain.Base/ViewModels/UserRoleViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Cowain.Base.Attributes;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Cowain.Base.ViewModels
|
||||
{
|
||||
public partial class UserRoleViewModel : ObservableValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色id
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
|
||||
private int _roleId;
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
[NotifyDataErrorInfo]
|
||||
[MinLength(2, "Errors.MinLength")]
|
||||
private string? _roleName;
|
||||
/// <summary>
|
||||
/// 是否有效
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private bool _isValid;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 菜单列表
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<UserRoleMenuViewModel>? _menus;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user