Browse Source

支持多设备配置与MES多设备适配,优化界面

本次提交实现了以下主要功能和优化:

- 设备信息(DeviceInfo)由单一对象改为列表,支持多设备配置、增删查改和批量保存。
- MES相关接口(StationGetAsync、SubmitGetAsync等)增加 deviceNo 参数,支持多设备独立通讯。
- 新增双MES验证、MES服务器时间获取等高级功能。
- Management 业务逻辑适配多设备,SN/工单等信息按设备编号管理。
- SettingView 设备配置页重构,支持设备树、参数表单、增删设备、CSV路径选择等交互。
- 图片保存路径增加OK/NG子目录,便于分类。
- 兼容旧配置文件,自动迁移为多设备格式。
- 修正部分注释乱码,AssemblyInfo 版本号升级为1.2.1.4。
徐孝锋 6 months ago
parent
commit
ee29998af3

+ 122 - 45
TeamAAS-VM/Core/Management.cs

@@ -106,11 +106,7 @@ namespace TeamAAS_VP.Core
         /// 超时自动停止秒数
         /// </summary>
         private int _pressureMaxDurationSeconds = 30; //超时自动停止秒数
-        /// <summary>
-        /// 产品主sn
-        /// </summary>
-        string ProductMainSN = "";
-        string wo = "";
+        
         DateTime StartCtqTime = DateTime.Now;//开始采集Ctq数据的时间
         #endregion
 
@@ -1231,6 +1227,14 @@ namespace TeamAAS_VP.Core
         /// 零件SN
         /// </summary>
         string PartSN = "";
+        /// <summary>
+        /// 产品SN
+        /// </summary>
+        string[] ProductMainSN = new string[10];//产品主sn
+        /// <summary>
+        /// 工单信息
+        /// </summary>
+        string[] wo = new string[10];
 
         /// <summary>
         /// 二次定位相机的工具坐标
@@ -2215,63 +2219,136 @@ namespace TeamAAS_VP.Core
                                 }
                                 var code = plc.ReadNode<string>(string.Format(addressConfig.In_Code.Address, i));
                                 string comp = "";
+                                //过站请求
                                 if (state == 1)
                                 {
-                                    //这里缺少从MES获取产品配方的内容,需要补充
 
-                                    _LastMesCheckTime[i] = DateTime.UtcNow;
+                                    // 这里缺少从MES获取产品配方的内容,需要补充
 
-                                    LogHelper.WriteLogMes($"【mes开始QUERY】");
-                                    SendTaskMessage($"mes开始QUERY", MessageLevel.Info);
+                                    _LastMesCheckTime[i] = (await _mesService.GetServerTimeAsync(i, code, 2)).Now;
 
-                                    StartCtqTime = DateTime.Now;
-                                    (bool isSuccess, string response) = await _mesService.StationGetAsync(code, comp, 2);
-                                    if (i == 0)
+                                    //获取当前工位的配置
+                                    var stationConfig = _configService.GetDeviceInfo(i);
+                                    if (stationConfig.EnableDoubleMes)
                                     {
-                                        SendTaskMessage($"收到当前过站检查请求(当站是否生产)...", MessageLevel.Debug);
-                                    }
-                                    else
-                                    {
-                                        SendTaskMessage($"收到{i}过站检查请求(当站是否生产)...", MessageLevel.Debug);
-                                    }
-                                    if (isSuccess)
-                                    {
-                                        //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
-                                        await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
-                                        await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1);
+                                        SendTaskMessage($"收到{i}:{stationConfig.DeviceName}过站检查请求(当站是否生产)(当前需要双MES验证)...", MessageLevel.Debug);
 
-                                        LogHelper.WriteLogMes($"询问mes成功");
-                                        SendTaskMessage($"询问mes成功", MessageLevel.Debug);
-                                        if (response != "MES功能未启用!")
+                                        LogHelper.WriteLogMes($"【mes开始QUERY】");
+                                        SendTaskMessage($"mes开始QUERY", MessageLevel.Info);
+                                        (bool isSuccess, string response) = await _mesService.StationGetExAsync(i, code, "", 2);//询问mes
+                                        if (isSuccess)
                                         {
-                                            string[] items = response.Split('\n');
-                                            if (items[3].Contains("sn="))
+                                            LogHelper.WriteLogMes($"询问mes成功");
+                                            SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug);
+                                            string ProductMainSN_Temp = "";
+                                            if (response != "MES功能未启用!")
                                             {
-                                                ProductMainSN = items[3].Replace("sn=", "");
+                                                string[] item = response.Split('\n');
+                                                //获取过度件sn
+                                                string snItem = item.FirstOrDefault(f => f.Contains("sn="));
+                                                if (snItem != null)
+                                                {
+                                                    ProductMainSN_Temp = snItem.Replace("sn=", "").Trim();
+                                                    SendTaskMessage($"过度件sn:{ProductMainSN_Temp}", MessageLevel.Info);
+                                                }
                                             }
-                                            SendTaskMessage($"产品主sn:{ProductMainSN}", MessageLevel.Info);
-                                            if (items[5].Contains("wo="))
+
+                                            (bool isSuccess2, string response2) = await _mesService.StationGetAsync(i, ProductMainSN_Temp, "", 2);//询问mes
+                                            if (isSuccess2)
                                             {
-                                                wo = items[5].Replace("wo=", "");
+                                                LogHelper.WriteLogMes($"第二次询问mes成功");
+                                                SendTaskMessage($"第二次询问mes成功:{response2}", MessageLevel.Debug);
+                                                if (response2 != "MES功能未启用!")
+                                                {
+                                                    string[] item = response2.Split('\n');
+                                                    //获取产品主sn和wo用于后续上传数据使用
+                                                    string snItem = item.FirstOrDefault(f => f.Contains("sn="));
+                                                    if (snItem != null)
+                                                    {
+                                                        ProductMainSN[i] = snItem.Replace("sn=", "").Trim();
+                                                        SendTaskMessage($"产品主sn:{ProductMainSN[i]}", MessageLevel.Info);
+                                                    }
+                                                    string woItem = item.FirstOrDefault(f => f.Contains("wo="));
+                                                    if (woItem != null)
+                                                    {
+                                                        wo[i] = woItem.Replace("wo=", "").Trim();
+                                                        SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info);
+                                                    }
+                                                }
+
+                                                //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
+                                                await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
+                                                await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1);
+                                            }
+                                            else
+                                            {
+                                                LogHelper.WriteLogMes($"第二次询问mes失败");
+                                                SendTaskMessage($"第二次询问mes失败:{response2}", MessageLevel.Error);
+
+                                                //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
+                                                await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
+                                                await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
                                             }
-                                            SendTaskMessage($"产品wo:{wo}", MessageLevel.Info);
+                                        }
+                                        else
+                                        {
+                                            //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
+                                            await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
+                                            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
+
+                                            LogHelper.WriteLogMes($"询问mes失败");
+                                            SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error);
+
+                                            //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
+                                            await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
+                                            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
                                         }
                                         LogHelper.WriteLogMes($"【mes结束QUERY】");
                                         SendTaskMessage($"mes结束QUERY", MessageLevel.Info);
