更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
using FluentValidation;
|
||||
using HandyControl.Controls;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static Dm.util.ByteArrayQueue;
|
||||
using ComboBox = System.Windows.Controls.ComboBox;
|
||||
using DatePicker = System.Windows.Controls.DatePicker;
|
||||
using PasswordBox = System.Windows.Controls.PasswordBox;
|
||||
using TextBox = System.Windows.Controls.TextBox;
|
||||
|
||||
namespace YY.Admin.Core.FluentValidation
|
||||
{
|
||||
public static class FluentValidationHelper
|
||||
{
|
||||
#region ✅ SkipValidation 附加属性
|
||||
public static readonly DependencyProperty SkipValidationProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"SkipValidation",
|
||||
typeof(bool),
|
||||
typeof(FluentValidationHelper),
|
||||
new System.Windows.PropertyMetadata(false));
|
||||
|
||||
public static void SetSkipValidation(DependencyObject element, bool value) =>
|
||||
element.SetValue(SkipValidationProperty, value);
|
||||
|
||||
public static bool GetSkipValidation(DependencyObject element) =>
|
||||
(bool)element.GetValue(SkipValidationProperty);
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 入口方法:从 ViewModel 自动获取 Validator,并绑定到所有子控件
|
||||
/// </summary>
|
||||
public static void Attach(FrameworkElement root, Type validatorOwnerType)
|
||||
{
|
||||
if (root == null || validatorOwnerType == null)
|
||||
return;
|
||||
|
||||
// 找出 validator 属性
|
||||
var validatorProp = validatorOwnerType
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.FirstOrDefault(p =>
|
||||
typeof(IValidator).IsAssignableFrom(p.PropertyType) &&
|
||||
p.Name.EndsWith("Validator", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (validatorProp == null)
|
||||
return;
|
||||
|
||||
var validatorInstance = root.DataContext != null
|
||||
? validatorProp.GetValue(root.DataContext)
|
||||
: null;
|
||||
|
||||
if (validatorInstance == null)
|
||||
return;
|
||||
|
||||
var validatedType = validatorProp.PropertyType.GetInterfaces()
|
||||
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IValidator<>))
|
||||
?.GetGenericArguments()[0];
|
||||
|
||||
if (validatedType == null)
|
||||
return;
|
||||
|
||||
AttachRecursive(root, validatorInstance, validatedType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归绑定所有子控件
|
||||
/// </summary>
|
||||
private static void AttachRecursive(DependencyObject parent, object validatorInstance, Type validatedType)
|
||||
{
|
||||
if (parent == null) return;
|
||||
|
||||
// 先尝试走逻辑树 —— 能穿透 ScrollViewer、Row、Col、ContentPresenter 等容器
|
||||
foreach (var childObj in LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>())
|
||||
{
|
||||
var child = childObj;
|
||||
|
||||
if (child is FrameworkElement feLogical)
|
||||
{
|
||||
// ⚠️ 先设置 ErrorTemplate,保证 skip 的控件也能显示样式
|
||||
var template = feLogical.TryFindResource("BottomLeftErrorTemplate_ForInfoElement") as ControlTemplate;
|
||||
if (template != null)
|
||||
{
|
||||
Validation.SetErrorTemplate(feLogical, template);
|
||||
}
|
||||
|
||||
// 🧭 再判断是否跳过校验
|
||||
if (GetSkipValidation(feLogical))
|
||||
{
|
||||
AttachRecursive(child, validatorInstance, validatedType);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 🪄 普通控件 / PasswordBox
|
||||
AttachValidationForElement(feLogical, validatorInstance, validatedType);
|
||||
}
|
||||
|
||||
// 递归逻辑树的子元素
|
||||
AttachRecursive(child, validatorInstance, validatedType);
|
||||
}
|
||||
|
||||
// 再补充视觉树 —— 仅在 parent 是 Visual 或 Visual3D 时调用,避免 ColumnDefinition 等非 Visual 抛错
|
||||
if (parent is Visual || parent is System.Windows.Media.Media3D.Visual3D)
|
||||
{
|
||||
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (int i = 0; i < childrenCount; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
|
||||
if (child is FrameworkElement fe)
|
||||
{
|
||||
// ⚠️ 先设置 ErrorTemplate,保证 skip 的控件也能显示样式
|
||||
var template = fe.TryFindResource("BottomLeftErrorTemplate_ForInfoElement") as ControlTemplate;
|
||||
if (template != null)
|
||||
{
|
||||
Validation.SetErrorTemplate(fe, template);
|
||||
}
|
||||
|
||||
// 🧭 再判断是否跳过校验
|
||||
if (GetSkipValidation(fe))
|
||||
{
|
||||
AttachRecursive(child, validatorInstance, validatedType);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 🪄 普通控件 / PasswordBox
|
||||
AttachValidationForElement(fe, validatorInstance, validatedType);
|
||||
}
|
||||
|
||||
// 递归子元素
|
||||
AttachRecursive(child, validatorInstance, validatedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 普通控件处理
|
||||
|
||||
private static void AttachValidationForElement(FrameworkElement fe, object validatorInstance, Type validatedType)
|
||||
{
|
||||
var localValues = fe.GetLocalValueEnumerator();
|
||||
while (localValues.MoveNext())
|
||||
{
|
||||
var entry = localValues.Current;
|
||||
var binding = BindingOperations.GetBinding(fe, entry.Property);
|
||||
if (binding == null) continue;
|
||||
|
||||
AddValidationRule(binding, validatorInstance, validatedType);
|
||||
|
||||
// 重新应用绑定才能生效
|
||||
BindingOperations.ClearBinding(fe, entry.Property);
|
||||
BindingOperations.SetBinding(fe, entry.Property, binding);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddValidationRule(Binding binding, object validatorInstance, Type validatedType)
|
||||
{
|
||||
var propertyPath = binding.Path?.Path;
|
||||
if (string.IsNullOrEmpty(propertyPath)) return;
|
||||
|
||||
var propertyName = propertyPath.Split('.').Last();
|
||||
var ruleType = typeof(FluentAutoValidationRule<>).MakeGenericType(validatedType);
|
||||
var rule = (ValidationRule)Activator.CreateInstance(ruleType)!;
|
||||
|
||||
ruleType.GetProperty("Validator")?.SetValue(rule, validatorInstance);
|
||||
ruleType.GetProperty("PropertyName")?.SetValue(rule, propertyName);
|
||||
|
||||
binding.ValidationRules.Add(rule);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 校验触发与跳过
|
||||
|
||||
public static bool ValidateAll<T>(FrameworkElement root, T model, IValidator<T> validator) where T : class
|
||||
{
|
||||
// 1️⃣ 获取所有 skip 的属性名
|
||||
var skipPropertyNames = GetSkipValidationPropertyNames(root);
|
||||
|
||||
// 2️⃣ 获取模型所有属性名
|
||||
var allPropertyNames = typeof(T)
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Select(p => p.Name)
|
||||
.ToList();
|
||||
|
||||
// 3️⃣ 排除 skip,剩下的就是要验证的属性
|
||||
var includePropertyNames = allPropertyNames
|
||||
.Except(skipPropertyNames, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
// 4️⃣ 验证时只验证这些属性
|
||||
var result = validator.ValidateAsync(model, options =>
|
||||
{
|
||||
options.IncludeProperties(includePropertyNames);
|
||||
}).ConfigureAwait(false) // 同步等待Task(ValidationRule 不支持 async → 只能同步等待。)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
// 5️⃣ 清除之前的错误
|
||||
ClearAllValidationErrors(root);
|
||||
|
||||
// 6️⃣ 只对未 skip 的属性标记错误
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
var controls = FindBoundControls(root, error.PropertyName);
|
||||
foreach (var control in controls)
|
||||
{
|
||||
var depProperty = GetBoundDependencyProperty(control);
|
||||
if (depProperty == null)
|
||||
continue;
|
||||
|
||||
var bindingExpr = BindingOperations.GetBindingExpression(control, depProperty);
|
||||
if (bindingExpr != null)
|
||||
{
|
||||
Validation.MarkInvalid(
|
||||
bindingExpr,
|
||||
new ValidationError(
|
||||
new ExceptionValidationRule(),
|
||||
bindingExpr,
|
||||
error.ErrorMessage,
|
||||
null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.IsValid;
|
||||
}
|
||||
|
||||
private static HashSet<string> GetSkipValidationPropertyNames(DependencyObject root)
|
||||
{
|
||||
var skipPropertyNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var control in FindVisualChildren<FrameworkElement>(root))
|
||||
{
|
||||
if (!GetSkipValidation(control))
|
||||
continue;
|
||||
|
||||
var dp = GetBoundDependencyProperty(control);
|
||||
if (dp == null) continue;
|
||||
|
||||
var binding = BindingOperations.GetBinding(control, dp);
|
||||
if (binding?.Path?.Path is string propertyPath && !string.IsNullOrEmpty(propertyPath))
|
||||
{
|
||||
var propertyName = propertyPath.Split('.').Last();
|
||||
skipPropertyNames.Add(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
return skipPropertyNames;
|
||||
}
|
||||
|
||||
public static void ClearAllValidationErrors(DependencyObject parent)
|
||||
{
|
||||
foreach (var control in FindVisualChildren<FrameworkElement>(parent))
|
||||
{
|
||||
if (GetSkipValidation(control))
|
||||
continue;
|
||||
|
||||
var depProperty = GetBoundDependencyProperty(control);
|
||||
if (depProperty == null) continue;
|
||||
|
||||
var expr = BindingOperations.GetBindingExpression(control, depProperty);
|
||||
if (expr != null)
|
||||
Validation.ClearInvalid(expr);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 工具方法
|
||||
|
||||
private static DependencyProperty? GetBoundDependencyProperty(FrameworkElement control)
|
||||
{
|
||||
return control switch
|
||||
{
|
||||
// 原生控件
|
||||
TextBox => TextBox.TextProperty,
|
||||
// ✅ WPF 原生 PasswordBox(注意:WPF 原生 PasswordBox 没有现成的依赖属性绑定 Password,需要额外处理)
|
||||
PasswordBox => PasswordBoxHelper.PasswordProperty,
|
||||
ComboBox cb => BindingOperations.GetBinding(cb, Selector.SelectedItemProperty) != null
|
||||
? Selector.SelectedItemProperty
|
||||
: BindingOperations.GetBinding(cb, Selector.SelectedValueProperty) != null
|
||||
? Selector.SelectedValueProperty
|
||||
: null,
|
||||
DatePicker => DatePicker.SelectedDateProperty,
|
||||
CheckBox => ToggleButton.IsCheckedProperty,
|
||||
|
||||
// HandyControl控件
|
||||
HandyControl.Controls.PasswordBox => HandyControl.Controls.PasswordBox.UnsafePasswordProperty,
|
||||
TimePicker => TimePicker.SelectedTimeProperty,
|
||||
NumericUpDown => NumericUpDown.ValueProperty,
|
||||
CheckComboBox => CheckComboBox.SelectedItemsProperty,
|
||||
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<FrameworkElement> FindBoundControls(DependencyObject parent, string propertyName)
|
||||
{
|
||||
return FindVisualChildren<FrameworkElement>(parent)
|
||||
.Where(fe =>
|
||||
{
|
||||
if (GetSkipValidation(fe)) return false;
|
||||
|
||||
var dp = GetBoundDependencyProperty(fe);
|
||||
if (dp == null) return false;
|
||||
|
||||
var expr = BindingOperations.GetBindingExpression(fe, dp);
|
||||
return expr != null && expr.ParentBinding.Path.Path.EndsWith(propertyName);
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
|
||||
{
|
||||
if (depObj == null) yield break;
|
||||
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(depObj, i);
|
||||
if (child is T tChild)
|
||||
yield return tChild;
|
||||
|
||||
foreach (var subChild in FindVisualChildren<T>(child))
|
||||
yield return subChild;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user