From fff2c0477cf594b085740c3249de77fea76b6f50 Mon Sep 17 00:00:00 2001 From: "David P.G" Date: Wed, 3 Sep 2025 11:12:42 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=99=E6=95=B0=E6=8D=AE=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=8F=9C=E5=8D=95=E6=93=8D=E4=BD=9C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTOs/Events/MenuChangedEventArgs.cs | 44 +++ DMS.Application/DTOs/MenuBeanDto.cs | 41 ++- .../Interfaces/IDataCenterService.cs | 150 ++++----- DMS.Application/Services/DataCenterService.cs | 303 ++++++++---------- DMS.Core/Models/MenuBean.cs | 35 +- .../DataCenterServiceTests.cs | 173 ---------- .../Services/OpcUaServiceTest.cs | 159 --------- DMS.WPF/App.xaml.cs | 2 +- DMS.WPF/ViewModels/Items/MenuItemViewModel.cs | 48 ++- 9 files changed, 357 insertions(+), 598 deletions(-) create mode 100644 DMS.Application/DTOs/Events/MenuChangedEventArgs.cs delete mode 100644 DMS.Infrastructure.UnitTests/DataCenterServiceTests.cs delete mode 100644 DMS.Infrastructure.UnitTests/Services/OpcUaServiceTest.cs diff --git a/DMS.Application/DTOs/Events/MenuChangedEventArgs.cs b/DMS.Application/DTOs/Events/MenuChangedEventArgs.cs new file mode 100644 index 0000000..17aa32c --- /dev/null +++ b/DMS.Application/DTOs/Events/MenuChangedEventArgs.cs @@ -0,0 +1,44 @@ +using System; + +namespace DMS.Application.DTOs.Events +{ + /// + /// 菜单变更事件参数 + /// + public class MenuChangedEventArgs : System.EventArgs + { + /// + /// 变更类型 + /// + public DataChangeType ChangeType { get; } + + /// + /// 菜单DTO + /// + public MenuBeanDto Menu { get; } + + /// + /// 父级菜单DTO + /// + public MenuBeanDto ParentMenu { get; } + + /// + /// 变更时间 + /// + public DateTime ChangeTime { get; } + + /// + /// 构造函数 + /// + /// 变更类型 + /// 菜单DTO + /// 父级菜单DTO + public MenuChangedEventArgs(DataChangeType changeType, MenuBeanDto menu, MenuBeanDto parentMenu) + { + ChangeType = changeType; + Menu = menu; + ParentMenu = parentMenu; + ChangeTime = DateTime.Now; + } + } +} \ No newline at end of file diff --git a/DMS.Application/DTOs/MenuBeanDto.cs b/DMS.Application/DTOs/MenuBeanDto.cs index 9e08565..25c4d10 100644 --- a/DMS.Application/DTOs/MenuBeanDto.cs +++ b/DMS.Application/DTOs/MenuBeanDto.cs @@ -3,27 +3,62 @@ using DMS.Core.Enums; namespace DMS.Application.DTOs; /// -/// 用于在UI上显示菜单项的DTO。 +/// 菜单数据传输对象(DTO) +/// 用于在应用程序层和表示层之间传输菜单数据,特别是在UI上显示菜单项时使用。 /// public class MenuBeanDto { + /// + /// 菜单项的唯一标识符 + /// public int Id { get; set; } - public int? ParentId { get; set; } + + /// + /// 父级菜单项的ID,用于构建层级菜单结构 + /// 如果为0表示为顶级菜单项 + /// + public int ParentId { get; set; } + + /// + /// 菜单项显示的标题文本 + /// public string Header { get; set; } + + /// + /// 菜单项显示的图标资源路径或标识符 + /// public string Icon { get; set; } + /// /// 菜单的类型,例如菜单关联的是设备,还是变量表,或者是MQTT + /// 用于区分不同类型的菜单项,决定点击菜单项时的行为 /// public MenuType MenuType { get; set; } /// /// 菜单关联的数据ID,例如设备Id,变量表Id + /// 根据MenuType的不同,此ID可以指向不同的数据实体 /// public int TargetId { get; set; } + /// - /// 菜单关联的数据ID,例如设备Id,变量表Id + /// 目标视图的键值,用于导航到特定的视图页面 /// public string TargetViewKey { get; set; } + + /// + /// 导航参数,传递给目标视图的额外参数信息 + /// public string NavigationParameter { get; set; } + + /// + /// 菜单项在同级菜单中的显示顺序 + /// 数值越小显示越靠前 + /// public int DisplayOrder { get; set; } + + /// + /// 子菜单项集合,用于构建层级菜单结构 + /// + public List Children { get; set; } = new List(); } \ No newline at end of file diff --git a/DMS.Application/Interfaces/IDataCenterService.cs b/DMS.Application/Interfaces/IDataCenterService.cs index 860d75d..f039f1a 100644 --- a/DMS.Application/Interfaces/IDataCenterService.cs +++ b/DMS.Application/Interfaces/IDataCenterService.cs @@ -13,34 +13,6 @@ namespace DMS.Application.Interfaces; /// public interface IDataCenterService { - #region 事件定义 - - /// - /// 当数据加载完成时触发 - /// - event EventHandler DataLoadCompleted; - - /// - /// 当设备数据发生变化时触发 - /// - event EventHandler DeviceChanged; - - /// - /// 当变量表数据发生变化时触发 - /// - event EventHandler VariableTableChanged; - - /// - /// 当变量数据发生变化时触发 - /// - event EventHandler VariableChanged; - - /// - /// 当数据发生任何变化时触发 - /// - event EventHandler DataChanged; - - #endregion #region 设备管理 /// @@ -138,91 +110,59 @@ public interface IDataCenterService #endregion - #region 变量管理 + #region 菜单管理 /// - /// 异步根据ID获取变量DTO。 + /// 异步获取所有菜单DTO列表。 /// - Task GetVariableByIdAsync(int id); + Task> GetAllMenusAsync(); /// - /// 异步获取所有变量DTO列表。 + /// 异步根据ID获取菜单DTO。 /// - Task> GetAllVariablesAsync(); + Task GetMenuByIdAsync(int id); /// - /// 异步创建一个新变量(事务性操作)。 + /// 异步创建一个新菜单。 /// - Task CreateVariableAsync(VariableDto variableDto); + Task CreateMenuAsync(MenuBeanDto menuDto); /// - /// 异步更新一个已存在的变量(事务性操作)。 + /// 异步更新一个已存在的菜单。 /// - Task UpdateVariableAsync(VariableDto variableDto); + Task UpdateMenuAsync(MenuBeanDto menuDto); /// - /// 异步批量更新变量(事务性操作)。 + /// 异步删除一个菜单。 /// - Task UpdateVariablesAsync(List variableDtos); + Task DeleteMenuAsync(int id); /// - /// 异步删除一个变量(事务性操作)。 + /// 在内存中添加菜单 /// - Task DeleteVariableAsync(int id); + void AddMenuToMemory(MenuBeanDto menuDto); /// - /// 异步批量删除变量(事务性操作)。 + /// 在内存中更新菜单 /// - Task DeleteVariablesAsync(List ids); + void UpdateMenuInMemory(MenuBeanDto menuDto); /// - /// 异步批量导入变量。 + /// 在内存中删除菜单 /// - Task BatchImportVariablesAsync(List variables); + void RemoveMenuFromMemory(int menuId); /// - /// 检测一组变量是否已存在。 + /// 获取根菜单列表 /// - /// 要检查的变量列表。 - /// 返回输入列表中已存在的变量。 - Task> FindExistingVariablesAsync(IEnumerable variablesToCheck); + List GetRootMenus(); /// - /// 检测单个变量是否已存在。 + /// 根据父级ID获取子菜单列表 /// - /// 要检查的变量。 - /// 如果变量已存在则返回该变量,否则返回null。 - Task FindExistingVariableAsync(VariableDto variableToCheck); - - /// - /// 在内存中添加变量 - /// - void AddVariableToMemory(VariableDto variableDto); - - /// - /// 在内存中更新变量 - /// - void UpdateVariableInMemory(VariableDto variableDto); - - /// - /// 在内存中删除变量 - /// - void RemoveVariableFromMemory(int variableId); - - /// - /// 批量在内存中添加变量 - /// - void AddVariablesToMemory(List variables); - - /// - /// 批量在内存中更新变量 - /// - void UpdateVariablesInMemory(List variables); - - /// - /// 批量在内存中删除变量 - /// - void RemoveVariablesFromMemory(List variableIds); + /// 父级菜单ID + /// 子菜单列表 + List GetChildMenus(int parentId); #endregion @@ -243,6 +183,11 @@ public interface IDataCenterService /// ConcurrentDictionary Variables { get; } + /// + /// 获取所有菜单的安全字典。 + /// + ConcurrentDictionary Menus { get; } + #endregion #region 数据加载和初始化 @@ -267,5 +212,44 @@ public interface IDataCenterService /// Task> LoadAllVariablesAsync(); + /// + /// 异步加载所有菜单数据。 + /// + Task> LoadAllMenusAsync(); + + #endregion + + #region 事件定义 + + /// + /// 当数据加载完成时触发 + /// + event EventHandler DataLoadCompleted; + + /// + /// 当设备数据发生变化时触发 + /// + event EventHandler DeviceChanged; + + /// + /// 当变量表数据发生变化时触发 + /// + event EventHandler VariableTableChanged; + + /// + /// 当变量数据发生变化时触发 + /// + event EventHandler VariableChanged; + + /// + /// 当菜单数据发生变化时触发 + /// + event EventHandler MenuChanged; + + /// + /// 当数据发生任何变化时触发 + /// + event EventHandler DataChanged; + #endregion } \ No newline at end of file diff --git a/DMS.Application/Services/DataCenterService.cs b/DMS.Application/Services/DataCenterService.cs index 8129f75..72df6cc 100644 --- a/DMS.Application/Services/DataCenterService.cs +++ b/DMS.Application/Services/DataCenterService.cs @@ -14,11 +14,38 @@ using System.Linq; namespace DMS.Application.Services; /// -/// 数据中心服务,负责管理所有的数据,包括设备、变量表和变量。 +/// 数据中心服务,负责管理所有的数据,包括设备、变量表、变量和菜单。 /// 实现 接口。 /// public class DataCenterService : IDataCenterService { + private readonly IRepositoryManager _repositoryManager; + private readonly IMapper _mapper; + private readonly IDeviceAppService _deviceAppService; + private readonly IVariableTableAppService _variableTableAppService; + private readonly IVariableAppService _variableAppService; + private readonly IMenuService _menuService; + + /// + /// 安全字典,用于存储所有设备数据 + /// + public ConcurrentDictionary Devices { get; } = new(); + + /// + /// 安全字典,用于存储所有变量表数据 + /// + public ConcurrentDictionary VariableTables { get; } = new(); + + /// + /// 安全字典,用于存储所有变量数据 + /// + public ConcurrentDictionary Variables { get; } = new(); + + /// + /// 安全字典,用于存储所有菜单数据 + /// + public ConcurrentDictionary Menus { get; } = new(); + #region 事件定义 /// @@ -41,32 +68,17 @@ public class DataCenterService : IDataCenterService /// public event EventHandler VariableChanged; + /// + /// 当菜单数据发生变化时触发 + /// + public event EventHandler MenuChanged; + /// /// 当数据发生任何变化时触发 /// public event EventHandler DataChanged; #endregion - private readonly IRepositoryManager _repositoryManager; - private readonly IMapper _mapper; - private readonly IDeviceAppService _deviceAppService; - private readonly IVariableTableAppService _variableTableAppService; - private readonly IVariableAppService _variableAppService; - - /// - /// 安全字典,用于存储所有设备数据 - /// - public ConcurrentDictionary Devices { get; } = new(); - - /// - /// 安全字典,用于存储所有变量表数据 - /// - public ConcurrentDictionary VariableTables { get; } = new(); - - /// - /// 安全字典,用于存储所有变量数据 - /// - public ConcurrentDictionary Variables { get; } = new(); /// /// 构造函数,通过依赖注入获取仓储管理器和相关服务实例。 @@ -76,18 +88,21 @@ public class DataCenterService : IDataCenterService /// 设备应用服务实例。 /// 变量表应用服务实例。 /// 变量应用服务实例。 + /// 菜单服务实例。 public DataCenterService( IRepositoryManager repositoryManager, IMapper mapper, IDeviceAppService deviceAppService, IVariableTableAppService variableTableAppService, - IVariableAppService variableAppService) + IVariableAppService variableAppService, + IMenuService menuService) { _repositoryManager = repositoryManager; _mapper = mapper; _deviceAppService = deviceAppService; _variableTableAppService = variableTableAppService; _variableAppService = variableAppService; + _menuService = menuService; } #region 设备管理 @@ -224,6 +239,7 @@ public class DataCenterService : IDataCenterService if (Devices.TryGetValue(variableTableDto.DeviceId, out var device)) { deviceDto = device; + variableTableDto.Device = deviceDto; } if (VariableTables.TryAdd(variableTableDto.Id, variableTableDto)) @@ -275,214 +291,117 @@ public class DataCenterService : IDataCenterService #endregion - #region 变量管理 + #region 菜单管理 /// - /// 异步根据ID获取变量DTO。 + /// 异步获取所有菜单DTO列表。 /// - public async Task GetVariableByIdAsync(int id) + public async Task> GetAllMenusAsync() { - return await _variableAppService.GetVariableByIdAsync(id); + return await _menuService.GetAllMenusAsync(); } /// - /// 异步获取所有变量DTO列表。 + /// 异步根据ID获取菜单DTO。 /// - public async Task> GetAllVariablesAsync() + public async Task GetMenuByIdAsync(int id) { - return await _variableAppService.GetAllVariablesAsync(); + return await _menuService.GetMenuByIdAsync(id); } /// - /// 异步创建一个新变量(事务性操作)。 + /// 异步创建一个新菜单。 /// - public async Task CreateVariableAsync(VariableDto variableDto) + public async Task CreateMenuAsync(MenuBeanDto menuDto) { - return await _variableAppService.CreateVariableAsync(variableDto); + return await _menuService.CreateMenuAsync(menuDto); } /// - /// 异步更新一个已存在的变量(事务性操作)。 + /// 异步更新一个已存在的菜单。 /// - public async Task UpdateVariableAsync(VariableDto variableDto) + public async Task UpdateMenuAsync(MenuBeanDto menuDto) { - return await _variableAppService.UpdateVariableAsync(variableDto); + await _menuService.UpdateMenuAsync(menuDto); } /// - /// 异步批量更新变量(事务性操作)。 + /// 异步删除一个菜单。 /// - public async Task UpdateVariablesAsync(List variableDtos) + public async Task DeleteMenuAsync(int id) { - return await _variableAppService.UpdateVariablesAsync(variableDtos); + await _menuService.DeleteMenuAsync(id); } /// - /// 异步删除一个变量(事务性操作)。 + /// 在内存中添加菜单 /// - public async Task DeleteVariableAsync(int id) + public void AddMenuToMemory(MenuBeanDto menuDto) { - return await _variableAppService.DeleteVariableAsync(id); - } - - /// - /// 异步批量删除变量(事务性操作)。 - /// - public async Task DeleteVariablesAsync(List ids) - { - return await _variableAppService.DeleteVariablesAsync(ids); - } - - /// - /// 异步批量导入变量。 - /// - public async Task BatchImportVariablesAsync(List variables) - { - return await _variableAppService.BatchImportVariablesAsync(variables); - } - - /// - /// 检测一组变量是否已存在。 - /// - public async Task> FindExistingVariablesAsync(IEnumerable variablesToCheck) - { - return await _variableAppService.FindExistingVariablesAsync(variablesToCheck); - } - - /// - /// 检测单个变量是否已存在。 - /// - public async Task FindExistingVariableAsync(VariableDto variableToCheck) - { - return await _variableAppService.FindExistingVariableAsync(variableToCheck); - } - - /// - /// 在内存中添加变量 - /// - public void AddVariableToMemory(VariableDto variableDto) - { - VariableTableDto variableTableDto = null; - if (VariableTables.TryGetValue(variableDto.VariableTableId, out var variableTable)) + if (Menus.TryAdd(menuDto.Id, menuDto)) { - variableTableDto = variableTable; - } - - if (Variables.TryAdd(variableDto.Id, variableDto)) - { - OnVariableChanged(new VariableChangedEventArgs( - DataChangeType.Added, - variableDto, - variableTableDto)); - } - } - - /// - /// 在内存中更新变量 - /// - public void UpdateVariableInMemory(VariableDto variableDto) - { - VariableTableDto variableTableDto = null; - if (VariableTables.TryGetValue(variableDto.VariableTableId, out var variableTable)) - { - variableTableDto = variableTable; - } - - Variables.AddOrUpdate(variableDto.Id, variableDto, (key, oldValue) => variableDto); - OnVariableChanged(new VariableChangedEventArgs( - DataChangeType.Updated, - variableDto, - variableTableDto)); - } - - /// - /// 在内存中删除变量 - /// - public void RemoveVariableFromMemory(int variableId) - { - if (Variables.TryRemove(variableId, out var variableDto)) - { - VariableTableDto variableTableDto = null; - if (variableDto != null && VariableTables.TryGetValue(variableDto.VariableTableId, out var variableTable)) + MenuBeanDto parentMenu = null; + if (menuDto.ParentId > 0 && Menus.TryGetValue(menuDto.ParentId, out var parent)) { - variableTableDto = variableTable; + parentMenu = parent; + parent.Children.Add(menuDto); + + } - OnVariableChanged(new VariableChangedEventArgs( - DataChangeType.Deleted, - variableDto, - variableTableDto)); + OnMenuChanged(new MenuChangedEventArgs(DataChangeType.Added, menuDto, parentMenu)); } } /// - /// 批量在内存中添加变量 + /// 在内存中更新菜单 /// - public void AddVariablesToMemory(List variables) + public void UpdateMenuInMemory(MenuBeanDto menuDto) { - foreach (var variable in variables) - { - VariableTableDto variableTableDto = null; - if (VariableTables.TryGetValue(variable.VariableTableId, out var variableTable)) - { - variableTableDto = variableTable; - } + Menus.AddOrUpdate(menuDto.Id, menuDto, (key, oldValue) => menuDto); - if (Variables.TryAdd(variable.Id, variable)) - { - OnVariableChanged(new VariableChangedEventArgs( - DataChangeType.Added, - variable, - variableTableDto)); - } + MenuBeanDto parentMenu = null; + if (menuDto.ParentId > 0 && Menus.TryGetValue(menuDto.ParentId, out var parent)) + { + parentMenu = parent; } - OnDataChanged(new DataChangedEventArgs(DataChangeType.BatchOperation)); + + OnMenuChanged(new MenuChangedEventArgs(DataChangeType.Updated, menuDto, parentMenu)); } /// - /// 批量在内存中更新变量 + /// 在内存中删除菜单 /// - public void UpdateVariablesInMemory(List variables) + public void RemoveMenuFromMemory(int menuId) { - foreach (var variable in variables) + if (Menus.TryRemove(menuId, out var menuDto)) { - VariableTableDto variableTableDto = null; - if (VariableTables.TryGetValue(variable.VariableTableId, out var variableTable)) + MenuBeanDto parentMenu = null; + if (menuDto.ParentId > 0 && Menus.TryGetValue(menuDto.ParentId, out var parent)) { - variableTableDto = variableTable; + parentMenu = parent; } - Variables.AddOrUpdate(variable.Id, variable, (key, oldValue) => variable); - OnVariableChanged(new VariableChangedEventArgs( - DataChangeType.Updated, - variable, - variableTableDto)); + OnMenuChanged(new MenuChangedEventArgs(DataChangeType.Deleted, menuDto, parentMenu)); } - OnDataChanged(new DataChangedEventArgs(DataChangeType.BatchOperation)); } /// - /// 批量在内存中删除变量 + /// 获取根菜单列表 /// - public void RemoveVariablesFromMemory(List variableIds) + public List GetRootMenus() { - foreach (var variableId in variableIds) - { - if (Variables.TryRemove(variableId, out var variableDto)) - { - VariableTableDto variableTableDto = null; - if (variableDto != null && VariableTables.TryGetValue(variableDto.VariableTableId, out var variableTable)) - { - variableTableDto = variableTable; - } + return Menus.Values.Where(m => m.ParentId == 0).ToList(); + } - OnVariableChanged(new VariableChangedEventArgs( - DataChangeType.Deleted, - variableDto, - variableTableDto)); - } - } - OnDataChanged(new DataChangedEventArgs(DataChangeType.BatchOperation)); + /// + /// 根据父级ID获取子菜单列表 + /// + /// 父级菜单ID + /// 子菜单列表 + public List GetChildMenus(int parentId) + { + return Menus.Values.Where(m => m.ParentId == parentId).ToList(); } #endregion @@ -529,6 +448,16 @@ public class DataCenterService : IDataCenterService OnDataChanged(new DataChangedEventArgs(e.ChangeType)); } + /// + /// 触发菜单变更事件 + /// + /// 事件参数 + protected virtual void OnMenuChanged(MenuChangedEventArgs e) + { + MenuChanged?.Invoke(this, e); + OnDataChanged(new DataChangedEventArgs(e.ChangeType)); + } + /// /// 触发数据变更事件 /// @@ -553,6 +482,7 @@ public class DataCenterService : IDataCenterService Devices.Clear(); VariableTables.Clear(); Variables.Clear(); + Menus.Clear(); // 加载所有设备 var devices = await _repositoryManager.Devices.GetAllAsync(); @@ -566,6 +496,10 @@ public class DataCenterService : IDataCenterService var variables = await _repositoryManager.Variables.GetAllAsync(); var variableDtos = _mapper.Map>(variables); + // 加载所有菜单 + var menus = await _repositoryManager.Menus.GetAllAsync(); + var menuDtos = _mapper.Map>(menus); + // 建立设备与变量表的关联 foreach (var deviceDto in deviceDtos) { @@ -594,6 +528,12 @@ public class DataCenterService : IDataCenterService Variables.TryAdd(variableDto.Id, variableDto); } + // 将菜单添加到安全字典 + foreach (var menuDto in menuDtos) + { + Menus.TryAdd(menuDto.Id, menuDto); + } + // 触发数据加载完成事件 OnDataLoadCompleted(new DataLoadCompletedEventArgs( deviceDtos, @@ -614,6 +554,23 @@ public class DataCenterService : IDataCenterService } } + /// + /// 异步加载所有菜单数据。 + /// + public async Task> LoadAllMenusAsync() + { + try + { + // 获取所有菜单 + var menus = await _repositoryManager.Menus.GetAllAsync(); + return _mapper.Map>(menus); + } + catch (Exception ex) + { + throw new ApplicationException($"加载所有菜单数据时发生错误,错误信息:{ex.Message}", ex); + } + } + /// /// 异步加载所有设备及其关联数据。 /// diff --git a/DMS.Core/Models/MenuBean.cs b/DMS.Core/Models/MenuBean.cs index f61b85c..25bd3b3 100644 --- a/DMS.Core/Models/MenuBean.cs +++ b/DMS.Core/Models/MenuBean.cs @@ -3,27 +3,58 @@ using DMS.Core.Enums; namespace DMS.Core.Models; /// -/// 领域模型:代表一个菜单项。 +/// 菜单项领域模型 +/// 代表系统中的一个菜单项,包含菜单的基本信息和导航相关信息 +/// 作为领域模型,它在核心业务逻辑中使用,与数据库实体对应 /// public class MenuBean { + /// + /// 菜单项的唯一标识符 + /// public int Id { get; set; } + + /// + /// 父级菜单项的ID,用于构建层级菜单结构 + /// 如果为null表示为顶级菜单项 + /// public int? ParentId { get; set; } + + /// + /// 菜单项显示的标题文本 + /// public string Header { get; set; } + + /// + /// 菜单项显示的图标资源路径或标识符 + /// public string Icon { get; set; } + /// /// 菜单的类型,例如菜单关联的是设备,还是变量表,或者是MQTT + /// 用于区分不同类型的菜单项,决定点击菜单项时的行为 /// public MenuType MenuType { get; set; } /// /// 菜单关联的数据ID,例如设备Id,变量表Id + /// 根据MenuType的不同,此ID可以指向不同的数据实体 /// public int TargetId { get; set; } + /// - /// 菜单关联的数据ID,例如设备Id,变量表Id + /// 目标视图的键值,用于导航到特定的视图页面 /// public string TargetViewKey { get; set; } + + /// + /// 导航参数,传递给目标视图的额外参数信息 + /// public string NavigationParameter { get; set; } + + /// + /// 菜单项在同级菜单中的显示顺序 + /// 数值越小显示越靠前 + /// public int DisplayOrder { get; set; } } \ No newline at end of file diff --git a/DMS.Infrastructure.UnitTests/DataCenterServiceTests.cs b/DMS.Infrastructure.UnitTests/DataCenterServiceTests.cs deleted file mode 100644 index 0c29b6d..0000000 --- a/DMS.Infrastructure.UnitTests/DataCenterServiceTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -using DMS.Application.DTOs; -using DMS.Application.DTOs.Events; -using DMS.Application.Interfaces; -using DMS.Application.Services; -using DMS.Core.Interfaces; -using Moq; -using System; -using System.Collections.Concurrent; -using Xunit; - -namespace DMS.Infrastructure.UnitTests -{ - public class DataCenterServiceTests - { - [Fact] - public void DataCenterService_Should_Implement_All_Required_Methods() - { - // Arrange - var mockRepositoryManager = new Mock(); - var mockDeviceAppService = new Mock(); - var mockVariableTableAppService = new Mock(); - var mockVariableAppService = new Mock(); - - // Act - var dataCenterService = new DataCenterService( - mockRepositoryManager.Object, - null, // 在测试中不会使用到mapper - mockDeviceAppService.Object, - mockVariableTableAppService.Object, - mockVariableAppService.Object); - - // Assert - Verify that service implements the interface - Assert.IsAssignableFrom(dataCenterService); - } - - [Fact] - public void DataCenterService_Should_Have_ConcurrentDictionary_Properties() - { - // Arrange - var mockRepositoryManager = new Mock(); - var mockDeviceAppService = new Mock(); - var mockVariableTableAppService = new Mock(); - var mockVariableAppService = new Mock(); - - // Act - var dataCenterService = new DataCenterService( - mockRepositoryManager.Object, - null, // 在测试中不会使用到mapper - mockDeviceAppService.Object, - mockVariableTableAppService.Object, - mockVariableAppService.Object); - - // Assert - Assert.NotNull(dataCenterService.Devices); - Assert.NotNull(dataCenterService.VariableTables); - Assert.NotNull(dataCenterService.Variables); - Assert.IsType>(dataCenterService.Devices); - Assert.IsType>(dataCenterService.VariableTables); - Assert.IsType>(dataCenterService.Variables); - } - - [Fact] - public void DataCenterService_AddDeviceToMemory_Should_Add_Device_To_Dictionary() - { - // Arrange - var mockRepositoryManager = new Mock(); - var mockMapper = new Mock(); - var mockDeviceAppService = new Mock(); - var mockVariableTableAppService = new Mock(); - var mockVariableAppService = new Mock(); - var dataCenterService = new DataCenterService( - mockRepositoryManager.Object, - mockMapper.Object, - mockDeviceAppService.Object, - mockVariableTableAppService.Object, - mockVariableAppService.Object); - - var deviceDto = new DeviceDto { Id = 1, Name = "Test Device" }; - - // Act - dataCenterService.AddDeviceToMemory(deviceDto); - - // Assert - Assert.True(dataCenterService.Devices.ContainsKey(1)); - Assert.Equal(deviceDto, dataCenterService.Devices[1]); - } - - [Fact] - public void DataCenterService_UpdateDeviceInMemory_Should_Update_Device_In_Dictionary() - { - // Arrange - var mockRepositoryManager = new Mock(); - var mockMapper = new Mock(); - var mockDeviceAppService = new Mock(); - var mockVariableTableAppService = new Mock(); - var mockVariableAppService = new Mock(); - var dataCenterService = new DataCenterService( - mockRepositoryManager.Object, - mockMapper.Object, - mockDeviceAppService.Object, - mockVariableTableAppService.Object, - mockVariableAppService.Object); - - var deviceDto = new DeviceDto { Id = 1, Name = "Test Device" }; - var updatedDeviceDto = new DeviceDto { Id = 1, Name = "Updated Device" }; - - // Act - dataCenterService.AddDeviceToMemory(deviceDto); - dataCenterService.UpdateDeviceInMemory(updatedDeviceDto); - - // Assert - Assert.True(dataCenterService.Devices.ContainsKey(1)); - Assert.Equal(updatedDeviceDto, dataCenterService.Devices[1]); - Assert.Equal("Updated Device", dataCenterService.Devices[1].Name); - } - - [Fact] - public void DataCenterService_RemoveDeviceFromMemory_Should_Remove_Device_From_Dictionary() - { - // Arrange - var mockRepositoryManager = new Mock(); - var mockMapper = new Mock(); - var mockDeviceAppService = new Mock(); - var mockVariableTableAppService = new Mock(); - var mockVariableAppService = new Mock(); - var dataCenterService = new DataCenterService( - mockRepositoryManager.Object, - mockMapper.Object, - mockDeviceAppService.Object, - mockVariableTableAppService.Object, - mockVariableAppService.Object); - - var deviceDto = new DeviceDto { Id = 1, Name = "Test Device" }; - - // Act - dataCenterService.AddDeviceToMemory(deviceDto); - dataCenterService.RemoveDeviceFromMemory(1); - - // Assert - Assert.False(dataCenterService.Devices.ContainsKey(1)); - } - - [Fact] - public void DataCenterService_Should_Raise_DeviceChanged_Event_On_Add() - { - // Arrange - var mockRepositoryManager = new Mock(); - var mockMapper = new Mock(); - var mockDeviceAppService = new Mock(); - var mockVariableTableAppService = new Mock(); - var mockVariableAppService = new Mock(); - var dataCenterService = new DataCenterService( - mockRepositoryManager.Object, - mockMapper.Object, - mockDeviceAppService.Object, - mockVariableTableAppService.Object, - mockVariableAppService.Object); - - DeviceChangedEventArgs eventArgs = null; - dataCenterService.DeviceChanged += (sender, args) => eventArgs = args; - - var deviceDto = new DeviceDto { Id = 1, Name = "Test Device" }; - - // Act - dataCenterService.AddDeviceToMemory(deviceDto); - - // Assert - Assert.NotNull(eventArgs); - Assert.Equal(DataChangeType.Added, eventArgs.ChangeType); - Assert.Equal(deviceDto, eventArgs.Device); - } - } -} \ No newline at end of file diff --git a/DMS.Infrastructure.UnitTests/Services/OpcUaServiceTest.cs b/DMS.Infrastructure.UnitTests/Services/OpcUaServiceTest.cs deleted file mode 100644 index 603b779..0000000 --- a/DMS.Infrastructure.UnitTests/Services/OpcUaServiceTest.cs +++ /dev/null @@ -1,159 +0,0 @@ -using DMS.Infrastructure.Interfaces.Services; -using DMS.Infrastructure.Services; -using Opc.Ua; -using System; -using System.Threading.Tasks; -using Xunit; - -namespace DMS.Infrastructure.UnitTests.Services -{ - public class OpcUaServiceTest - { - [Fact] - public async Task TestOpcUaService_CreateSession_WithValidUrl_ShouldCreateSession() - { - // Arrange - var service = new OpcUaService(); - var opcUaServerUrl = "opc.tcp://localhost:4840"; // 示例URL,实际测试时需要真实的OPC UA服务器 - - // Act & Assert - // 注意:这个测试需要真实的OPC UA服务器才能通过 - // 在实际测试环境中,您需要启动一个OPC UA服务器 - try - { - await service.CreateSession(opcUaServerUrl); - // 如果没有异常,则认为会话创建成功 - Assert.True(true); - } - catch (Exception ex) - { - // 在没有真实服务器的情况下,我们期望出现连接异常 - Assert.NotNull(ex); - } - } - - [Fact] - public async Task TestOpcUaService_CreateSession_WithNullUrl_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - string opcUaServerUrl = null; - - // Act & Assert - await Assert.ThrowsAsync(async () => - { - await service.CreateSession(opcUaServerUrl); - }); - } - - [Fact] - public async Task TestOpcUaService_CreateSession_WithEmptyUrl_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - var opcUaServerUrl = ""; - - // Act & Assert - await Assert.ThrowsAsync(async () => - { - await service.CreateSession(opcUaServerUrl); - }); - } - - [Fact] - public void TestOpcUaService_IsConnected_WithoutSession_ShouldReturnFalse() - { - // Arrange - var service = new OpcUaService(); - - // Act - var isConnected = service.IsConnected(); - - // Assert - Assert.False(isConnected); - } - - [Fact] - public async Task TestOpcUaService_ConnectAsync_WithoutSession_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - - // Act & Assert - await Assert.ThrowsAsync(async () => - { - await service.ConnectAsync("opc.tcp://localhost:4840"); - }); - } - - [Fact] - public void TestOpcUaService_Connect_WithoutSession_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - - // Act & Assert - Assert.Throws(() => - { - service.Connect(); - }); - } - - [Fact] - public void TestOpcUaService_AddSubscription_WithoutSession_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - var subscriptionName = "TestSubscription"; - - // Act & Assert - Assert.Throws(() => - { - service.AddSubscription(subscriptionName); - }); - } - - [Fact] - public void TestOpcUaService_BrowseNodes_WithoutSession_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - var nodeId = NodeId.Null; - - // Act & Assert - Assert.Throws(() => - { - service.BrowseNodes(nodeId); - }); - } - - [Fact] - public void TestOpcUaService_ReadValue_WithoutSession_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - var nodeId = NodeId.Null; - - // Act & Assert - Assert.Throws(() => - { - service.ReadValue(nodeId); - }); - } - - [Fact] - public void TestOpcUaService_WriteValue_WithoutSession_ShouldThrowException() - { - // Arrange - var service = new OpcUaService(); - var nodeId = NodeId.Null; - var value = "test"; - - // Act & Assert - Assert.Throws(() => - { - service.WriteValue(nodeId, value); - }); - } - } -} \ No newline at end of file diff --git a/DMS.WPF/App.xaml.cs b/DMS.WPF/App.xaml.cs index 1bceb16..d4b0e37 100644 --- a/DMS.WPF/App.xaml.cs +++ b/DMS.WPF/App.xaml.cs @@ -167,9 +167,9 @@ public partial class App : System.Windows.Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); //注册WPF中的服务 services.AddSingleton(); diff --git a/DMS.WPF/ViewModels/Items/MenuItemViewModel.cs b/DMS.WPF/ViewModels/Items/MenuItemViewModel.cs index 42ee7e6..e673088 100644 --- a/DMS.WPF/ViewModels/Items/MenuItemViewModel.cs +++ b/DMS.WPF/ViewModels/Items/MenuItemViewModel.cs @@ -8,34 +8,74 @@ using DMS.WPF.Services; namespace DMS.WPF.ViewModels.Items; +/// +/// 菜单项视图模型 +/// 用于在WPF界面中绑定和显示菜单项数据,实现MVVM模式 +/// 继承自ObservableObject以支持属性更改通知 +/// public partial class MenuItemViewModel : ObservableObject { + /// + /// 菜单项的唯一标识符 + /// public int Id { get; set; } + /// + /// 父级菜单项的ID,用于构建层级菜单结构 + /// 如果为null表示为顶级菜单项 + /// [ObservableProperty] private int? _parentId; + /// + /// 菜单项显示的标题文本 + /// [ObservableProperty] private string _header; + /// + /// 菜单项显示的图标资源路径或标识符 + /// [ObservableProperty] private string _icon; + /// + /// 菜单的类型,例如菜单关联的是设备,还是变量表,或者是MQTT + /// 用于区分不同类型的菜单项,决定点击菜单项时的行为 + /// [ObservableProperty] private MenuType _menuType; + /// + /// 菜单关联的数据ID,例如设备Id,变量表Id + /// 根据MenuType的不同,此ID可以指向不同的数据实体 + /// [ObservableProperty] private int _targetId; + + /// + /// 目标视图的键值,用于导航到特定的视图页面 + /// [ObservableProperty] private string _targetViewKey; + /// + /// 导航参数,传递给目标视图的额外参数信息 + /// [ObservableProperty] private string _navigationParameter; + /// + /// 菜单项在同级菜单中的显示顺序 + /// 数值越小显示越靠前 + /// [ObservableProperty] private int _displayOrder; - [ObservableProperty] - private ObservableCollection _children=new (); - -} + /// + /// 子菜单项集合,用于构建层级菜单结构 + /// 使用ObservableCollection以支持动态添加和删除子项并自动更新UI + /// + [ObservableProperty] + private ObservableCollection _children = new(); +} \ No newline at end of file