-                                        return;
                                     }
                                     else
                                     {
-                                        //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
-                                        await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
-                                        await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
+                                        SendTaskMessage($"收到{i}:{stationConfig.DeviceName}过站检查请求(当站是否生产)(当前不需要双MES验证)...", MessageLevel.Debug);
 
-                                        LogHelper.WriteLogMes($"询问mes失败");
-                                        SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error);
+                                        LogHelper.WriteLogMes($"【mes开始QUERY】");
+                                        SendTaskMessage($"mes开始QUERY", MessageLevel.Info);
+                                        (bool isSuccess, string response) = await _mesService.StationGetAsync(i, code, "", 2);//询问mes
+                                        if (isSuccess)
+                                        {
+                                            if (response != "MES功能未启用!")
+                                            {
+                                                string[] item = response.Split('\n');
+                                                //获取产品主sn和wo用于后续上传数据使用
+                                                string snItem = item.FirstOrDefault(f => f.Contains("sn="));
+                                                if (snItem != null)
+                                                {
+                                                    ProductMainSN[i] = snItem.Replace("sn=", "").Trim();
+                                                    SendTaskMessage($"产品主sn:{ProductMainSN[i]}", MessageLevel.Info);
+                                                }
+                                                string woItem = item.FirstOrDefault(f => f.Contains("wo="));
+                                                if (woItem != null)
+                                                {
+                                                    wo[i] = woItem.Replace("wo=", "").Trim();
+                                                    SendTaskMessage($"工单信息:{wo[i]}", MessageLevel.Info);
+                                                }
+                                            }
 
+                                            LogHelper.WriteLogMes($"询问mes成功");
+                                            SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug);
+                                            //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
+                                            await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
+                                            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1);
+                                        }
+                                        else
+                                        {
+                                            LogHelper.WriteLogMes($"询问mes失败");
+                                            SendTaskMessage($"询问mes失败:{response}", MessageLevel.Error);
+                                            //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
+                                            await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
+                                            await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)2);
+                                        }
                                         LogHelper.WriteLogMes($"【mes结束QUERY】");
                                         SendTaskMessage($"mes结束QUERY", MessageLevel.Info);
-                                        return;
                                     }
                                 }
                                 // 上传本站数据
@@ -2291,7 +2368,7 @@ namespace TeamAAS_VP.Core
                                     //读取压力值
                                     float pressure = plc.ReadNode<float>(string.Format(addressConfig.In_PressureValue.Address, i));
                                     AssemblyRecord assemblyRecord = new AssemblyRecord();
-                                    assemblyRecord.ProductSN = ProductMainSN;
+                                    assemblyRecord.ProductSN = ProductMainSN[i];
                                     assemblyRecord.RecipeName = currentProduct.Name;
                                     assemblyRecord.PartSN = PartSN;
                                     assemblyRecord.AssemblyDurationSeconds = ct;
@@ -2317,7 +2394,7 @@ namespace TeamAAS_VP.Core
                                         await _systemDatabaseService.RecordProductionAsync(new ProductionRecord()
                                         {
                                             ProductName = currentProduct.Name,
-                                            ProductCode = ProductMainSN,
+                                            ProductCode = ProductMainSN[i],
                                             DeviceId = i,
                                             DeviceName = "",
                                             Category = resultIndex == 1 ? "OK" : "NG",
@@ -2347,7 +2424,7 @@ namespace TeamAAS_VP.Core
 
                                     }
                                     //cc-复检cc,assypress-组装站压力
-                                    (bool isSuccess, string response) = await _mesService.SubmitGetAsync(ProductMainSN, comp, cc, assypress, resultIndex == 1 ? true : false, _LastMesCheckTime[i], DateTime.UtcNow);
+                                    (bool isSuccess, string response) = await _mesService.SubmitGetAsync(i, ProductMainSN[i], comp, cc, assypress, resultIndex == 1 ? true : false, _LastMesCheckTime[i], DateTime.UtcNow);
                                     if (isSuccess)
                                     {
                                         LogHelper.WriteLogMes($"上传mes成功");
@@ -2371,7 +2448,7 @@ namespace TeamAAS_VP.Core
                                         DFC.Clear();
                                         DFC.Add(new DataFormCtq
                                         {
-                                            PPID = ProductMainSN,
+                                            PPID = ProductMainSN[i],
                                             PROCESS_NAME = cdata.PROCESS_NAME,
                                             PROCESS_START_TIME = StartCtqTime.ToString(),
                                             CTQ_NAME = "CC",
@@ -2383,7 +2460,7 @@ namespace TeamAAS_VP.Core
                                             SUPPLIER_NAME = cdata.SUPPLIER_NAME,
                                             COMMODITY_TYPE = cdata.COMMODITY_TYPE,
                                             REVISION = cdata.REVISION,
-                                            WO = wo,
+                                            WO = wo[i],
                                             MFG_LOT = cdata.MFG_LOT,
                                             MFG_ASSY_LINE = cdata.MFG_ASSY_LINE,
                                             PROCESS_OUTCOME = assyPress ? "PASS" : "FAIL",

+ 34 - 3
TeamAAS-VM/Interfaces/IConfigService.cs

@@ -480,17 +480,48 @@ namespace TeamAAS_VP.Interfaces
         /// </summary>
         Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record);
 
-        // Device info
+        // Device info - 支持多设备
         /// <summary>
-        /// 获取或创建设备信息配置
+        /// 获取所有设备信息配置列表
+        /// </summary>
+        /// <returns>设备信息列表的只读集合</returns>
+        IReadOnlyCollection<DeviceInfo> GetAllDeviceInfos();
+
+        /// <summary>
+        /// 根据设备编号获取设备信息(兼容旧API,返回第一个设备)
         /// </summary>
         DeviceInfo GetDeviceInfo();
 
         /// <summary>
-        /// 保存设备信息配置
+        /// 根据设备编号获取设备信息
+        /// </summary>
+        /// <param name="deviceNo">设备编号</param>
+        DeviceInfo GetDeviceInfo(int deviceNo);
+
+        /// <summary>
+        /// 保存设备信息配置(添加或更新)
         /// </summary>
         void SaveDeviceInfo(DeviceInfo deviceInfo);
 
+        /// <summary>
+        /// 保存所有设备信息列表
+        /// </summary>
+        /// <param name="deviceInfoList">设备信息列表</param>
+        void SaveAllDeviceInfos(IEnumerable<DeviceInfo> deviceInfoList);
+
+        /// <summary>
+        /// 添加新设备
+        /// </summary>
+        /// <returns>新添加的设备</returns>
+        DeviceInfo AddDeviceInfo();
+
+        /// <summary>
+        /// 删除设备
+        /// </summary>
+        /// <param name="deviceNo">设备编号</param>
+        /// <returns>是否删除成功</returns>
+        bool RemoveDeviceInfo(int deviceNo);
+
         //刷卡登录
         /// <summary>
         /// 获取刷卡器配置

+ 21 - 4
TeamAAS-VM/Interfaces/IMesService.cs

@@ -27,7 +27,7 @@ namespace TeamAAS_VP.Interfaces
         /// - 实现应尊重 <paramref name="cancellationToken"/>,在取消时尽早抛出 <see cref="OperationCanceledException"/> 或返回失败结果。
         /// - 若 <paramref name="sn"/> 无效,应抛出 <see cref="ArgumentNullException"/> 或 <see cref="ArgumentException"/>。
         /// </remarks>
-        Task<(bool IsSuccess, string Response)> StationGetAsync(string sn, string comp, CancellationToken cancellationToken = default);
+        Task<(bool IsSuccess, string Response)> StationGetAsync(int deviceNo, string sn, string comp, CancellationToken cancellationToken = default);
 
         /// <summary>
         /// 使用配置的过站(Station)URL 发起 GET 请求,携带序列号(sn)作为查询参数进行过站,并使用指定的超时时间(秒)。
@@ -43,7 +43,16 @@ namespace TeamAAS_VP.Interfaces
         /// - 此重载方便在不使用 <see cref="CancellationToken"/> 的场景中指定超时时间。
         /// - 实现应验证 <paramref name="timeoutInSeconds"/> 为合理值(例如 > 0),否则可抛出 <see cref="ArgumentOutOfRangeException"/>。
         /// </remarks>
-        Task<(bool IsSuccess, string Response)> StationGetAsync(string sn, string comp, double timeoutInSeconds);
+        Task<(bool IsSuccess, string Response)> StationGetAsync(int deviceNo, string sn, string comp, double timeoutInSeconds);
+
+        /// <summary>
+        /// 过站请求的扩展版本,允许同时指定 <see cref="CancellationToken"/> 和超时时间(秒)。实现应优先考虑取消请求,但在未取消的情况下应用超时。
+        /// </summary>
+        /// <param name="sn"></param>
+        /// <param name="comp"></param>
+        /// <param name="timeoutInSeconds"></param>
+        /// <returns></returns>
+        Task<(bool IsSuccess, string Response)> StationGetExAsync(int deviceNo, string sn, string comp, double timeoutInSeconds);
 
         /// <summary>
         /// 使用配置的提交(Submit)URL 发起 GET 请求,提交过站结果及时间信息。
@@ -63,7 +72,7 @@ namespace TeamAAS_VP.Interfaces
         /// - 若 <paramref name="start_time"/> 晚于 <paramref name="stop_time"/>,实现应记录并视情况返回失败或抛出异常。
         /// - 实现应尊重 <paramref name="cancellationToken"/>,并在取消时尽早中止请求。
         /// </remarks>
-        Task<(bool IsSuccess, string Response)> SubmitGetAsync(string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, CancellationToken cancellationToken = default);
+        Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, CancellationToken cancellationToken = default);
 
         /// <summary>
         /// 使用配置的提交(Submit)URL 发起 GET 请求,提交过站结果及时间信息,并使用指定的超时时间(秒)。
@@ -82,6 +91,14 @@ namespace TeamAAS_VP.Interfaces
         /// - 此重载用于在不传入 <see cref="CancellationToken"/> 的场景中指定超时时间。
         /// - 实现应确保对时间参数与布尔参数进行适当的 URL 编码或格式化。
         /// </remarks>
-        Task<(bool IsSuccess, string Response)> SubmitGetAsync(string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds);
+        Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds);
+
+        /// <summary>
+        /// 获取服务器当前时间
+        /// </summary>
+        /// <param name="sn"></param>
+        /// <param name="timeoutInSeconds"></param>
+        /// <returns></returns>
+        Task<(bool IsSuccess, DateTime Now)> GetServerTimeAsync(int deviceNo, string sn, double timeoutInSeconds);
     }
 }

