wenxiangchao
3 天以前 b544d5cccd76735e034c8055918a50927b437474
Plugin.DeviceConfig/DeviceEditModal.xaml.cs
@@ -3,6 +3,7 @@
using Plugin.DeviceConfig.Entity;
using Plugin.DeviceConfig.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
@@ -19,6 +20,7 @@
        private DeviceDefinition _device;
        private ObservableCollection<DeviceChannel> _channels = new();
        private bool _isNew;
        private bool _execModeSyncing;
        public DeviceEditModal()
        {
@@ -40,6 +42,11 @@
            cbTransport.ItemsSource = Enum.GetValues(typeof(TransportType)).Cast<TransportType>().ToList();
            cbTransport.SelectionChanged += CbTransport_SelectionChanged;
            // 执行位置:仲裁域/BrokerId 候选由配置枚举
            cbAuthority.ItemsSource = V3AuthorityCatalog.AuthorityIds;
            cbBrokerId.ItemsSource = V3AuthorityCatalog.BrokerIds;
            tbOwnerProcessId.Text = System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
            // 填充数据
            tbDeviceId.Text = device.DeviceId;
            tbDeviceId.IsReadOnly = !_isNew;
@@ -47,6 +54,29 @@
            tbDriverType.Text = device.DriverType;
            cbTransport.SelectedItem = device.Transport;
            tbEndpoint.Text = device.Endpoint;
            // 执行位置回显:旧数据 AuthorityId 为空时默认本机 Local 域
            _execModeSyncing = true;
            rbLocal.IsChecked = device.ExecutionMode == ExecutionModeKind.Local;
            rbBroker.IsChecked = device.ExecutionMode == ExecutionModeKind.Broker;
            cbAuthority.SelectedItem = string.IsNullOrWhiteSpace(device.AuthorityId)
                ? V3AuthorityCatalog.LocalAuthorityId : device.AuthorityId;
            if (cbAuthority.SelectedItem == null)
            {
                // 存量值不在当前候选枚举中:保留展示并追加
                var list = V3AuthorityCatalog.AuthorityIds.ToList();
                list.Add(device.AuthorityId);
                cbAuthority.ItemsSource = list;
                cbAuthority.SelectedItem = device.AuthorityId;
            }
            cbBrokerId.SelectedItem = device.BrokerId;
            if (!string.IsNullOrWhiteSpace(device.OwnerProcessId))
            {
                tbOwnerProcessId.Text = device.OwnerProcessId;
            }
            _execModeSyncing = false;
            RefreshExecModeUi();
            RefreshSessionKeyCandidates();
            // 默认驱动类型建议 & 端点提示
            UpdateDriverHint();
@@ -63,6 +93,9 @@
                _channels = new ObservableCollection<DeviceChannel>();
            }
            channelListView.ItemsSource = _channels;
            // 通道 SessionKey 已加载,重新生成会话键候选(Broker 模式)
            RefreshSessionKeyCandidates();
            return ShowDialog();
        }
