From 9d1660270272180cd71147d6ddd53d1382643d2e Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 02 十一月 2026 18:00:14 +0800
Subject: [PATCH] merge: 并入远程 Initial commit 作为历史根节点
---
Plugin.DeviceConfig/DeviceConfig.xaml.cs | 611 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 611 insertions(+), 0 deletions(-)
diff --git a/Plugin.DeviceConfig/DeviceConfig.xaml.cs b/Plugin.DeviceConfig/DeviceConfig.xaml.cs
new file mode 100644
index 0000000..e4d3357
--- /dev/null
+++ b/Plugin.DeviceConfig/DeviceConfig.xaml.cs
@@ -0,0 +1,611 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using HMI.Base;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using Plugin.DeviceConfig.Entity;
+using Plugin.DeviceConfig.Model;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+
+namespace Plugin.DeviceConfig
+{
+ /// <summary>
+ /// DeviceConfig.xaml 鐨勪氦浜掗�昏緫
+ /// </summary>
+ [ObservableObject]
+ public partial class DeviceConfig : SimpleMaskControl
+ {
+ private DeviceConfigModel _model;
+
+ [ObservableProperty]
+ private ObservableCollection<DeviceDefinition> devices = new();
+
+ [ObservableProperty]
+ private ObservableCollection<DeviceChannel> channels = new();
+
+ [ObservableProperty]
+ private ObservableCollection<SlotDeviceMapping> slotMappings = new();
+
+ [ObservableProperty]
+ private DeviceDefinition selectedDevice;
+
+ [ObservableProperty]
+ private string currentVersionStatus = "Draft";
+
+ [ObservableProperty]
+ private ObservableCollection<TreeNode> treeCategories = new();
+
+ public DeviceConfig()
+ {
+ DataContext = this;
+ InitializeComponent();
+ }
+
+ /// <summary>
+ /// 寮哄埗鍒锋柊鏁版嵁锛堜緵闆嗘垚娴嬭瘯棰勭疆璁惧鍚庤皟鐢級
+ /// </summary>
+ public void ForceRefreshForTest()
+ {
+ try { RefreshAllData(); }
+ catch (Exception ex) { log.Error("ForceRefreshForTest Error.", ex); }
+ }
+
+ public override void InitFinish()
+ {
+ base.InitFinish();
+ try
+ {
+ _model = new DeviceConfigModel();
+ var dbPath = Path.Combine(BaseFolder, "data.db");
+ _model.InitDatabase(dbPath);
+ LoadTreeCategories();
+ RefreshAllData();
+ }
+ catch (Exception ex)
+ {
+ log.Error("InitFinish Error.", ex);
+ ToastHelper.Error($"鍒濆鍖栧け璐�: {ex.Message}");
+ }
+ }
+
+ #region 鏍戝垎绫诲姞杞�
+
+ private void LoadTreeCategories()
+ {
+ TreeCategories = new ObservableCollection<TreeNode>
+ {
+ new TreeNode { Name = "鍏ㄩ儴璁惧", Kind = null, Children = new ObservableCollection<TreeNode>() },
+ new TreeNode { Name = "鐢垫簮绯荤粺", Kind = DeviceKind.PowerSupply, Children = new ObservableCollection<TreeNode>() },
+ new TreeNode { Name = "CAN/LIN", Kind = DeviceKind.CanDevice, Children = new ObservableCollection<TreeNode>() },
+ new TreeNode { Name = "PLC/IO", Kind = DeviceKind.PlcDevice, Children = new ObservableCollection<TreeNode>() },
+ new TreeNode { Name = "娓╃/DAQ", Kind = null, Children = new ObservableCollection<TreeNode>(),
+ FilterKinds = new[] { DeviceKind.Chamber, DeviceKind.DAQ } },
+ };
+ deviceTree.ItemsSource = TreeCategories;
+ }
+
+ #endregion
+
+ #region 鏁版嵁鍔犺浇
+
+ private void RefreshAllData()
+ {
+ try
+ {
+ var deviceList = _model.GetAllDevices();
+ Devices = new ObservableCollection<DeviceDefinition>(deviceList);
+ SlotMappings = new ObservableCollection<SlotDeviceMapping>(_model.GetSlotMappings());
+ RefreshTreeDevices();
+ LoadLatestVersionStatus();
+ }
+ catch (Exception ex)
+ {
+ log.Error("RefreshAllData Error.", ex);
+ ToastHelper.Error($"鍔犺浇鏁版嵁澶辫触: {ex.Message}");
+ }
+ }
+
+ private void RefreshTreeDevices()
+ {
+ foreach (var cat in TreeCategories)
+ {
+ cat.Children.Clear();
+ IEnumerable<DeviceDefinition> filtered = Devices;
+ if (cat.Kind.HasValue)
+ {
+ filtered = Devices.Where(d => d.Kind == cat.Kind.Value);
+ }
+ else if (cat.FilterKinds != null)
+ {
+ filtered = Devices.Where(d => cat.FilterKinds.Contains(d.Kind));
+ }
+ foreach (var dev in filtered)
+ {
+ cat.Children.Add(new TreeNode { Name = $"{dev.DeviceId} ({dev.Endpoint})", Tag = dev });
+ }
+ }
+ }
+
+ private void LoadLatestVersionStatus()
+ {
+ try
+ {
+ var versions = _model.GetConfigVersions();
+ var effective = versions.FirstOrDefault(v => v.Status == VersionStatus.Effective);
+ if (effective != null)
+ {
+ CurrentVersionStatus = "Effective";
+ }
+ else if (versions.Any(v => v.Status == VersionStatus.Published))
+ {
+ CurrentVersionStatus = "Published";
+ }
+ else
+ {
+ CurrentVersionStatus = "Draft";
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("LoadLatestVersionStatus Error.", ex);
+ }
+ }
+
+ #endregion
+
+ #region 浜嬩欢澶勭悊
+
+ private void SimpleMaskControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ // InitFinish 宸插姞杞芥暟鎹紝姝ゅ浠呭仛涓�娆℃�� UI 鍒濆鍖�
+ Loaded -= SimpleMaskControl_Loaded;
+ }
+
+ private void DeviceTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
+ {
+ if (e.NewValue is TreeNode node && node.Tag is DeviceDefinition dev)
+ {
+ SelectDevice(dev);
+ }
+ }
+
+ private void SearchBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ try
+ {
+ var keyword = searchBox.Text?.Trim() ?? string.Empty;
+ if (string.IsNullOrEmpty(keyword))
+ {
+ RefreshTreeDevices();
+ return;
+ }
+ foreach (var cat in TreeCategories)
+ {
+ cat.Children.Clear();
+ var matched = Devices.Where(d =>
+ d.DeviceId.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0 ||
+ d.Endpoint.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0 ||
+ d.DriverType.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0);
+ if (cat.Kind.HasValue)
+ matched = matched.Where(d => d.Kind == cat.Kind.Value);
+ else if (cat.FilterKinds != null)
+ matched = matched.Where(d => cat.FilterKinds.Contains(d.Kind));
+
+ foreach (var dev in matched)
+ cat.Children.Add(new TreeNode { Name = $"{dev.DeviceId} ({dev.Endpoint})", Tag = dev });
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("SearchBox_TextChanged Error.", ex);
+ }
+ }
+
+ private void SelectDevice(DeviceDefinition dev)
+ {
+ try
+ {
+ SelectedDevice = dev;
+ tbDeviceId.Text = dev.DeviceId;
+ tbKind.Text = dev.Kind.ToString();
+ tbDriverType.Text = dev.DriverType;
+ tbTransport.Text = dev.Transport.ToString();
+ tbEndpoint.Text = dev.Endpoint;
+ tbEffectiveVersion.Text = dev.EffectiveVersion;
+
+ var chList = _model.GetChannelsByDevice(dev.DeviceId);
+ Channels = new ObservableCollection<DeviceChannel>(chList);
+ }
+ catch (Exception ex)
+ {
+ log.Error("SelectDevice Error.", ex);
+ ToastHelper.Error($"鍔犺浇璁惧璇︽儏澶辫触: {ex.Message}");
+ }
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ Button btn = sender as Button;
+ try
+ {
+ switch (btn?.Name)
+ {
+ case "addBtn":
+ OpenDeviceEditModal(null);
+ break;
+ case "editBtn":
+ EditSelectedDevice();
+ break;
+ case "delBtn":
+ DeleteSelectedDevice();
+ break;
+ case "batchBtn":
+ OpenBatchGenerateModal();
+ break;
+ case "importBtn":
+ ImportDevices();
+ break;
+ case "exportBtn":
+ ExportDevices();
+ break;
+ case "versionBtn":
+ OpenVersionManageModal();
+ break;
+ case "rfhBtn":
+ RefreshAllData();
+ ToastHelper.Success("鍒锋柊鎴愬姛");
+ break;
+ case "saveDraftBtn":
+ SaveDraft();
+ break;
+ case "publishBtn":
+ PublishVersion();
+ break;
+ case "applyBtn":
+ ApplyVersion();
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("Button_Click Error.", ex);
+ ToastHelper.Error($"鎿嶄綔澶辫触: {ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region 寮圭獥
+
+ private void OpenDeviceEditModal(DeviceDefinition device)
+ {
+ try
+ {
+ device ??= new DeviceDefinition();
+ var win = new DeviceEditModal
+ {
+ Title = string.IsNullOrEmpty(device.DeviceId) ? "鏂板璁惧" : "缂栬緫璁惧",
+ Owner = Window.GetWindow(this)
+ };
+ if (win.Open(device, _model) == true)
+ {
+ RefreshAllData();
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("OpenDeviceEditModal Error.", ex);
+ ToastHelper.Error($"鎵撳紑缂栬緫寮圭獥澶辫触: {ex.Message}");
+ }
+ }
+
+ /// <summary>
+ /// 缂栬緫褰撳墠閫変腑鐨勮澶�
+ /// </summary>
+ private void EditSelectedDevice()
+ {
+ try
+ {
+ if (SelectedDevice == null)
+ {
+ ToastHelper.Warning("璇峰厛鍦ㄥ乏渚ц澶囨爲涓�夋嫨涓�涓澶�");
+ return;
+ }
+
+ // 浣跨敤褰撳墠璁惧淇℃伅鐨勫壇鏈伩鍏嶇洿鎺ヤ慨鏀瑰唴閮ㄦ暟鎹�
+ var editingDevice = new DeviceDefinition
+ {
+ DeviceId = SelectedDevice.DeviceId,
+ Kind = SelectedDevice.Kind,
+ DriverType = SelectedDevice.DriverType,
+ Transport = SelectedDevice.Transport,
+ Endpoint = SelectedDevice.Endpoint,
+ EffectiveVersion = SelectedDevice.EffectiveVersion,
+ CreatedAt = SelectedDevice.CreatedAt,
+ UpdatedAt = SelectedDevice.UpdatedAt
+ };
+
+ var win = new DeviceEditModal
+ {
+ Title = $"缂栬緫璁惧 - {SelectedDevice.DeviceId}",
+ Owner = Window.GetWindow(this)
+ };
+ if (win.Open(editingDevice, _model) == true)
+ {
+ RefreshAllData();
+ ToastHelper.Success($"璁惧 {editingDevice.DeviceId} 宸叉洿鏂�");
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("EditSelectedDevice Error.", ex);
+ ToastHelper.Error($"缂栬緫璁惧澶辫触: {ex.Message}");
+ }
+ }
+
+ /// <summary>
+ /// 鍒犻櫎褰撳墠閫変腑鐨勮澶囷紙鍚屾椂娓呯悊鐩稿叧閫氶亾涓� Slot 鏄犲皠锛�
+ /// </summary>
+ private void DeleteSelectedDevice()
+ {
+ try
+ {
+ if (SelectedDevice == null)
+ {
+ ToastHelper.Warning("璇峰厛鍦ㄥ乏渚ц澶囨爲涓�夋嫨涓�涓澶�");
+ return;
+ }
+
+ var deviceId = SelectedDevice.DeviceId;
+ if (MsgBoxHelper.WarnMessage(Window.GetWindow(this),
+ $"纭畾瑕佸垹闄よ澶� [{deviceId}] 鍚楋紵\n璇ユ搷浣滃皢鍚屾椂鍒犻櫎鐩稿叧鐨勯�氶亾涓� Slot 鏄犲皠锛屼笖涓嶅彲鎭㈠銆�") != MessageBoxResult.OK)
+ {
+ return;
+ }
+
+ // 鍏堝垹闄ゅ叧鑱旂殑閫氶亾涓� Slot 鏄犲皠
+ var channels = _model.GetChannelsByDevice(deviceId);
+ foreach (var ch in channels)
+ {
+ _model.DeleteChannel(deviceId, ch.ChannelId);
+ }
+
+ var mappings = _model.GetSlotMappings();
+ foreach (var m in mappings.Where(x => x.DeviceId == deviceId))
+ {
+ _model.DeleteSlotMapping(m.SlotId, m.Capability, m.Version);
+ }
+
+ // 鍒犻櫎璁惧鏈韩
+ _model.DeleteDevice(deviceId);
+
+ SelectedDevice = null;
+ ClearDeviceDetails();
+ RefreshAllData();
+ ToastHelper.Success($"璁惧 {deviceId} 宸插垹闄�");
+ }
+ catch (Exception ex)
+ {
+ log.Error("DeleteSelectedDevice Error.", ex);
+ ToastHelper.Error($"鍒犻櫎璁惧澶辫触: {ex.Message}");
+ }
+ }
+
+ /// <summary>
+ /// 娓呯┖涓爮璁惧璇︽儏灞曠ず
+ /// </summary>
+ private void ClearDeviceDetails()
+ {
+ tbDeviceId.Text = string.Empty;
+ tbKind.Text = string.Empty;
+ tbDriverType.Text = string.Empty;
+ tbTransport.Text = string.Empty;
+ tbEndpoint.Text = string.Empty;
+ tbEffectiveVersion.Text = string.Empty;
+ Channels = new ObservableCollection<DeviceChannel>();
+ }
+
+ private void OpenBatchGenerateModal()
+ {
+ try
+ {
+ var win = new BatchGenerateModal
+ {
+ Title = "鎵归噺鐢熸垚璁惧",
+ Owner = Window.GetWindow(this)
+ };
+ if (win.Open(_model) == true)
+ {
+ RefreshAllData();
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("OpenBatchGenerateModal Error.", ex);
+ ToastHelper.Error($"鎵撳紑鎵归噺鐢熸垚寮圭獥澶辫触: {ex.Message}");
+ }
+ }
+
+ private void OpenVersionManageModal()
+ {
+ try
+ {
+ var win = new VersionManageModal
+ {
+ Title = "鐗堟湰绠$悊",
+ Owner = Window.GetWindow(this)
+ };
+ if (win.Open(_model) == true)
+ {
+ RefreshAllData();
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("OpenVersionManageModal Error.", ex);
+ ToastHelper.Error($"鎵撳紑鐗堟湰绠$悊寮圭獥澶辫触: {ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region 鐗堟湰鎿嶄綔
+
+ private void SaveDraft()
+ {
+ try
+ {
+ var versionId = $"V{DateTime.Now:yyyyMMddHHmmss}";
+ var hash = _model.ComputeConfigHash();
+ var ver = new DeviceConfigVersion
+ {
+ VersionId = versionId,
+ Status = VersionStatus.Draft,
+ Hash = hash,
+ Author = Environment.UserName,
+ Timestamp = DateTime.Now,
+ DiffDescription = "淇濆瓨鑽夌"
+ };
+ _model.SaveConfigVersion(ver);
+ CurrentVersionStatus = "Draft";
+ ToastHelper.Success($"鑽夌宸蹭繚瀛�: {versionId}");
+ }
+ catch (Exception ex)
+ {
+ log.Error("SaveDraft Error.", ex);
+ ToastHelper.Error($"淇濆瓨鑽夌澶辫触: {ex.Message}");
+ }
+ }
+
+ private void PublishVersion()
+ {
+ try
+ {
+ var versions = _model.GetConfigVersions();
+ var draft = versions.FirstOrDefault(v => v.Status == VersionStatus.Draft);
+ if (draft == null)
+ {
+ ToastHelper.Warning("娌℃湁鍙彂甯冪殑鑽夌鐗堟湰");
+ return;
+ }
+ var hash = _model.ComputeConfigHash();
+ draft.Hash = hash;
+ draft.Status = VersionStatus.Published;
+ draft.Timestamp = DateTime.Now;
+ _model.SaveConfigVersion(draft);
+ CurrentVersionStatus = "Published";
+ ToastHelper.Success($"鐗堟湰 {draft.VersionId} 宸插彂甯�");
+ }
+ catch (Exception ex)
+ {
+ log.Error("PublishVersion Error.", ex);
+ ToastHelper.Error($"鍙戝竷澶辫触: {ex.Message}");
+ }
+ }
+
+ private void ApplyVersion()
+ {
+ try
+ {
+ var versions = _model.GetConfigVersions();
+ var published = versions.FirstOrDefault(v => v.Status == VersionStatus.Published);
+ if (published == null)
+ {
+ ToastHelper.Warning("娌℃湁鍙簲鐢ㄧ殑宸插彂甯冪増鏈�");
+ return;
+ }
+
+ var effective = versions.FirstOrDefault(v => v.Status == VersionStatus.Effective);
+ if (effective != null)
+ {
+ if (MsgBoxHelper.WarnMessage(Window.GetWindow(this),
+ $"褰撳墠宸叉湁鐢熸晥鐗堟湰 [{effective.VersionId}]锛屽簲鐢ㄦ柊鐗堟湰灏嗘浛鎹㈠畠锛屾槸鍚︾户缁紵") != MessageBoxResult.OK)
+ {
+ return;
+ }
+ effective.Status = VersionStatus.Published;
+ _model.SaveConfigVersion(effective);
+ }
+
+ published.Status = VersionStatus.Effective;
+ published.Timestamp = DateTime.Now;
+ _model.SaveConfigVersion(published);
+ CurrentVersionStatus = "Effective";
+ ToastHelper.Success($"鐗堟湰 {published.VersionId} 宸插簲鐢ㄤ负鐢熸晥鐗堟湰");
+ }
+ catch (Exception ex)
+ {
+ log.Error("ApplyVersion Error.", ex);
+ ToastHelper.Error($"搴旂敤鐗堟湰澶辫触: {ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region 瀵煎叆瀵煎嚭
+
+ private void ImportDevices()
+ {
+ try
+ {
+ var ofd = new Microsoft.Win32.OpenFileDialog
+ {
+ Filter = "XML鏂囦欢|*.xml|JSON鏂囦欢|*.json"
+ };
+ if (ofd.ShowDialog() == true)
+ {
+ // TODO: 瀹炵幇瀵煎叆閫昏緫
+ ToastHelper.Success($"宸蹭粠 {Path.GetFileName(ofd.FileName)} 瀵煎叆");
+ RefreshAllData();
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("ImportDevices Error.", ex);
+ ToastHelper.Error($"瀵煎叆澶辫触: {ex.Message}");
+ }
+ }
+
+ private void ExportDevices()
+ {
+ try
+ {
+ var sfd = new Microsoft.Win32.SaveFileDialog
+ {
+ FileName = "DeviceConfig",
+ Filter = "XML鏂囦欢|*.xml"
+ };
+ if (sfd.ShowDialog() == true)
+ {
+ // TODO: 瀹炵幇瀵煎嚭閫昏緫
+ ToastHelper.Success("瀵煎嚭鎴愬姛");
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error("ExportDevices Error.", ex);
+ ToastHelper.Error($"瀵煎嚭澶辫触: {ex.Message}");
+ }
+ }
+
+ #endregion
+ }
+
+ /// <summary>
+ /// 鏍戣妭鐐规ā鍨�
+ /// </summary>
+ public class TreeNode
+ {
+ public string Name { get; set; } = string.Empty;
+ public DeviceKind? Kind { get; set; }
+ public DeviceKind[] FilterKinds { get; set; }
+ public object Tag { get; set; }
+ public ObservableCollection<TreeNode> Children { get; set; } = new();
+ }
+}
--
Gitblit v1.9.1