+ 72 - 5
TeamAAS-VM/Models/DeviceInfo.cs

@@ -1,34 +1,53 @@
 using Prism.Mvvm;
 using System;
+using System.Windows;
 
 namespace TeamAAS_VP.Models
 {
     /// <summary>
-    /// 设备信息配置,用于保存线别/站别/机台号以及 MES 相关配置。
+    /// 璁惧�淇℃伅閰嶇疆锛岀敤浜庝繚瀛樼嚎鍒�/绔欑偣/鏈哄彴浠ュ強 MES 涓婁紶璁剧疆銆�
     /// </summary>
     public class DeviceInfo : BindableBase
     {
+        private int _deviceNo;
+        /// <summary>
+        /// 璁惧�缂栧彿锛屼粠0寮€濮�
+        /// </summary>
+        public int DeviceNo { get => _deviceNo; set => SetProperty(ref _deviceNo, value); }
+
+        private string _deviceName;
+        /// <summary>
+        /// 璁惧�鍚嶇О
+        /// </summary>
+        public string DeviceName { get => _deviceName; set => SetProperty(ref _deviceName, value); }
+
+        private string _deviceDesc;
+        /// <summary>
+        /// 璁惧�鎻忚堪
+        /// </summary>
+        public string DeviceDesc { get => _deviceDesc; set => SetProperty(ref _deviceDesc, value); }
+
         private string _line;
         /// <summary>
-        /// 当前作业的线别
+        /// 褰撳墠浣滀笟鐨勭嚎鍒�
         /// </summary>
         public string Line { get => _line; set => SetProperty(ref _line, value); }
 
         private string _station;
         /// <summary>
-        /// 当前作业的站别
+        /// 褰撳墠浣滀笟鐨勭珯鐐�
         /// </summary>
         public string Station { get => _station; set => SetProperty(ref _station, value); }
 
         private string _fixtureId;
         /// <summary>
-        /// 当前作业的机台编号
+        /// 褰撳墠浣滀笟鐨勬満鍙扮紪鍙�
         /// </summary>
         public string FixtureId { get => _fixtureId; set => SetProperty(ref _fixtureId, value); }
 
         private bool _enableMes;
         /// <summary>
-        /// 是否启用 MES 通信功能
+        /// 鏄�惁鍚�敤 MES 閫氳�鍔熻兘
         /// </summary>
         public bool EnableMES { get => _enableMes; set => SetProperty(ref _enableMes, value); }
 
@@ -40,13 +59,41 @@ namespace TeamAAS_VP.Models
 
 
         private string _f;
+        /// <summary>
+        /// 鍏ㄥ眬function鍙傛暟锛屼紶妯″潡锛岀粍瑁呯珯PQC妫€娴嬬粍瑁呯殑浼燩TL
+        /// f=PTL
+        /// </summary>
         public string F { get => _f; set => SetProperty(ref _f, value); }
 
         private string _comp;
+        /// <summary>
+        /// 姝ゅ弬鏁板彧鏈夊寘瑁呯珯鏄�繀浼狅紝鍏朵粬缁勮�绔欎笉鐢ㄤ紶锛屾牸寮忔槸閮ㄤ欢鍚�:閮ㄤ欢缂栫爜锛屽�涓�椂鐢ㄨ嫳鏂囬€楀彿闅斿紑
+        /// comp=battery1:B00012345,battery2:B00022355
+        /// </summary>
         public string comp { get => _comp; set => SetProperty(ref _comp, value); }
 
+        private bool _saveCsv;
+        /// <summary>
+        /// 鏄�惁淇濆瓨CSV鏂囦欢锛岄粯璁や笉淇濆瓨
+        /// </summary>
+        public bool SaveCsv { get => _saveCsv; set => SetProperty(ref _saveCsv, value); }
+
+        private string _saveCsvPath;
+        /// <summary>
+        /// 淇濆瓨鐨凜SV鏂囦欢璺�緞
+        /// </summary>
+        public string SaveCsvPath { get => _saveCsvPath; set => SetProperty(ref _saveCsvPath, value); }
+
+        private bool _enableDoubleMes;
+        /// <summary>
+        /// 鏄�惁鍚�敤涓ゆ�浜у搧缂栧彿MES鑾峰彇锛岄粯璁や笉鍚�敤
+        /// </summary>
+        public bool EnableDoubleMes { get => _enableDoubleMes; set => SetProperty(ref _enableDoubleMes, value); }
+
+
         public DeviceInfo()
         {
+            DeviceNo = 0;
             Line = string.Empty;
             Station = string.Empty;
             FixtureId = string.Empty;
@@ -55,5 +102,25 @@ namespace TeamAAS_VP.Models
             MesUrl = string.Empty;
             EnableMES = false;
         }
+
+        public DeviceInfo Copy()
+        {
+            return new DeviceInfo
+            {
+                DeviceNo = this.DeviceNo,
+                DeviceName = this.DeviceName,
+                DeviceDesc = this.DeviceDesc,
+                Line = this.Line,
+                Station = this.Station,
+                FixtureId = this.FixtureId,
+                EnableMES = this.EnableMES,
+                MesUrl = this.MesUrl,
+                F = this.F,
+                comp = this.comp,
+                SaveCsv = this.SaveCsv,
+                SaveCsvPath = this.SaveCsvPath,
+                EnableDoubleMes = this.EnableDoubleMes
+            };
+        }
     }
 }

+ 2 - 2
TeamAAS-VM/Properties/AssemblyInfo.cs

@@ -51,5 +51,5 @@ using System.Windows;
 //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
 //通过使用 "*",如下所示:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.2.1.3")]
-[assembly: AssemblyFileVersion("1.2.1.3")]
+[assembly: AssemblyVersion("1.2.1.4")]
+[assembly: AssemblyFileVersion("1.2.1.4")]

+ 162 - 21
TeamAAS-VM/Services/ConfigService.cs

@@ -68,8 +68,8 @@ namespace TeamAAS_VP.Services
         private ObservableCollection<FeederClearWork> FeederClearanceTasks { get; set; }
         // 光源控制器配置
         private ObservableCollection<LightControllerConfig> LightControllers { get; set; }
-        // Device info
-        private DeviceInfo _deviceInfo;
+        // Device info - 支持多设备
+        private List<DeviceInfo> _deviceInfoList;
 
         public CTQ_Data CtqData { get; set; }
         private PlcAddressConfig PlcAddressConfig { get; set; }
@@ -223,13 +223,34 @@ namespace TeamAAS_VP.Services
                     FileHelper.WriteJsonFile(_screwDriverConfig, ConfigPaths.ScrewDriverConfigurationPath);
                 }
 
-                // load device info
+                // load device info list (支持多设备)
                 if (File.Exists(ConfigPaths.DeviceInfoConfigurationPath))
