From 32ade95742f7854939d8de2e943dd5dded79f9e8 Mon Sep 17 00:00:00 2001 From: "David P.G" Date: Sat, 23 Aug 2025 09:09:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BB=8ETIA=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interfaces/IVariableAppService.cs | 7 + .../Services/VariableAppService.cs | 38 +++++ DMS.Infrastructure/Entities/DbVariable.cs | 117 +++++++++++++++- DMS.WPF/ViewModels/DeviceDetailViewModel.cs | 11 +- DMS.WPF/ViewModels/DevicesViewModel.cs | 7 +- .../Dialogs/ConfrimDialogViewModel.cs | 23 +-- .../Dialogs/ImportExcelDialogViewModel.cs | 13 +- DMS.WPF/ViewModels/VariableTableViewModel.cs | 131 +++++++----------- DMS.WPF/Views/Dialogs/ConfirmDialog.xaml | 2 +- DMS.WPF/Views/Dialogs/ImportExcelDialog.xaml | 6 +- 10 files changed, 243 insertions(+), 112 deletions(-) diff --git a/DMS.Application/Interfaces/IVariableAppService.cs b/DMS.Application/Interfaces/IVariableAppService.cs index 494e153..ad46287 100644 --- a/DMS.Application/Interfaces/IVariableAppService.cs +++ b/DMS.Application/Interfaces/IVariableAppService.cs @@ -36,4 +36,11 @@ public interface IVariableAppService /// 异步批量导入变量。 /// Task BatchImportVariablesAsync(List variables); + + /// + /// 检测一组变量是否已存在。 + /// + /// 要检查的变量列表。 + /// 返回输入列表中已存在的变量。 + Task> FindExistingVariablesAsync(IEnumerable variablesToCheck); } \ No newline at end of file diff --git a/DMS.Application/Services/VariableAppService.cs b/DMS.Application/Services/VariableAppService.cs index 9b0c3df..e582a4d 100644 --- a/DMS.Application/Services/VariableAppService.cs +++ b/DMS.Application/Services/VariableAppService.cs @@ -3,6 +3,8 @@ using DMS.Core.Interfaces; using DMS.Core.Models; using DMS.Application.DTOs; using DMS.Application.Interfaces; +using System.Collections.Generic; +using System.Linq; namespace DMS.Application.Services; @@ -138,4 +140,40 @@ public class VariableAppService : IVariableAppService throw new ApplicationException($"批量导入变量时发生错误,错误信息:{ex.Message}", ex); } } + + public async Task> FindExistingVariablesAsync(IEnumerable variablesToCheck) + { + if (variablesToCheck == null || !variablesToCheck.Any()) + { + return new List(); + } + + var names = variablesToCheck.Select(v => v.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToList(); + var s7Addresses = variablesToCheck.Select(v => v.S7Address).Where(a => !string.IsNullOrEmpty(a)).Distinct().ToList(); + var opcUaNodeIds = variablesToCheck.Select(v => v.OpcUaNodeId).Where(id => !string.IsNullOrEmpty(id)).Distinct().ToList(); + + var allVariables = await _repoManager.Variables.GetAllAsync(); + var existingVariablesFromDb = allVariables.Where(v => + (names.Any() && !string.IsNullOrEmpty(v.Name) && names.Contains(v.Name)) || + (s7Addresses.Any() && !string.IsNullOrEmpty(v.S7Address) && s7Addresses.Contains(v.S7Address)) || + (opcUaNodeIds.Any() && !string.IsNullOrEmpty(v.OpcUaNodeId) && opcUaNodeIds.Contains(v.OpcUaNodeId))) + .ToList(); + + if (existingVariablesFromDb == null || !existingVariablesFromDb.Any()) + { + return new List(); + } + + var existingNames = new HashSet(existingVariablesFromDb.Select(v => v.Name).Where(n => !string.IsNullOrEmpty(n))); + var existingS7Addresses = new HashSet(existingVariablesFromDb.Select(v => v.S7Address).Where(a => !string.IsNullOrEmpty(a))); + var existingOpcUaNodeIds = new HashSet(existingVariablesFromDb.Select(v => v.OpcUaNodeId).Where(id => !string.IsNullOrEmpty(id))); + + var result = variablesToCheck.Where(v => + (!string.IsNullOrEmpty(v.Name) && existingNames.Contains(v.Name)) || + (!string.IsNullOrEmpty(v.S7Address) && existingS7Addresses.Contains(v.S7Address)) || + (!string.IsNullOrEmpty(v.OpcUaNodeId) && existingOpcUaNodeIds.Contains(v.OpcUaNodeId))) + .ToList(); + + return result; + } } \ No newline at end of file diff --git a/DMS.Infrastructure/Entities/DbVariable.cs b/DMS.Infrastructure/Entities/DbVariable.cs index 9f3db0c..6ccdd1c 100644 --- a/DMS.Infrastructure/Entities/DbVariable.cs +++ b/DMS.Infrastructure/Entities/DbVariable.cs @@ -5,37 +5,146 @@ using CSharpDataType = SqlSugar.CSharpDataType; namespace DMS.Infrastructure.Entities; +/// +/// 代表数据库中的变量实体,与 'variable' 表对应。 +/// public class DbVariable { + /// + /// 主键ID,自增长。 + /// [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] public int Id { get; set; } + + /// + /// 变量名称。 + /// public string Name { get; set; } + + /// + /// 变量的描述信息,可以为空。 + /// + [SugarColumn(IsNullable = true)] public string Description { get; set; } + + /// + /// 变量的信号类型(例如:状态、控制、报警等)。 + /// [SugarColumn(ColumnDataType="varchar(20)",SqlParameterDbType=typeof(EnumToStringConvert))] - public SignalType SignalType { get; set; } // 对应 SignalType 枚举 + public SignalType SignalType { get; set; } + + /// + /// 变量的轮询级别,决定数据采集频率。 + /// [SugarColumn(ColumnDataType="varchar(20)",SqlParameterDbType=typeof(EnumToStringConvert))] - public PollLevelType PollLevel { get; set; } // 对应 PollLevelType 枚举 + public PollLevelType PollLevel { get; set; } + + /// + /// 指示此变量是否处于激活状态。 + /// public bool IsActive { get; set; } + + /// + /// 所属变量表的ID (外键)。 + /// public int VariableTableId { get; set; } + + /// + /// 从设备读取到的原始值。 + /// + [SugarColumn(IsNullable = true)] public string DataValue { get; set; } + + /// + /// 经过转换公式计算后的显示值。 + /// + [SugarColumn(IsNullable = true)] public string DisplayValue { get; set; } + + /// + /// S7协议中的地址 (例如: DB1.DBD0, M100.0),可以为空。 + /// + [SugarColumn(IsNullable = true)] public string S7Address { get; set; } + + /// + /// OPC UA协议中的NodeId,可以为空。 + /// + [SugarColumn(IsNullable = true)] public string OpcUaNodeId { get; set; } + + /// + /// 是否启用历史数据记录。 + /// public bool IsHistoryEnabled { get; set; } + + /// + /// 历史数据记录的死区值,变化量超过该值时才记录。 + /// public double HistoryDeadband { get; set; } + + /// + /// 是否启用报警功能。 + /// public bool IsAlarmEnabled { get; set; } + + /// + /// 报警下限值。 + /// public double AlarmMinValue { get; set; } + + /// + /// 报警上限值。 + /// public double AlarmMaxValue { get; set; } + + /// + /// 报警死区值,变化量超过该值时才触发报警。 + /// public double AlarmDeadband { get; set; } + + /// + /// 变量使用的通讯协议类型。 + /// [SugarColumn(ColumnDataType="varchar(20)",SqlParameterDbType=typeof(EnumToStringConvert))] - public ProtocolType Protocol { get; set; } // 对应 ProtocolType 枚举 + public ProtocolType Protocol { get; set; } + + /// + /// 变量的数据类型。 + /// [SugarColumn(ColumnDataType="varchar(20)",SqlParameterDbType=typeof(EnumToStringConvert))] - public CSharpDataType CSharpDataType { get; set; } // 对应 CSharpDataType 枚举 + public CSharpDataType CSharpDataType { get; set; } + + /// + /// 数值转换公式 (例如: "+3*5"),可以为空。 + /// + [SugarColumn(IsNullable = true)] public string ConversionFormula { get; set; } + + /// + /// 记录创建时间。 + /// public DateTime CreatedAt { get; set; } + + /// + /// 记录最后更新时间。 + /// public DateTime UpdatedAt { get; set; } + + /// + /// 最后更新记录的用户名或系统进程名。 + /// + [SugarColumn(IsNullable = true)] public string UpdatedBy { get; set; } + + /// + /// 标记该记录是否被修改,用于同步。 + /// public bool IsModified { get; set; } + + /// + /// OPC UA的更新类型(例如:轮询、订阅)。 + /// [SugarColumn(ColumnDataType="varchar(20)",SqlParameterDbType=typeof(EnumToStringConvert))] public OpcUaUpdateType OpcUaUpdateType { get; set; } } \ No newline at end of file diff --git a/DMS.WPF/ViewModels/DeviceDetailViewModel.cs b/DMS.WPF/ViewModels/DeviceDetailViewModel.cs index 0723187..8943730 100644 --- a/DMS.WPF/ViewModels/DeviceDetailViewModel.cs +++ b/DMS.WPF/ViewModels/DeviceDetailViewModel.cs @@ -125,13 +125,10 @@ public partial class DeviceDetailViewModel : ViewModelBase, INavigatable return; } - ConfrimDialogViewModel viewModel = new ConfrimDialogViewModel(); - viewModel.Message = $"确认要删除变量表名为:{SelectedVariableTable.Name} \n\n此操作将同时删除该变量表下的所有变量数据,且无法恢复!"; - viewModel.Title = "删除变量表"; - viewModel.PrimaryButContent = "删除"; - - var resViewModel = await _dialogService.ShowDialogAsync(viewModel); - if (resViewModel.IsPrimaryButton) + string message = $"确认要删除变量表名为:{SelectedVariableTable.Name} \n\n此操作将同时删除该变量表下的所有变量数据,且无法恢复!"; + ConfrimDialogViewModel viewModel = new ConfrimDialogViewModel("删除变量表",message,"删除"); + var res = await _dialogService.ShowDialogAsync(viewModel); + if (res) { var isDel = await _variableTableAppService.DeleteVariableTableAsync(SelectedVariableTable.Id); if (isDel) diff --git a/DMS.WPF/ViewModels/DevicesViewModel.cs b/DMS.WPF/ViewModels/DevicesViewModel.cs index 05bf519..82078e8 100644 --- a/DMS.WPF/ViewModels/DevicesViewModel.cs +++ b/DMS.WPF/ViewModels/DevicesViewModel.cs @@ -155,13 +155,8 @@ public partial class DevicesViewModel : ViewModelBase, INavigatable return; } - ConfrimDialogViewModel viewModel = new ConfrimDialogViewModel(); - viewModel.Message = $"确认要删除设备名为:{SelectedDevice.Name}"; - viewModel.Title = "删除设备"; - viewModel.PrimaryButContent = "删除"; - var resViewModel = await _dialogService.ShowDialogAsync(viewModel); - if (resViewModel.IsPrimaryButton) + if (await _dialogService.ShowDialogAsync(new ConfrimDialogViewModel("删除设备",$"确认要删除设备名为:{SelectedDevice.Name}","删除设备"))) { var isDel = await _deviceAppService.DeleteDeviceByIdAsync(SelectedDevice.Id); if (isDel) diff --git a/DMS.WPF/ViewModels/Dialogs/ConfrimDialogViewModel.cs b/DMS.WPF/ViewModels/Dialogs/ConfrimDialogViewModel.cs index 3791c4e..5dcc63b 100644 --- a/DMS.WPF/ViewModels/Dialogs/ConfrimDialogViewModel.cs +++ b/DMS.WPF/ViewModels/Dialogs/ConfrimDialogViewModel.cs @@ -3,25 +3,28 @@ using CommunityToolkit.Mvvm.Input; namespace DMS.WPF.ViewModels.Dialogs; -public partial class ConfrimDialogViewModel : DialogViewModelBase +public partial class ConfrimDialogViewModel : DialogViewModelBase { - public bool IsPrimaryButton { get; set; } [ObservableProperty] - private string message; - + private string _message; + + public ConfrimDialogViewModel(string title,string message,string primaryButText) + { + Message = message; + Title = title; + PrimaryButContent = primaryButText; + } [RelayCommand] - public void ParimaryButton() + private void PrimaryButton() { - IsPrimaryButton=true; - Close(this); + Close(true); } [RelayCommand] - public void CancleButton() + private void CancleButton() { - IsPrimaryButton=false; - Close(this); + Close(false); } } \ No newline at end of file diff --git a/DMS.WPF/ViewModels/Dialogs/ImportExcelDialogViewModel.cs b/DMS.WPF/ViewModels/Dialogs/ImportExcelDialogViewModel.cs index ce098fc..1e7ba43 100644 --- a/DMS.WPF/ViewModels/Dialogs/ImportExcelDialogViewModel.cs +++ b/DMS.WPF/ViewModels/Dialogs/ImportExcelDialogViewModel.cs @@ -13,6 +13,7 @@ namespace DMS.WPF.ViewModels.Dialogs; public partial class ImportExcelDialogViewModel : DialogViewModelBase> { + private readonly IMapper _mapper; private readonly IExcelService _excelService; [ObservableProperty] @@ -20,13 +21,18 @@ public partial class ImportExcelDialogViewModel : DialogViewModelBase _variables = new(); + + [ObservableProperty] + private ObservableCollection _variableItemViewModels ; [ObservableProperty] private IList _selectedVariables = new ArrayList(); - public ImportExcelDialogViewModel(IExcelService excelService) + public ImportExcelDialogViewModel(IMapper mapper,IExcelService excelService) { + _mapper = mapper; _excelService = excelService; + VariableItemViewModels = new(); } partial void OnFilePathChanged(string? value) @@ -39,6 +45,7 @@ public partial class ImportExcelDialogViewModel : DialogViewModelBase(_mapper.Map>(Variables)); } catch (System.Exception ex) { @@ -55,8 +62,8 @@ public partial class ImportExcelDialogViewModel : DialogViewModelBase().ToList(); - Close(selected); + var selected = SelectedVariables.Cast().ToList(); + Close(_mapper.Map>(selected)); } [RelayCommand] diff --git a/DMS.WPF/ViewModels/VariableTableViewModel.cs b/DMS.WPF/ViewModels/VariableTableViewModel.cs index 906dce7..e24e05a 100644 --- a/DMS.WPF/ViewModels/VariableTableViewModel.cs +++ b/DMS.WPF/ViewModels/VariableTableViewModel.cs @@ -10,11 +10,14 @@ using DMS.WPF.ViewModels.Items; using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows.Data; +using DMS.Application.DTOs; +using DMS.Application.Interfaces; +using DMS.Application.Services; +using DMS.Helper; using Microsoft.Extensions.DependencyInjection; namespace DMS.WPF.ViewModels; - partial class VariableTableViewModel : ViewModelBase, INavigatable { private readonly IMapper _mapper; @@ -24,6 +27,8 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable /// private readonly IDialogService _dialogService; + private readonly IVariableAppService _variableAppService; + /// /// 当前正在操作的变量表实体。 /// 通过 ObservableProperty 自动生成 VariableTable 属性和 OnVariableTableChanged 方法。 @@ -91,10 +96,12 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable /// 对话服务接口的实例。 private readonly DataServices _dataServices; - public VariableTableViewModel(IMapper mapper, IDialogService dialogService, DataServices dataServices) + public VariableTableViewModel(IMapper mapper, IDialogService dialogService, IVariableAppService variableAppService, + DataServices dataServices) { _mapper = mapper; _dialogService = dialogService; + _variableAppService = variableAppService; _dataServices = dataServices; IsLoadCompletion = false; // 初始设置为 false,表示未完成加载 _variables = new ObservableCollection(); // 初始化集合 @@ -199,7 +206,7 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable { // 查找所有已修改的变量数据 var modifiedDatas = Variables.Where(d => d.IsModified == true) - .ToList(); + .ToList(); // 如果没有修改,则直接允许退出 if (modifiedDatas.Count == 0) return true; @@ -294,83 +301,51 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable [RelayCommand] private async void ImprotFromTiaVarTable() { - ImportExcelDialogViewModel viewModel = App.Current.Services.GetRequiredService(); - List improtVariable = await _dialogService.ShowDialogAsync(viewModel); - - if (improtVariable == null || improtVariable.Count==0) return; + try + { + ImportExcelDialogViewModel + viewModel = App.Current.Services.GetRequiredService(); + List improtVariable = await _dialogService.ShowDialogAsync(viewModel); + if (improtVariable == null || improtVariable.Count == 0) return; + var improtVariableDtos = _mapper.Map>(improtVariable); + foreach (var variableDto in improtVariableDtos) + { + variableDto.CreatedAt = DateTime.Now; + variableDto.UpdatedAt = DateTime.Now; + } - // ContentDialog processingDialog = null; // 用于显示处理中的对话框 - // try - // { - // // 让用户选择要导入的Excel文件路径 - // var filePath = await _dialogService.ShowImportExcelDialog(); - // if (string.IsNullOrEmpty(filePath)) - // return; // 如果用户取消选择,则返回 - // - // // 读取Excel文件并将其内容转换为 Variable 列表 - // var importVarDataList = DMS.Infrastructure.Helper.ExcelHelper.ImprotFromTiaVariableTable(filePath); - // if (importVarDataList.Count == 0) - // return; // 如果没有读取到数据,则返回 - // - // // 显示处理中的对话框 - // processingDialog = _dialogService.ShowProcessingDialog("正在处理...", "正在导入变量,请稍等片刻...."); - // - // List newVariables = new List(); - // List importedVariableNames = new List(); - // List existingVariableNames = new List(); - // - // foreach (var variableData in importVarDataList) - // { - // // 判断是否存在重复变量 - // // 判断是否存在重复变量,仅在当前 VariableTable 的 Variables 中查找 - // bool isDuplicate = Variables.Any(existingVar => - // (existingVar.Name == variableData.Name) || - // (!string.IsNullOrEmpty(variableData.NodeId) && - // existingVar.NodeId == variableData.NodeId) || - // (!string.IsNullOrEmpty(variableData.S7Address) && - // existingVar.S7Address == variableData.S7Address) - // ); - // - // if (isDuplicate) - // { - // existingVariableNames.Add(variableData.Name); - // } - // else - // { - // variableData.CreateTime = DateTime.Now; - // variableData.IsActive = true; - // variableData.VariableTableId = VariableTable.Id; - // newVariables.Add(variableData); - // importedVariableNames.Add(variableData.Name); - // } - // } - // - // if (newVariables.Any()) - // { - // // 批量插入新变量数据到数据库 - // var resVarDataCount = await _varDataRepository.AddAsync(newVariables); - // NlogHelper.Info($"成功导入变量:{resVarDataCount}个。"); - // } - // - // // 更新界面显示的数据:重新从数据库加载所有变量数据 - // await RefreshDataView(); - // - // processingDialog?.Hide(); // 隐藏处理中的对话框 - // - // // 显示导入结果对话框 - // await _dialogService.ShowImportResultDialog(importedVariableNames, existingVariableNames); - // } - // catch (Exception e) - // { - // // 捕获并显示错误通知 - // NotificationHelper.ShowError($"从TIA导入变量的过程中发生了不可预期的错误:{e.Message}", e); - // } - // finally - // { - // processingDialog?.Hide(); // 确保在任何情况下都隐藏对话框 - // } + var existList = await _variableAppService.FindExistingVariablesAsync(improtVariableDtos); + if (existList.Count > 0) + { + // // 拼接要删除的变量名称,用于确认提示 + var existNames = string.Join("、", existList.Select(v => v.Name)); + var confrimDialogViewModel + = new ConfrimDialogViewModel("存在已经添加的变量", $"变量名称:{existNames},已经存在,是否跳过继续添加其他的变量。取消则不添加任何变量", "继续"); + var res = await _dialogService.ShowDialogAsync(confrimDialogViewModel); + if (!res) return; + // 从导入列表中删除已经存在的变量 + improtVariableDtos.RemoveAll(variableDto => existList.Contains(variableDto)); + } + + if (improtVariableDtos.Count != 0) + { + var isSuccess = await _variableAppService.BatchImportVariablesAsync(improtVariableDtos); + if (isSuccess) + { + NotificationHelper.ShowSuccess($"从Excel导入变量成功,共导入变量:{improtVariableDtos.Count}个"); + } + } + else + { + NotificationHelper.ShowSuccess($"列表中没有要添加的变量了。 "); + } + } + catch (Exception e) + { + NotificationHelper.ShowError($"从TIA导入变量的过程中发生了不可预期的错误:{e.Message}", e); + } } /// @@ -904,8 +879,6 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable { IsOpcUaProtocolSelected = true; } - } - } } \ No newline at end of file diff --git a/DMS.WPF/Views/Dialogs/ConfirmDialog.xaml b/DMS.WPF/Views/Dialogs/ConfirmDialog.xaml index 4243ee0..d8e064d 100644 --- a/DMS.WPF/Views/Dialogs/ConfirmDialog.xaml +++ b/DMS.WPF/Views/Dialogs/ConfirmDialog.xaml @@ -18,7 +18,7 @@ mc:Ignorable="d"> - + diff --git a/DMS.WPF/Views/Dialogs/ImportExcelDialog.xaml b/DMS.WPF/Views/Dialogs/ImportExcelDialog.xaml index f9b4979..d14e344 100644 --- a/DMS.WPF/Views/Dialogs/ImportExcelDialog.xaml +++ b/DMS.WPF/Views/Dialogs/ImportExcelDialog.xaml @@ -56,7 +56,7 @@ @@ -66,7 +66,7 @@