@@ -211,6 +244,120 @@
        #endregion
        #region 执行位置(V3.0 §05)
        /// <summary>当前是否 Broker 模式</summary>
        private bool IsBrokerMode => rbBroker.IsChecked == true;
        private void ExecMode_Changed(object sender, RoutedEventArgs e)
        {
            // XAML 初始化阶段 rbLocal.IsChecked=True 会提前触发,此时其余控件字段尚未创建
            if (_execModeSyncing || rbBroker == null || cbAuthority == null) return;
            _execModeSyncing = true;
            // Broker 设备的 AuthorityId 即其 Broker 域;切回 Local 时使用本机默认域
            if (IsBrokerMode)
            {
                // 当前仍为 local 域(或未选)→ 切到首个 Broker 域
                if (V3AuthorityCatalog.IsLocalAuthority(cbAuthority.SelectedItem as string) || cbAuthority.SelectedItem == null)
                {
                    cbAuthority.SelectedItem = V3AuthorityCatalog.BrokerIds.FirstOrDefault()
                        ?? V3AuthorityCatalog.AuthorityIds.LastOrDefault(a => !V3AuthorityCatalog.IsLocalAuthority(a));
                }
            }
            else
            {
                cbAuthority.SelectedItem = V3AuthorityCatalog.LocalAuthorityId;
            }
            _execModeSyncing = false;
            RefreshExecModeUi();
        }
        private void CbAuthority_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (_execModeSyncing || rbLocal == null || rbBroker == null || cbBrokerId == null) return;
            var auth = cbAuthority.SelectedItem as string;
            if (string.IsNullOrEmpty(auth)) return;
            _execModeSyncing = true;
            // 选中 local- 域→Local;选中 Broker 域→Broker 并同步 BrokerId
            if (V3AuthorityCatalog.IsLocalAuthority(auth))
            {
                rbLocal.IsChecked = true;
            }
            else
            {
                rbBroker.IsChecked = true;
                cbBrokerId.SelectedItem = V3AuthorityCatalog.BrokerIds.Contains(auth) ? auth : cbBrokerId.SelectedItem;
            }
            _execModeSyncing = false;
            RefreshExecModeUi();
        }
        /// <summary>根据模式切换 Broker 字段可见性与只读派生的访问策略</summary>
        private void RefreshExecModeUi()
        {
            spBroker.Visibility = IsBrokerMode ? Visibility.Visible : Visibility.Collapsed;
            tbAccessPolicy.Text = (IsBrokerMode ? ExecutionModeKind.Broker : ExecutionModeKind.Local)
                switch
                {
                    ExecutionModeKind.Broker => "共享仲裁 · Broker SessionGate 代理",
                    _ => "独占直连 · 本进程持有物理会话"
                };
            if (IsBrokerMode)
            {
                RefreshSessionKeyCandidates();
            }
        }
        /// <summary>
        /// PhysicalSessionKey 候选:由端点规范化的写死字符串(V3.0 §04:地址别名必须归一为同一物理身份)
        /// </summary>
        private void RefreshSessionKeyCandidates()
        {
            var deviceId = tbDeviceId.Text?.Trim() ?? string.Empty;
            var transport = cbTransport.SelectedItem is TransportType tt ? tt : TransportType.Tcp;
            var endpoint = tbEndpoint.Text?.Trim() ?? string.Empty;
            var candidates = new List<string>();
            if (transport == TransportType.Can)
            {
                var bus = endpoint.Split(':')[0].Trim();
                if (!string.IsNullOrEmpty(bus)) candidates.Add($"can/{bus}/main");
            }
            else if (transport == TransportType.Tcp && endpoint.Contains(":"))
            {
                candidates.Add($"tcp/{endpoint}/main");
            }
            else if (transport == TransportType.Serial)
            {
                var com = endpoint.Split(':')[0].Trim();
                if (!string.IsNullOrEmpty(com)) candidates.Add($"serial/{com}/main");
            }
            else if (transport == TransportType.Visa)
            {
                candidates.Add($"visa/{deviceId}/main");
            }
            else if (transport == TransportType.SDK)
            {
                candidates.Add($"sdk/{deviceId}/main");
            }
            if (!string.IsNullOrEmpty(deviceId)) candidates.Add($"{deviceId}/main");
            // 已存通道 SessionKey 也列入候选(去重)
            foreach (var key in _channels.Select(c => c.SessionKey)
                         .Where(k => !string.IsNullOrWhiteSpace(k)).Distinct())
            {
                candidates.Add(key);
            }
            candidates = candidates.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
            string? current = _device?.PhysicalSessionKey;
            cbSessionKey.ItemsSource = candidates;
            cbSessionKey.Text = !string.IsNullOrWhiteSpace(current) ? current
                : (candidates.Count > 0 ? candidates[0] : string.Empty);
        }
        #endregion
        #region 保存/取消
        private void SaveBtn_Click(object sender, RoutedEventArgs e)
@@ -275,12 +422,42 @@
                    }
                }
                // 执行位置(V3.0 §05)校验
                var authorityId = cbAuthority.SelectedItem as string ?? string.Empty;
                if (string.IsNullOrWhiteSpace(authorityId))
                {
                    ToastHelper.Warning("仲裁域不能为空,请从候选中选择");
                    return;
                }
                string brokerId = string.Empty, sessionKey = string.Empty, ownerPid = string.Empty;
                if (IsBrokerMode)
                {
                    brokerId = cbBrokerId.SelectedItem as string ?? string.Empty;
                    if (string.IsNullOrWhiteSpace(brokerId))
                    {
                        ToastHelper.Warning("共享仪器(Broker)必须指定 BrokerId");
                        return;
                    }
                    sessionKey = cbSessionKey.Text?.Trim() ?? string.Empty;
                    if (string.IsNullOrWhiteSpace(sessionKey))
                    {
                        ToastHelper.Warning("共享仪器(Broker)必须指定物理会话键");
                        return;
                    }
                    ownerPid = tbOwnerProcessId.Text?.Trim() ?? string.Empty;
                }
                // 保存设备
                _device.DeviceId = deviceId;
                _device.Kind = kind;
                _device.DriverType = tbDriverType.Text?.Trim() ?? string.Empty;
                _device.Transport = transport;
                _device.Endpoint = endpoint;
                _device.ExecutionMode = IsBrokerMode ? ExecutionModeKind.Broker : ExecutionModeKind.Local;
                _device.AuthorityId = authorityId;
                _device.BrokerId = brokerId;
                _device.PhysicalSessionKey = sessionKey;
                _device.OwnerProcessId = ownerPid;
                _model.SaveDevice(_device);
@@ -309,6 +486,10 @@
        private void CbTransport_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            UpdateEndpointHint();
            if (IsBrokerMode)
            {
                RefreshSessionKeyCandidates();
            }
        }
        /// <summary>