-                    _deviceInfo = FileHelper.ReadJsonFile<DeviceInfo>(ConfigPaths.DeviceInfoConfigurationPath);
+                {
+                    try
+                    {
+                        // 尝试作为列表读取
+                        _deviceInfoList = FileHelper.ReadJsonFile<List<DeviceInfo>>(ConfigPaths.DeviceInfoConfigurationPath);
+                    }
+                    catch
+                    {
+                        // 兼容旧版单个 DeviceInfo,将其转换为列表
+                        var singleDevice = FileHelper.ReadJsonFile<DeviceInfo>(ConfigPaths.DeviceInfoConfigurationPath);
+                        if (singleDevice != null)
+                        {
+                            singleDevice.DeviceNo = 0;
+                            _deviceInfoList = new List<DeviceInfo> { singleDevice };
+                        }
+                        else
+                        {
+                            _deviceInfoList = new List<DeviceInfo> { new DeviceInfo { DeviceNo = 0 } };
+                        }
+                        FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
+                    }
+                }
                 else
                 {
-                    _deviceInfo = new DeviceInfo();
-                    FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                    _deviceInfoList = new List<DeviceInfo> { new DeviceInfo { DeviceNo = 0 } };
+                    FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
                 }
 
                 // load card reader configuration
@@ -284,9 +305,9 @@ namespace TeamAAS_VP.Services
                     FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
                 // save plc addresses
                 //FileHelper.WriteJsonFile(PlcAddressConfig, "..//Config//PlcAddressConfiguration.cfg");
-                // save device info
-                if (_deviceInfo != null)
-                    FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                // save device info list (支持多设备)
+                if (_deviceInfoList != null)
+                    FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
                 // save card reader config
                 if (_cardReaderConfig != null)
                     FileHelper.WriteJsonFile(_cardReaderConfig, ConfigPaths.CardReaderConfigurationPath);
@@ -396,38 +417,158 @@ namespace TeamAAS_VP.Services
 
         #region DeviceInfo
         /// <summary>
-        /// 获取设备信息配置
+        /// 获取所有设备信息配置列表
         /// </summary>
-        /// <returns></returns>
-        public DeviceInfo GetDeviceInfo()
+        /// <returns>设备信息列表的只读集合</returns>
+        public IReadOnlyCollection<DeviceInfo> GetAllDeviceInfos()
         {
             lock (_sync)
             {
-                if (_deviceInfo == null)
+                if (_deviceInfoList == null)
                 {
                     if (File.Exists(ConfigPaths.DeviceInfoConfigurationPath))
-                        _deviceInfo = FileHelper.ReadJsonFile<DeviceInfo>(ConfigPaths.DeviceInfoConfigurationPath);
+                    {
+                        try
+                        {
+                            _deviceInfoList = FileHelper.ReadJsonFile<List<DeviceInfo>>(ConfigPaths.DeviceInfoConfigurationPath);
+                        }
+                        catch
+                        {
+                            // 兼容旧版单个 DeviceInfo
+                            var singleDevice = FileHelper.ReadJsonFile<DeviceInfo>(ConfigPaths.DeviceInfoConfigurationPath);
+                            if (singleDevice != null)
+                            {
+                                singleDevice.DeviceNo = 0;
+                                _deviceInfoList = new List<DeviceInfo> { singleDevice };
+                            }
+                            else
+                            {
+                                _deviceInfoList = new List<DeviceInfo> { new DeviceInfo { DeviceNo = 0 } };
+                            }
+                            FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
+                        }
+                    }
                     else
                     {
-                        _deviceInfo = new DeviceInfo();
-                        FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                        _deviceInfoList = new List<DeviceInfo> { new DeviceInfo { DeviceNo = 0 } };
+                        FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
                     }
                 }
-                return _deviceInfo;
+                return _deviceInfoList.AsReadOnly();
+            }
+        }
+
+        /// <summary>
+        /// 根据设备编号获取设备信息(兼容旧API,返回第一个设备)
+        /// </summary>
+        /// <returns>设备信息,若不存在则创建默认设备</returns>
+        public DeviceInfo GetDeviceInfo()
+        {
+            return GetDeviceInfo(0);
+        }
+
+        /// <summary>
+        /// 根据设备编号获取设备信息
+        /// </summary>
+        /// <param name="deviceNo">设备编号</param>
+        /// <returns>设备信息,若不存在则返回 null</returns>
+        public DeviceInfo GetDeviceInfo(int deviceNo)
+        {
+            lock (_sync)
+            {
+                var allDevices = GetAllDeviceInfos();
+                return allDevices.FirstOrDefault(d => d.DeviceNo == deviceNo);
             }
         }
 
         /// <summary>
-        /// 保存设备信息
+        /// 添加或更新设备信息
         /// </summary>
-        /// <param name="deviceInfo"></param>
+        /// <param name="deviceInfo">设备信息对象</param>
         public void SaveDeviceInfo(DeviceInfo deviceInfo)
         {
             if (deviceInfo == null) return;
             lock (_sync)
             {
-                _deviceInfo = deviceInfo;
-                FileHelper.WriteJsonFile(_deviceInfo, ConfigPaths.DeviceInfoConfigurationPath);
+                // 确保列表已初始化
+                if (_deviceInfoList == null)
+                {
+                    _deviceInfoList = new List<DeviceInfo>();
+                }
+
+                // 查找是否已存在相同设备编号的设备
+                var existing = _deviceInfoList.FirstOrDefault(d => d.DeviceNo == deviceInfo.DeviceNo);
+                if (existing != null)
+                {
+                    // 更新现有设备
+                    var index = _deviceInfoList.IndexOf(existing);
+                    _deviceInfoList[index] = deviceInfo;
+                }
+                else
+                {
+                    // 添加新设备
+                    _deviceInfoList.Add(deviceInfo);
+                }
+                // 按设备编号排序
+                _deviceInfoList = _deviceInfoList.OrderBy(d => d.DeviceNo).ToList();
+                FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
+            }
+        }
+
+        /// <summary>
+        /// 保存所有设备信息列表
+        /// </summary>
+        /// <param name="deviceInfoList">设备信息列表</param>
+        public void SaveAllDeviceInfos(IEnumerable<DeviceInfo> deviceInfoList)
+        {
+            if (deviceInfoList == null) return;
+            lock (_sync)
+            {
+                _deviceInfoList = deviceInfoList.OrderBy(d => d.DeviceNo).ToList();
+                FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
+            }
+        }
+
+        /// <summary>
+        /// 添加新设备
+        /// </summary>
+        /// <returns>新添加的设备</returns>
+        public DeviceInfo AddDeviceInfo()
+        {
+            lock (_sync)
+            {
+                if (_deviceInfoList == null)
+                {
+                    _deviceInfoList = new List<DeviceInfo>();
+                }
+
+                // 计算新设备编号
+                int newDeviceNo = _deviceInfoList.Count > 0 ? _deviceInfoList.Max(d => d.DeviceNo) + 1 : 0;
+                var newDevice = new DeviceInfo { DeviceNo = newDeviceNo };
+                _deviceInfoList.Add(newDevice);
+                FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
+                return newDevice;
+            }
+        }
+
+        /// <summary>
+        /// 删除设备
+        /// </summary>
+        /// <param name="deviceNo">设备编号</param>
+        /// <returns>是否删除成功</returns>
+        public bool RemoveDeviceInfo(int deviceNo)
+        {
+            lock (_sync)
+            {
+                if (_deviceInfoList == null) return false;
+                var device = _deviceInfoList.FirstOrDefault(d => d.DeviceNo == deviceNo);
+                if (device != null)
+                {
+                    _deviceInfoList.Remove(device);
+                    FileHelper.WriteJsonFile(_deviceInfoList, ConfigPaths.DeviceInfoConfigurationPath);
+                    return true;
+                }
+                return false;
             }
         }
         #endregion

+ 165 - 8
TeamAAS-VM/Services/MesService.cs

@@ -2,6 +2,7 @@ using Prism.Events;
 using Prism.Ioc;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Net.Http;
 using System.Text;
 using System.Threading;
