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