@@ -76,12 +77,14 @@ namespace TeamAAS_VP.Services
         /// - 捕获 <see cref="OperationCanceledException"/> 返回 "Canceled",捕获其它异常则返回异常消息。
         /// - 若实例已被释放则抛出 <see cref="ObjectDisposedException"/>。
         /// </remarks>
-        public async Task<(bool IsSuccess, string Response)> StationGetAsync(string sn, string comp, CancellationToken cancellationToken = default)
+        public async Task<(bool IsSuccess, string Response)> StationGetAsync(int deviceNo, string sn, string comp, CancellationToken cancellationToken = default)
         {
             // 如果已经释放,则抛出异常,防止在已释放资源上继续操作。
             if (_disposed) throw new ObjectDisposedException(nameof(MesService));
 
-            var device = _configService.GetDeviceInfo();
+            var device = _configService.GetDeviceInfo(deviceNo);
+            if (device == null)
+                device = _configService.GetDeviceInfo();
             if (device == null || !device.EnableMES)
             {
                 return (true, "MES功能未启用!");
@@ -149,11 +152,93 @@ namespace TeamAAS_VP.Services
         /// <param name="sn">序列号(SN)。</param>
         /// <param name="timeoutInSeconds">请求超时,单位为秒。</param>
         /// <returns>与 <see cref="StationGetAsync(string, CancellationToken)"/> 相同的返回语义。</returns>
-        public async Task<(bool IsSuccess, string Response)> StationGetAsync(string sn, string comp, double timeoutInSeconds)
+        public async Task<(bool IsSuccess, string Response)> StationGetAsync(int deviceNo, string sn, string comp, double timeoutInSeconds)
         {
             using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)))
             {
-                return await StationGetAsync(sn, comp, cts.Token);
+                return await StationGetAsync(deviceNo, sn, comp, cts.Token);
+            }
+        }
+
+        /// <summary>
+        /// 向配置中指定的 MES 站点 URL 发起 GET 请求(用于过站查询)。
+        /// </summary>
+        /// <param name="sn">要查询的序列号(SN)。</param>
+        /// <param name="cancellationToken">可选的取消令牌,用于请求超时或取消。</param>
+        /// <returns>
+        /// 返回元组 (IsSuccess, Response):
+        /// - IsSuccess: 请求是否视为成功(即 MES 返回了预期的 "SFC_OK" 且包含 "unit_process_check=OK")。
+        /// - Response: 原始响应文本或错误信息说明(如 "MES功能未启用!"、"Missing MES station URL"、"Canceled" 等)。
+        /// </returns>
+        /// <remarks>
+        /// 行为说明:
+        /// - 从配置获取设备信息,若 MES 未启用则直接返回 IsSuccess=true 且 Response 为提示文本(不视为错误)。
+        /// - 若未配置 MES URL 则返回 IsSuccess=false 与错误文本。
+        /// - 使用配置的 URL(通过 <see cref="string.Format"/> 填充 sn)发起 GET 请求,最多重试 10 次。
+        /// - 只有当响应包含 "SFC_OK" 且包含 "unit_process_check=OK" 时视为成功并立即返回。
+        /// - 捕获 <see cref="OperationCanceledException"/> 返回 "Canceled",捕获其它异常则返回异常消息。
+        /// - 若实例已被释放则抛出 <see cref="ObjectDisposedException"/>。
+        /// </remarks>
+        public async Task<(bool IsSuccess, string Response)> StationGetExAsync(int deviceNo, string sn, string comp, CancellationToken cancellationToken = default)
+        {
+            // 如果已经释放,则抛出异常,防止在已释放资源上继续操作。
+            if (_disposed) throw new ObjectDisposedException(nameof(MesService));
+
+            var device = _configService.GetDeviceInfo(deviceNo);
+            if (device == null)
+                device = _configService.GetDeviceInfo();
+            if (device == null || !device.EnableMES)
+            {
+                return (true, "MES功能未启用!");
+            }
+            if (string.IsNullOrWhiteSpace(device.MesUrl))
+                return (false, "Missing MES station URL");
+
+            string url = "";
+            url = $"{device.MesUrl}?c=QUERY_RECORD&line={device.Line}&station={device.Station}&fixtureid={device.FixtureId}&sn={sn}&p=sn,message";
+
+            LogHelper.WriteLogMes($"【询问URL】{url}");
+            SendTaskMessage($"【询问URL】{url}", MessageLevel.Info);
+            (bool IsSuccess, string Response) result = (false, string.Empty);
+
+            for (int i = 0; i < 3; i++)
+            {
+                try
+                {
+                    using (var rsp = await _httpClient.GetAsync(url))
+                    {
+                        var text = await rsp.Content.ReadAsStringAsync();
+                        LogHelper.WriteLogMes($"【HTTP接收】{text}");
+                        SendTaskMessage($"【HTTP接收】{text}", MessageLevel.Info);
+
+                        // "SFC_OK" 代表和 MES 连接成功
+                        if (text.Contains("SFC_OK"))
+                        {
+                            if (text.Contains("message=OK"))
+                            {
+                                return (true, text);
+                            }
+                        }
+                        result = (false, text);
+                    }
+                }
+                catch (OperationCanceledException)
+                {
+                    result = (false, "Canceled");
+                }
+                catch (Exception ex)
+                {
+                    result = (false, ex.Message);
+                }
+            }
+            return result;
+        }
+
+        public async Task<(bool IsSuccess, string Response)> StationGetExAsync(int deviceNo, string sn, string comp, double timeoutInSeconds)
+        {
+            using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)))
+            {
+                return await StationGetExAsync(deviceNo, sn, comp, cts.Token);
             }
         }
 
@@ -179,12 +264,14 @@ namespace TeamAAS_VP.Services
         /// - 捕获 <see cref="OperationCanceledException"/> 返回 "Canceled",捕获其它异常则返回异常消息。
         /// - 若实例已被释放则抛出 <see cref="ObjectDisposedException"/>。
         /// </remarks>
-        public async Task<(bool IsSuccess, string Response)> SubmitGetAsync(string sn, string comp, string cc,string assypress,bool isSuccess, DateTime start_time, DateTime stop_time, CancellationToken cancellationToken = default)
+        public async Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, CancellationToken cancellationToken = default)
         {
             // 如果已经释放,则抛出异常,防止在已释放资源上继续操作。
             if (_disposed) throw new ObjectDisposedException(nameof(MesService));
 
-            var device = _configService.GetDeviceInfo();
+            var device = _configService.GetDeviceInfo(deviceNo);
+            if (device == null)
+                device = _configService.GetDeviceInfo();
             if (device == null || !device.EnableMES)
             {
                 return (true, "MES功能未启用!");
@@ -259,14 +346,84 @@ namespace TeamAAS_VP.Services
         /// <param name="stop_time">结束时间。</param>
         /// <param name="timeoutInSeconds">请求超时,单位为秒。</param>
         /// <returns>与 <see cref="SubmitGetAsync(string, bool, DateTime, DateTime, CancellationToken)"/> 相同的返回语义。</returns>
-        public async Task<(bool IsSuccess, string Response)> SubmitGetAsync(string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds)
+        public async Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string cc, string assypress, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds)
         {
             using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)))
             {
-                return await SubmitGetAsync(sn, comp, cc, assypress, isSuccess, start_time, stop_time, cts.Token);
+                return await SubmitGetAsync(deviceNo, sn, comp, cc, assypress, isSuccess, start_time, stop_time, cts.Token);
             }
         }
 
+        /// <summary>
+        /// 获取服务器当前时间
+        /// </summary>
+        /// <param name="sn"></param>
+        /// <param name="timeoutInSeconds"></param>
+        /// <returns></returns>
+        public async Task<(bool IsSuccess, DateTime Now)> GetServerTimeAsync(int deviceNo, string sn, double timeoutInSeconds)
+        {
+            // 如果已经释放,则抛出异常,防止在已释放资源上继续操作。
+            if (_disposed) throw new ObjectDisposedException(nameof(MesService));
+
+            var device = _configService.GetDeviceInfo(deviceNo);
+            if (device == null)
+                device = _configService.GetDeviceInfo();
+            if (device == null || !device.EnableMES)
+            {
+                return (true, DateTime.UtcNow);
+            }
+            if (string.IsNullOrWhiteSpace(device.MesUrl))
+                return (false, DateTime.UtcNow);
+
+            string url = "";
+            url = $"{device.MesUrl}?c=QUERY_RECORD&line={device.Line}&station={device.Station}&fixtureid={device.FixtureId}&sn={sn}&p=mes_server_time";
+
+            LogHelper.WriteLogMes($"【询问URL】{url}");
+            SendTaskMessage($"【询问URL】{url}", MessageLevel.Info);
+            (bool IsSuccess, DateTime Now) result = (false, DateTime.UtcNow);
+
+            for (int i = 0; i < 3; i++)
+            {
+                try
+                {
+                    using (var rsp = await _httpClient.GetAsync(url))
+                    {
+                        var text = await rsp.Content.ReadAsStringAsync();
+                        LogHelper.WriteLogMes($"【HTTP接收】{text}");
+                        SendTaskMessage($"【HTTP接收】{text}", MessageLevel.Info);
+
+                        // "SFC_OK" 代表和 MES 连接成功
+                        if (text.Contains("SFC_OK"))
+                        {
+                            //按照分隔符'/n'分割字符串
+                            string[] lines = text.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
+                            //通过linq查找是否包含"mes_server_time"的行
+                            var timeLine = lines.FirstOrDefault(l => l.Contains("mes_server_time"));
+                            if (timeLine != null)
+                            {
+                                // 提取时间字符串
+                                var timeString = timeLine.Split('=')[1].Trim();
+                                if (DateTime.TryParse(timeString, out DateTime serverTime))
+                                {
+                                    return (true, serverTime);
+                                }
+                            }
+                        }
+                        result = (false, DateTime.UtcNow);
+                    }
+                }
+                catch (OperationCanceledException)
+                {
+                    result = (false, DateTime.UtcNow);
+                }
+                catch (Exception ex)
+                {
+                    result = (false, DateTime.UtcNow);
+                }
+            }
+            return result;
+        }
+
         /// <summary>
         /// 释放服务占用的托管资源(当前仅 <see cref="_httpClient"/>)。
         /// <para>本方法为幂等操作,重复调用不会产生异常。调用后实例不可再用于发起请求,尝试使用将抛出 <see cref="ObjectDisposedException"/>。</para>

+ 134 - 9
TeamAAS-VM/ViewModels/SettingViewModel.cs

@@ -336,11 +336,39 @@ namespace TeamAAS_VP.ViewModels
             set { SetProperty(ref _SelectLightController, value); }
         }
 
-        private DeviceInfo _DeviceInfo;
+        // 设备信息列表
+        private ObservableCollection<DeviceInfo> _DeviceInfoList;
+        public ObservableCollection<DeviceInfo> DeviceInfoList
+        {
+            get { return _DeviceInfoList; }
+            set { SetProperty(ref _DeviceInfoList, value); }
+        }
+
+        // 当前选中的设备信息
+        private DeviceInfo _SelectDeviceInfo;
+        public DeviceInfo SelectDeviceInfo
+        {
+            get { return _SelectDeviceInfo; }
+            set { SetProperty(ref _SelectDeviceInfo, value); }
+        }
+
+        // 兼容旧代码,DeviceInfo 指向当前选中的设备或第一个设备
         public DeviceInfo DeviceInfo
         {
-            get { return _DeviceInfo; }
-            set { SetProperty(ref _DeviceInfo, value); }
+            get { return _SelectDeviceInfo ?? DeviceInfoList?.FirstOrDefault(); }
+            set
+            {
+                if (value != null && DeviceInfoList != null)
+                {
+                    var existing = DeviceInfoList.FirstOrDefault(d => d.DeviceNo == value.DeviceNo);
+                    if (existing != null)
+                    {
+                        var index = DeviceInfoList.IndexOf(existing);
+                        DeviceInfoList[index] = value;
+                    }
+                }
+                RaisePropertyChanged();
+            }
         }
 
         private SerialPortConfig _CardReaderConfig;
@@ -446,9 +474,20 @@ namespace TeamAAS_VP.ViewModels
         public DelegateCommand SaveScrewFeedersCommand =>
             _SaveScrewFeedersCommand ?? (_SaveScrewFeedersCommand = new DelegateCommand(ExecuteSaveScrewFeedersCommand));
 
+        private DelegateCommand _AddDeviceInfoCommand;
+        public DelegateCommand AddDeviceInfoCommand => _AddDeviceInfoCommand ?? (_AddDeviceInfoCommand = new DelegateCommand(ExecuteAddDeviceInfoCommand));
+
+        private DelegateCommand _RemoveDeviceInfoCommand;
+        public DelegateCommand RemoveDeviceInfoCommand => _RemoveDeviceInfoCommand ?? (_RemoveDeviceInfoCommand = new DelegateCommand(ExecuteRemoveDeviceInfoCommand));
+
         private DelegateCommand _SaveDeviceInfoCommand;
         public DelegateCommand SaveDeviceInfoCommand => _SaveDeviceInfoCommand ?? (_SaveDeviceInfoCommand = new DelegateCommand(ExecuteSaveDeviceInfoCommand));
 
+        private DelegateCommand _SelectSaveImagePathCommand;
+        public DelegateCommand SelectSaveImagePathCommand =>
+            _SelectSaveImagePathCommand ?? (_SelectSaveImagePathCommand = new DelegateCommand(ExecuteSelectSaveImagePathCommand));
+
+
         private DelegateCommand<object> _LightModelChangedCommand;
         public DelegateCommand<object> LightModelChangedCommand =>
             _LightModelChangedCommand ?? (_LightModelChangedCommand = new DelegateCommand<object>(ExecuteLightModelChangedCommand));
@@ -1358,10 +1397,16 @@ namespace TeamAAS_VP.ViewModels
                 BgModbus = _configService.GetBgModbusTcp();
             }
 
-            if (DeviceInfo == null)
+            if (DeviceInfoList == null || DeviceInfoList.Count == 0)
             {
-                // load device info
-                DeviceInfo = _configService.GetDeviceInfo();
+                // load device info list (支持多设备)
+                var devices = _configService.GetAllDeviceInfos();
+                DeviceInfoList = new ObservableCollection<DeviceInfo>(devices);
+                // 默认选中第一个设备
+                if (DeviceInfoList.Count > 0)
+                {
+                    SelectDeviceInfo = DeviceInfoList[0];
+                }
             }
 
             if (CardReaderConfig == null)
@@ -1630,23 +1675,103 @@ namespace TeamAAS_VP.ViewModels
 
         #region 设备信息
         /// <summary>
-        /// 保存设备信息
+        /// 保存当前选中的设备信息
         /// </summary>
         void ExecuteSaveDeviceInfoCommand()
         {
             try
             {
-                if (DeviceInfo != null)
+                if (SelectDeviceInfo != null)
                 {
-                    _configService.SaveDeviceInfo(DeviceInfo);
+                    _configService.SaveDeviceInfo(SelectDeviceInfo);
                     _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
                 }
+                else if (DeviceInfoList != null && DeviceInfoList.Count > 0)
+                {
+                    // 保存所有设备信息
+                    _configService.SaveAllDeviceInfos(DeviceInfoList);
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                }
+            }
+            catch (Exception ex)
+            {
+                _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
+            }
+        }
+
+        /// <summary>
+        /// 添加新设备
+        /// </summary>
+        void ExecuteAddDeviceInfoCommand()
+        {
+            try
+            {
+                var newDevice = _configService.AddDeviceInfo();
+                if (DeviceInfoList == null)
+                {
+                    DeviceInfoList = new ObservableCollection<DeviceInfo>();
+                }
+                DeviceInfoList.Add(newDevice);
+                SelectDeviceInfo = newDevice;
+                _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
             }
             catch (Exception ex)
             {
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
             }
         }
+
+        /// <summary>
+        /// 删除当前选中的设备
+        /// </summary>
+        void ExecuteRemoveDeviceInfoCommand()
+        {
+            try
+            {
+                if (SelectDeviceInfo != null && DeviceInfoList != null && DeviceInfoList.Count > 1)
+                {
+                    var deviceNo = SelectDeviceInfo.DeviceNo;
+                    if (_configService.RemoveDeviceInfo(deviceNo))
+                    {
+                        DeviceInfoList.Remove(SelectDeviceInfo);
+                        SelectDeviceInfo = DeviceInfoList.FirstOrDefault();
+                        _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                    }
+                }
+                else
+                {
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = "至少保留一个设备", Duration = 2 });
+                }
+            }
+            catch (Exception ex)
+            {
+                _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
+            }
+        }
+
+        /// <summary>
+        /// 选择CSV存储路径
+        /// </summary>
+        void ExecuteSelectSaveImagePathCommand()
+        {
+            if (SelectDeviceInfo == null)
+            {
+                return;
+            }
+            using (System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog())
+            {
+                // Optional: set the initial directory
+                //dialog.SelectedPath = @"C:\";
+
+                // Show the folder browser dialog
+                System.Windows.Forms.DialogResult result = dialog.ShowDialog();
+
+                if (result == System.Windows.Forms.DialogResult.OK)
+                {
+                    SelectDeviceInfo.SaveCsvPath = dialog.SelectedPath;
+                }
+            }
+        }
         #endregion
     }
 }

+ 4 - 4
TeamAAS-VM/Views/Home/ShowVisionRender.xaml.cs

@@ -407,7 +407,7 @@ namespace TeamAAS_VP.Views.Home
                                 productSN = productSN.Replace(c, '_');
 
                             string resultStr = result ? "OK" : "NG";
-                            Originalpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
+                            Originalpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
                         }
 
                         if (!Directory.Exists(Path.GetDirectoryName(Originalpath)))
@@ -455,7 +455,7 @@ namespace TeamAAS_VP.Views.Home
                                 productSN = productSN.Replace(c, '_');
 
                             string resultStr = result ? "OK" : "NG";
-                            Recordedpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
+                            Recordedpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
                         }
 
                         if (!Directory.Exists(Path.GetDirectoryName(Recordedpath)))
@@ -523,8 +523,8 @@ namespace TeamAAS_VP.Views.Home
                                 productSN = productSN.Replace(c, '_');
 
                             string resultStr = result ? "OK" : "NG";
-                            Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
-                            Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
+                            Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
+                            Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
                         }
 
                         if (!Directory.Exists(Path.GetDirectoryName(Originalpath)))

+ 239 - 224
TeamAAS-VM/Views/SettingView.xaml

@@ -228,237 +228,252 @@
                     </StackPanel>
                 </TabItem.Header>
 
-                <Grid>
+                <Grid Margin="16">
                     <Grid.RowDefinitions>
-                        <RowDefinition Height="auto" />
-                        <RowDefinition Height="auto" />
+                        <RowDefinition Height="50" />
+                        <RowDefinition Height="*" />
                     </Grid.RowDefinitions>
-                    <Grid Margin="16">
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                        </Grid.RowDefinitions>
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="auto" />
-                            <ColumnDefinition Width="*" />
-                        </Grid.ColumnDefinitions>
-
-
-                        <TextBlock Grid.Row="0"
-                                   Grid.Column="0"
-                                   Text="Url:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <TextBox Grid.Row="0"
-                                 Grid.Column="1"
-                                 Text="{Binding DeviceInfo.MesUrl, Mode=TwoWay}"
-                                 Margin="6" />
-
-                        <TextBlock Grid.Row="1"
-                                   Grid.Column="0"
-                                   Text="Line:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <TextBox Grid.Row="1"
-                                 Grid.Column="1"
-                                 Text="{Binding DeviceInfo.Line, Mode=TwoWay}"
-                                 Margin="6" />
-
-                        <TextBlock Grid.Row="2"
-                                   Grid.Column="0"
-                                   Text="Station:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <TextBox Grid.Row="2"
-                                 Grid.Column="1"
-                                 Text="{Binding DeviceInfo.Station, Mode=TwoWay}"
-                                 Margin="6" />
-
-                        <TextBlock Grid.Row="3"
-                                   Grid.Column="0"
-                                   Text="FixtureId:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <TextBox Grid.Row="3"
-                                 Grid.Column="1"
-                                 Text="{Binding DeviceInfo.FixtureId, Mode=TwoWay}"
-                                 Margin="6" />
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="auto" />
+                        <ColumnDefinition Width="*" />
+                    </Grid.ColumnDefinitions>
 
-                        <TextBlock Grid.Row="4"
-                                   Grid.Column="0"
-                                   Text="f:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <TextBox Grid.Row="4"
-                                 Grid.Column="1"
-                                 Text="{Binding DeviceInfo.F, Mode=TwoWay}"
-                                 Margin="6" />
+                    <!--增删改查按钮-->
+                    <StackPanel Grid.ColumnSpan="2"
+                                IsEnabled="{Binding IsAllowEdit}"
+                                Orientation="Horizontal">
+                        <Button Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
+                                ToolTip="添加设备"
+                                Command="{Binding AddDeviceInfoCommand}">
+                            <materialDesign:PackIcon Kind="Plus" />
+                        </Button>
+                        <Button Style="{StaticResource MaterialDesignFloatingActionMiniDarkButton}"
+                                ToolTip="删除设备"
+                                Margin="20,0"
+                                Command="{Binding RemoveDeviceInfoCommand}">
+                            <materialDesign:PackIcon Kind="DeleteForeverOutline" />
+                        </Button>
+                        <Button Style="{StaticResource MaterialDesignFloatingActionMiniSecondaryDarkButton}"
+                                ToolTip="{lex:Loc 保存}"
+                                Command="{Binding SaveDeviceInfoCommand}">
+                            <materialDesign:PackIcon Kind="ContentSaveCheck" />
+                        </Button>
+                    </StackPanel>
 
-                        <TextBlock Grid.Row="5"
-                                   Grid.Column="0"
-                                   Text="comp:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <TextBox Grid.Row="5"
-                                 Grid.Column="1"
-                                 Text="{Binding DeviceInfo.comp, Mode=TwoWay}"
-                                 Margin="6" />
+                    <!--设备列表-->
+                    <TreeView Grid.Row="1"
+                              Grid.Column="0"
+                              SelectedItemChanged="TreeView_SelectedItemChanged">
+                        <TreeViewItem Header="设备列表"
+                                      FontSize="{DynamicResource Font.Size.Title4}"
+                                      FontWeight="Bold"
+                                      IsExpanded="True"
+                                      ItemsSource="{Binding DeviceInfoList}">
+                            <TreeViewItem.ItemTemplate>
+                                <HierarchicalDataTemplate>
+                                    <StackPanel Orientation="Vertical">
+                                        <TextBlock FontSize="{DynamicResource Font.Size.Title4}">
+                                            <TextBlock.Text>
+                                                <MultiBinding StringFormat="设备 {0}">
+                                                    <Binding Path="DeviceNo" />
+                                                </MultiBinding>
+                                            </TextBlock.Text>
+                                        </TextBlock>
+                                        <StackPanel Orientation="Horizontal">
+                                            <TextBlock Text="Line: "
+                                                       Style="{StaticResource MaterialDesignCaptionTextBlock}"
+                                                       FontSize="{DynamicResource Font.Size.Body3}" />
+                                            <TextBlock Text="{Binding Line}"
+                                                       Style="{StaticResource MaterialDesignCaptionTextBlock}"
+                                                       FontSize="{DynamicResource Font.Size.Body3}" />
+                                        </StackPanel>
+                                        <StackPanel Orientation="Horizontal">
+                                            <TextBlock Text="Station: "
+                                                       Style="{StaticResource MaterialDesignCaptionTextBlock}"
+                                                       FontSize="{DynamicResource Font.Size.Body3}" />
+                                            <TextBlock Text="{Binding Station}"
+                                                       Style="{StaticResource MaterialDesignCaptionTextBlock}"
+                                                       FontSize="{DynamicResource Font.Size.Body3}" />
+                                        </StackPanel>
+                                        <Border Height="2"
+                                                Margin="0"
+                                                BorderThickness="2"
+                                                BorderBrush="Green" />
+                                    </StackPanel>
+                                </HierarchicalDataTemplate>
+                            </TreeViewItem.ItemTemplate>
+                        </TreeViewItem>
+                    </TreeView>
 
-                        <TextBlock Grid.Row="6"
-                                   Grid.Column="0"
-                                   Text="{lex:Loc 启用MES}"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <CheckBox Grid.Row="6"
+                    <!--参数详情-->
+                    <ScrollViewer Grid.Row="1"
                                   Grid.Column="1"
-                                  IsChecked="{Binding DeviceInfo.EnableMES, Mode=TwoWay}"
-                                  Margin="6" />
-
-                        <StackPanel Orientation="Horizontal"
-                                    HorizontalAlignment="Right"
-                                    Grid.Row="7"
-                                    Grid.ColumnSpan="2"
-                                    Margin="6">
-                            <Button Style="{StaticResource MaterialDesignRaisedButton}"
-                                    Command="{Binding SaveDeviceInfoCommand}">保存</Button>
-                        </StackPanel>
-                    </Grid>
+                                  IsEnabled="{Binding IsAllowEdit}"
+                                  Margin="20"
+                                  Visibility="{Binding SelectDeviceInfo,Converter={StaticResource ObjectToVisibilityConverter}}">
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="auto" />
+                                <RowDefinition Height="auto" />
+                            </Grid.RowDefinitions>
 
-                    <!--<Grid Margin="16"
-Grid.Row="1">
-              <Grid.RowDefinitions>
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-                  <RowDefinition Height="Auto" />
-              </Grid.RowDefinitions>
-              <Grid.ColumnDefinitions>
-                  <ColumnDefinition Width="auto" />
-                  <ColumnDefinition Width="*" />
-              </Grid.ColumnDefinitions>
-
-              <TextBlock Grid.Row="0"
-         Grid.Column="0"
-         Text="PROCESS_NAME:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="0"
-       Grid.Column="1"
-       Text="{Binding DataInfo.PROCESS_NAME, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="1"
-         Grid.Column="0"
-         Text="CTQ_LSL(压力下限):"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="1"
-       Grid.Column="1"
-       Text="{Binding DataInfo.CTQ_LSL_Pressure, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="2"
-         Grid.Column="0"
-         Text="CTQ_USL(压力上限):"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="2"
-       Grid.Column="1"
-       Text="{Binding DataInfo.CTQ_USL_Pressure, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="3"
-         Grid.Column="0"
-         Text="SUPPLIER_NAME:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="3"
-       Grid.Column="1"
-       Text="{Binding DataInfo.SUPPLIER_NAME, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="4"
-         Grid.Column="0"
-         Text="COMMODITY_TYPE:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="4"
-       Grid.Column="1"
-       Text="{Binding DataInfo.COMMODITY_TYPE, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="5"
-         Grid.Column="0"
-         Text="REVISION:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="5"
-       Grid.Column="1"
-       Text="{Binding DataInfo.REVISION, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="6"
-         Grid.Column="0"
-         Text="WO#:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="6"
-       Grid.Column="1"
-       Text="{Binding DataInfo.WO, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="7"
-         Grid.Column="0"
-         Text="MFG_LOT#:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="7"
-       Grid.Column="1"
-       Text="{Binding DataInfo.MFG_LOT, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="8"
-         Grid.Column="0"
-         Text="MFG_ASSY_LINE:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="8"
-       Grid.Column="1"
-       Text="{Binding DataInfo.MFG_ASSY_LINE, Mode=TwoWay}"
-       Margin="6" />
-
-              <TextBlock Grid.Row="9"
-         Grid.Column="0"
-         Text="PROCESS_MACHINE_ID:"
-         HorizontalAlignment="Right"
-         VerticalAlignment="Center" />
-              <TextBox Grid.Row="9"
-       Grid.Column="1"
-       Text="{Binding DataInfo.PROCESS_MACHINE_ID, Mode=TwoWay}"
-       Margin="6" />
-
-              <StackPanel Orientation="Horizontal"
-          HorizontalAlignment="Right"
-          Grid.Row="10"
-          Grid.ColumnSpan="2"
-          Margin="6">
-                  <Button Style="{StaticResource MaterialDesignRaisedButton}"
-          Command="{Binding SaveDataInfoCommand}">保存</Button>
-              </StackPanel>
-          </Grid>-->
+                            <!--设备信息表单-->
+                            <Grid>
+                                <Grid.RowDefinitions>
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                    <RowDefinition Height="Auto" />
+                                </Grid.RowDefinitions>
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="auto" />
+                                    <ColumnDefinition Width="*" />
+                                </Grid.ColumnDefinitions>
+
+                                <TextBlock Grid.Row="0"
+                                           Grid.Column="0"
+                                           Text="设备编号:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBlock Grid.Row="0"
+                                           Grid.Column="1"
+                                           Text="{Binding SelectDeviceInfo.DeviceNo}"
+                                           Margin="6,2"
+                                           VerticalAlignment="Center" />
+
+                                <TextBlock Grid.Row="1"
+                                           Grid.Column="0"
+                                           Text="设备名称:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="1"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.DeviceName, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="2"
+                                           Grid.Column="0"
+                                           Text="设备描述:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="2"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.DeviceDesc, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="3"
+                                           Grid.Column="0"
+                                           Text="Url:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="3"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.MesUrl, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="4"
+                                           Grid.Column="0"
+                                           Text="Line:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="4"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.Line, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="5"
+                                           Grid.Column="0"
+                                           Text="Station:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="5"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.Station, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="6"
+                                           Grid.Column="0"
+                                           Text="FixtureId:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="6"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.FixtureId, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="7"
+                                           Grid.Column="0"
+                                           Text="f:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="7"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.F, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="8"
+                                           Grid.Column="0"
+                                           Text="comp:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="8"
+                                         Grid.Column="1"
+                                         Text="{Binding SelectDeviceInfo.comp, Mode=TwoWay}"
+                                         Margin="6,2" />
+
+                                <TextBlock Grid.Row="9"
+                                           Grid.Column="0"
+                                           Text="启用 MES:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <CheckBox Grid.Row="9"
+                                          Grid.Column="1"
+                                          IsChecked="{Binding SelectDeviceInfo.EnableMES, Mode=TwoWay}"
+                                          Margin="6,2" />
+                                <CheckBox Grid.Row="10"
+                                          Grid.Column="1"
+                                          Content="是否启用两次产品编号"
+                                          IsChecked="{Binding SelectDeviceInfo.EnableDoubleMes, Mode=TwoWay}"
+                                          Margin="6,2" />
+                                <CheckBox Grid.Row="11"
+                                          Grid.Column="1"
+                                          Content="是否保存CSV文件"
+                                          IsChecked="{Binding SelectDeviceInfo.SaveCsv, Mode=TwoWay}"
+                                          Margin="6,2" />
+                                <TextBlock Grid.Row="12"
+                                           Grid.Column="0"
+                                           Text="保存的CSV文件路径:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <DockPanel HorizontalAlignment="Stretch"
+                                           Grid.Row="12"
+                                           Grid.Column="1"
+                                           Margin="6,2">
+                                    <Button DockPanel.Dock="Right"
+                                            HorizontalAlignment="Right"
+                                            ToolTip="{lex:Loc 选择路径}"
+                                            Command="{Binding SelectSaveImagePathCommand}">
+                                        <Button.Content>
+                                            <materialDesign:PackIcon Kind="FolderOpenOutline" />
+                                        </Button.Content>
+                                    </Button>
+                                    <TextBox  Text="{Binding SelectDeviceInfo.SaveCsvPath, Mode=TwoWay}" />
+
+                                </DockPanel>
+
+                            </Grid>
+                        </Grid>
+                    </ScrollViewer>
                 </Grid>
             </TabItem>
 

+ 4 - 0
TeamAAS-VM/Views/SettingView.xaml.cs

@@ -51,6 +51,10 @@ namespace TeamAAS_VP.Views
                 {
                     viewModel.SelectScrewFeeder = e.NewValue as ScrewFeederInfo;
                 }
+                else if (e.NewValue is DeviceInfo)
+                {
+                    viewModel.SelectDeviceInfo = e.NewValue as DeviceInfo;
+                }
             }
             catch (Exception)
             {