Przeglądaj źródła

支持设备信息多设备管理及MES多设备接口

本次更新实现设备信息配置的多设备支持,相关数据结构、接口、界面全面扩展。
- DeviceInfo 支持多设备,新增编号、名称、描述等字段
- ConfigService 支持设备列表的增删查改,兼容旧数据
- MES接口增加 deviceNo 参数,支持多设备查询与提交
- 新增获取MES服务器时间接口,保证时间同步
- SettingView/SettingViewModel 支持设备列表展示、切换、编辑、增删、保存
- 相关注释与文档同步更新,所有业务逻辑兼容多设备
适用于多台设备并行作业场景,提升配置灵活性与界面交互体验。
徐孝锋 6 miesięcy temu
rodzic
commit
84d20b6bab

+ 12 - 9
TeamAAS-VM/Core/Management.cs

@@ -1208,7 +1208,7 @@ namespace TeamAAS_VP.Core
             {
                 foreach (var item in current.PickPoints)
                 {
-                    await item.WriteToPlcAddress(addressConfig.Out_Pick, plc,item.Feeder);
+                    await item.WriteToPlcAddress(addressConfig.Out_Pick, plc, item.Feeder);
                 }
             }
             if (current.SecPosCameraPoints != null && current.SecPosCameraPoints.Count > 0)
@@ -1602,7 +1602,7 @@ namespace TeamAAS_VP.Core
 
                             // 执行视觉任务
                             var _cts = new CancellationTokenSource();
-                            var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint(process, null, new double[] { currentPosition.X, currentPosition.Y, currentPosition.U }, _cts.Token, cameraIndex, null,null);
+                            var visionResult = await _remoteCommandService.ExecutePhotoGetSinglePoint(process, null, new double[] { currentPosition.X, currentPosition.Y, currentPosition.U }, _cts.Token, cameraIndex, null, null);
                             if (visionResult.IsSucceed)
                             {
                                 await plc.WriteNodeAsync(addressConfig.Out_UPCameracheckStatus.Address, (Int16)1);
@@ -2199,10 +2199,11 @@ namespace TeamAAS_VP.Core
                                 {
                                     //这里缺少从MES获取产品配方的内容,需要补充
 
-                                    _LastMesCheckTime[i] = DateTime.UtcNow;
+
+                                    _LastMesCheckTime[i] = (await _mesService.GetServerTimeAsync(i, code, 2)).Now;
                                     LogHelper.WriteLogMes($"【mes开始QUERY】");
                                     SendTaskMessage($"mes开始QUERY", MessageLevel.Info);
-                                    (bool isSuccess, string response) = await _mesService.StationGetExAsync(code, "", 2);//基于电池的SN获取产品SN,询问mes
+                                    (bool isSuccess, string response) = await _mesService.StationGetExAsync(i, code, "", 2);//基于电池的SN获取产品SN,询问mes
                                     if (i == 0)
                                     {
                                         SendTaskMessage($"收到当前过站检查请求(当站是否生产)...", MessageLevel.Debug);
@@ -2216,10 +2217,10 @@ namespace TeamAAS_VP.Core
                                         //产品发生改变并且供料器发生了变化时,需要通知PLC进行取料更改
                                         await plc.WriteNodeAsync(addressConfig.Out_ScrewFeederIsChanged.Address, (Int16)1); //不发生改变
                                         await plc.WriteNodeAsync(string.Format(addressConfig.Out_MesStatus.Address, i), (Int16)1);
-                                        
+
                                         if (response != "MES功能未启用!")
                                         {
-                                            string[] item= response.Split('\n');
+                                            string[] item = response.Split('\n');
                                             if (item[3].Contains("sn="))
                                             {
                                                 ProductMainSN = item[3].Replace("sn=", "");
@@ -2236,7 +2237,7 @@ namespace TeamAAS_VP.Core
                                         SendTaskMessage($"询问mes成功:{response}", MessageLevel.Debug);
                                         if (response != "MES功能未启用!")
                                         {
-                                            string[] item= response.Split('\n');
+                                            string[] item = response.Split('\n');
                                             if (item[3].Contains("sn="))
                                             {
                                                 ProductMainSN = item[3].Replace("sn=", "");
@@ -2271,12 +2272,14 @@ namespace TeamAAS_VP.Core
                                 else if (state == 2)
                                 {
                                     SendTaskMessage($"收到{i}上传本站数据请求...", MessageLevel.Debug);
+                                    //从服务器中获取当前时间
+                                    var serverTimeResult = await _mesService.GetServerTimeAsync(i, code, 2);
 
                                     //这里缺少上传MES的本站数据结果及开始和结束时间,需要补充
 
                                     if (i == 0)
                                     {
-                                        CTtime = DateTime.UtcNow.Subtract(_LastMesCheckTime[i]).TotalSeconds;
+                                        CTtime = serverTimeResult.Now.Subtract(_LastMesCheckTime[i]).TotalSeconds;
                                     }
 
                                     //读取产品结果
@@ -2301,7 +2304,7 @@ namespace TeamAAS_VP.Core
                                     string torque = string.Join(",", torqueList);
                                     string pressure = string.Join(",", pressureList);
                                     string turn = string.Join(",", turnList);
-                                    (bool isSuccess, string response) = await _mesService.SubmitGetAsync(ProductMainSN, "", torque, pressure, turn, resultIndex == 1 ? true : false, _LastMesCheckTime[i], DateTime.UtcNow);
+                                    (bool isSuccess, string response) = await _mesService.SubmitGetAsync(i, ProductMainSN, "", torque, pressure, turn, resultIndex == 1 ? true : false, _LastMesCheckTime[i], serverTimeResult.Now);
                                     if (isSuccess)
                                     {
                                         LogHelper.WriteLogMes($"上传mes成功");

+ 177 - 146
TeamAAS-VM/Interfaces/IConfigService.cs

@@ -14,124 +14,124 @@ using TeamAAS_VP.Models.Torque;
 namespace TeamAAS_VP.Interfaces
 {
     /// <summary>
-    /// 配置管理服务:管理系统设置、相机、机器人、Feeder、PLC、后台通讯等配置的增删改查与持久化。
-    /// 实现类负责在内存与持久化存储(例如文件或数据库)之间同步数据,并提供同步与异步的加载/保存接口。
+    /// 锟斤拷锟矫癸拷锟斤拷锟斤拷锟今:癸拷锟斤拷系统锟斤拷锟矫★拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷恕锟紽eeder锟斤拷PLC锟斤拷锟斤拷台通讯锟斤拷锟斤拷锟矫碉拷锟斤拷删锟侥诧拷锟斤拷志没锟斤拷锟�
+    /// 实锟斤拷锟洁负锟斤拷锟斤拷锟节达拷锟斤拷志没锟斤拷娲�拷锟斤拷锟斤拷锟斤拷募锟斤拷锟斤拷锟斤拷菘猓┲�拷锟酵�拷锟斤拷锟斤拷荩锟斤拷锟斤拷峁┩�拷锟斤拷锟斤拷觳斤拷募锟斤拷锟�/锟斤拷锟斤拷涌凇锟�
     /// </summary>
     public interface IConfigService : IDisposable
     {
         // Load/Save
 
         /// <summary>
-        /// 同步加载所有配置(从持久化存储读取并更新内存中的配置)。
+        /// 同锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫o拷锟接持久伙拷锟芥储锟斤拷取锟斤拷锟斤拷锟斤拷锟节达拷锟叫碉拷锟斤拷锟矫o拷锟斤拷
         /// </summary>
         /// <remarks>
-        /// 该方法为阻塞调用,适用于应用启动时或明确需要同步加载的场景。
-        /// 实现应保证在发生错误时记录日志并在必要时抛出异常以通知调用方。
+        /// 锟矫凤拷锟斤拷为锟斤拷锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷应锟斤拷锟斤拷锟斤拷时锟斤拷锟斤拷确锟斤拷要同锟斤拷锟斤拷锟截的筹拷锟斤拷锟斤拷
+        /// 实锟斤拷应锟斤拷证锟节凤拷锟斤拷锟斤拷锟斤拷时锟斤拷录锟斤拷志锟斤拷锟节憋拷要时锟阶筹拷锟届常锟斤拷通知锟斤拷锟矫凤拷锟斤拷
         /// </remarks>
         void LoadAll();
 
         /// <summary>
-        /// 异步加载所有配置(从持久化存储读取并更新内存中的配置)。
+        /// 锟届步锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟矫o拷锟接持久伙拷锟芥储锟斤拷取锟斤拷锟斤拷锟斤拷锟节达拷锟叫碉拷锟斤拷锟矫o拷锟斤拷
         /// </summary>
-        /// <returns>一个表示异步加载操作的任务。</returns>
+        /// <returns>一锟斤拷锟斤拷示锟届步锟斤拷锟截诧拷锟斤拷锟斤拷锟斤拷锟斤拷</returns>
         Task LoadAllAsync();
 
         /// <summary>
-        /// 同步保存所有当前内存中的配置到持久化存储。
+        /// 同锟斤拷锟斤拷锟斤拷锟斤拷锟叫碉拷前锟节达拷锟叫碉拷锟斤拷锟矫碉拷锟街久伙拷锟芥储锟斤拷
         /// </summary>
         /// <remarks>
-        /// 该方法为阻塞调用,适用于需要立即保证配置持久化的场景。
+        /// 锟矫凤拷锟斤拷为锟斤拷锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟斤拷要锟斤拷锟斤拷锟斤拷证锟斤拷锟矫持久伙拷锟侥筹拷锟斤拷锟斤拷
         /// </remarks>
         void SaveAll();
 
         /// <summary>
-        /// 异步保存所有当前内存中的配置到持久化存储。
+        /// 锟届步锟斤拷锟斤拷锟斤拷锟叫碉拷前锟节达拷锟叫碉拷锟斤拷锟矫碉拷锟街久伙拷锟芥储锟斤拷
         /// </summary>
-        /// <returns>表示保存操作完成的任务。</returns>
+        /// <returns>锟斤拷示锟斤拷锟斤拷锟斤拷锟斤拷锟缴碉拷锟斤拷锟斤拷</returns>
         Task SaveAllAsync();
 
         // Cameras
 
         /// <summary>
-        /// 获取所有已注册的相机信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷只锟斤拷锟斤拷锟较★拷
         /// </summary>
-        /// <returns>相机信息的只读集合快照。</returns>
+        /// <returns>锟斤拷锟斤拷锟较�拷锟街伙拷锟斤拷锟斤拷峡锟斤拷铡锟�</returns>
         IReadOnlyCollection<CameraInfo> GetAllCameras();
 
         /// <summary>
-        /// 根据标识获取单个相机信息。
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟较�拷锟�
         /// </summary>
-        /// <param name="id">相机的唯一标识符。</param>
-        /// <returns>对应的 <see cref="CameraInfo"/> 实例;若未找到则返回 null。</returns>
+        /// <param name="id">锟斤拷锟斤拷锟轿ㄒ伙拷锟绞讹拷锟斤拷锟�</param>
+        /// <returns>锟斤拷应锟斤拷 <see cref="CameraInfo"/> 实锟斤拷锟斤拷锟斤拷未锟揭碉拷锟津返伙拷 null锟斤拷</returns>
         CameraInfo GetCamera(Guid id);
 
         /// <summary>
-        /// 新增或更新指定的相机信息。
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟街革拷锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="camera">要新增或更新的相机信息,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="camera"/> 为 null 时抛出。</exception>
+        /// <param name="camera">要锟斤拷锟斤拷锟斤拷锟斤拷碌锟斤拷锟斤拷锟斤拷息锟斤拷锟斤拷锟斤拷为 null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="camera"/> 为 null 时锟阶筹拷锟斤拷</exception>
         CameraInfo AddOrUpdateCamera(CameraInfo camera);
 
         /// <summary>
-        /// 移除指定标识的相机信息。
+        /// 锟狡筹拷指锟斤拷锟斤拷识锟斤拷锟斤拷锟斤拷锟较�拷锟�
         /// </summary>
-        /// <param name="id">要移除的相机的唯一标识符。</param>
-        /// <returns>如果找到并移除成功则返回 true;否则返回 false。</returns>
+        /// <param name="id">要锟狡筹拷锟斤拷锟斤拷锟斤拷锟轿ㄒ伙拷锟绞讹拷锟斤拷锟�</param>
+        /// <returns>锟斤拷锟斤拷业锟斤拷锟斤拷瞥锟斤拷晒锟斤拷蚍祷锟� true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool RemoveCamera(Guid id);
 
         /// <summary>
-        /// 判断是否包含指定标识的相机配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷锟斤拷锟斤拷锟斤拷锟矫★拷
         /// </summary>
-        /// <param name="id">相机唯一标识符。</param>
-        /// <returns>存在则返回 true,否则返回 false。</returns>
+        /// <param name="id">锟斤拷锟轿ㄒ伙拷锟绞讹拷锟斤拷锟�</param>
+        /// <returns>锟斤拷锟斤拷锟津返伙拷 true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool ContainsCamera(Guid id);
 
         // Robots
 
         /// <summary>
-        /// 获取所有已注册的机器人信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷幕锟斤拷锟斤拷锟斤拷锟较�拷锟街伙拷锟斤拷锟斤拷稀锟�
         /// </summary>
-        /// <returns>机器人信息的只读集合快照。</returns>
+        /// <returns>锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷只锟斤拷锟斤拷锟较匡拷锟秸★拷</returns>
         IReadOnlyCollection<RobotInfo> GetAllRobots();
 
         /// <summary>
-        /// 根据标识获取单个机器人信息。
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="id">机器人的唯一标识符。</param>
-        /// <returns>对应的 <see cref="RobotInfo"/> 实例;若未找到则返回 null。</returns>
+        /// <param name="id">锟斤拷锟斤拷锟剿碉拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷应锟斤拷 <see cref="RobotInfo"/> 实锟斤拷锟斤拷锟斤拷未锟揭碉拷锟津返伙拷 null锟斤拷</returns>
         RobotInfo GetRobot(Guid id);
 
         /// <summary>
-        /// 新增或更新指定的机器人信息。
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟街革拷锟斤拷幕锟斤拷锟斤拷锟斤拷锟较�拷锟�
         /// </summary>
-        /// <param name="robot">要新增或更新的机器人信息,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="robot"/> 为 null 时抛出。</exception>
+        /// <param name="robot">要锟斤拷锟斤拷锟斤拷锟斤拷碌幕锟斤拷锟斤拷锟斤拷锟较�拷锟斤拷锟斤拷锟轿� null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="robot"/> 为 null 时锟阶筹拷锟斤拷</exception>
         RobotInfo AddOrUpdateRobot(RobotInfo robot);
 
         /// <summary>
-        /// 移除指定标识的机器人信息。
+        /// 锟狡筹拷指锟斤拷锟斤拷识锟侥伙拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="id">要移除的机器人的唯一标识符。</param>
-        /// <returns>如果找到并移除成功则返回 true;否则返回 false。</returns>
+        /// <param name="id">要锟狡筹拷锟侥伙拷锟斤拷锟剿碉拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷业锟斤拷锟斤拷瞥锟斤拷晒锟斤拷蚍祷锟� true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool RemoveRobot(Guid id);
 
         /// <summary>
-        /// 判断是否包含指定标识的机器人配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷幕锟斤拷锟斤拷锟斤拷锟斤拷谩锟�
         /// </summary>
-        /// <param name="id">机器人唯一标识符。</param>
-        /// <returns>存在则返回 true,否则返回 false。</returns>
+        /// <param name="id">锟斤拷锟斤拷锟斤拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷锟津返伙拷 true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool ContainsRobot(Guid id);
 
         /// <summary>
-        /// 修改指定机器人id的步进距离
+        /// 锟睫革拷指锟斤拷锟斤拷锟斤拷锟斤拷id锟侥诧拷锟斤拷锟斤拷锟斤拷
         /// </summary>
-        /// <param name="id">机器人唯一标识符。</param>
-        /// <param name="stepDistance">步进距离</param>
+        /// <param name="id">锟斤拷锟斤拷锟斤拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <param name="stepDistance">锟斤拷锟斤拷锟斤拷锟斤拷</param>
         /// <returns></returns>
         bool UpdateRobotStepDistance(Guid id, double stepDistance);
 
         /// <summary>
-        /// 获取指定机器人Id的步进距离
+        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷Id锟侥诧拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="id"></param>
         /// <returns></returns>
@@ -140,73 +140,73 @@ namespace TeamAAS_VP.Interfaces
         // Feeders
 
         /// <summary>
-        /// 获取所有已注册的 Feeder 信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷锟� Feeder 锟斤拷息锟斤拷只锟斤拷锟斤拷锟较★拷
         /// </summary>
-        /// <returns>Feeder 信息的只读集合快照。</returns>
+        /// <returns>Feeder 锟斤拷息锟斤拷只锟斤拷锟斤拷锟较匡拷锟秸★拷</returns>
         IReadOnlyCollection<FeederInfo> GetAllFeeders();
 
         /// <summary>
-        /// 根据标识获取单个 Feeder 信息。
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷 Feeder 锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="id">Feeder 的唯一标识符。</param>
-        /// <returns>对应的 <see cref="FeederInfo"/> 实例;若未找到则返回 null。</returns>
+        /// <param name="id">Feeder 锟斤拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷应锟斤拷 <see cref="FeederInfo"/> 实锟斤拷锟斤拷锟斤拷未锟揭碉拷锟津返伙拷 null锟斤拷</returns>
         FeederInfo GetFeeder(Guid id);
 
         /// <summary>
-        /// 新增或更新指定的 Feeder 信息。
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟街革拷锟斤拷锟� Feeder 锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="feeder">要新增或更新的 Feeder 信息,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="feeder"/> 为 null 时抛出。</exception>
+        /// <param name="feeder">要锟斤拷锟斤拷锟斤拷锟斤拷碌锟� Feeder 锟斤拷息锟斤拷锟斤拷锟斤拷为 null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="feeder"/> 为 null 时锟阶筹拷锟斤拷</exception>
         FeederInfo AddOrUpdateFeeder(FeederInfo feeder);
 
         /// <summary>
-        /// 移除指定标识的 Feeder 信息。
+        /// 锟狡筹拷指锟斤拷锟斤拷识锟斤拷 Feeder 锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="id">要移除的 Feeder 的唯一标识符。</param>
-        /// <returns>如果找到并移除成功则返回 true;否则返回 false。</returns>
+        /// <param name="id">要锟狡筹拷锟斤拷 Feeder 锟斤拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷业锟斤拷锟斤拷瞥锟斤拷晒锟斤拷蚍祷锟� true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool RemoveFeeder(Guid id);
 
         /// <summary>
-        /// 判断是否包含指定标识的 Feeder 配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷锟� Feeder 锟斤拷锟矫★拷
         /// </summary>
-        /// <param name="id">Feeder 唯一标识符。</param>
-        /// <returns>存在则返回 true,否则返回 false。</returns>
+        /// <param name="id">Feeder 唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷锟津返伙拷 true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool ContainsFeeder(Guid id);
 
         // PLCs
 
         /// <summary>
-        /// 获取所有已注册的 PLC 信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷锟� PLC 锟斤拷息锟斤拷只锟斤拷锟斤拷锟较★拷
         /// </summary>
-        /// <returns>PLC 信息的只读集合快照。</returns>
+        /// <returns>PLC 锟斤拷息锟斤拷只锟斤拷锟斤拷锟较匡拷锟秸★拷</returns>
         IReadOnlyCollection<PlcInfo> GetAllPlcs();
 
         /// <summary>
-        /// 根据标识获取单个 PLC 信息。
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷 PLC 锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="id">PLC 的唯一标识符。</param>
-        /// <returns>对应的 <see cref="PlcInfo"/> 实例;若未找到则返回 null。</returns>
+        /// <param name="id">PLC 锟斤拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷应锟斤拷 <see cref="PlcInfo"/> 实锟斤拷锟斤拷锟斤拷未锟揭碉拷锟津返伙拷 null锟斤拷</returns>
         PlcInfo GetPlc(Guid id);
 
         /// <summary>
-        /// 新增或更新指定的 PLC 信息。
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟街革拷锟斤拷锟� PLC 锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="plc">要新增或更新的 PLC 信息,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="plc"/> 为 null 时抛出。</exception>
+        /// <param name="plc">要锟斤拷锟斤拷锟斤拷锟斤拷碌锟� PLC 锟斤拷息锟斤拷锟斤拷锟斤拷为 null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="plc"/> 为 null 时锟阶筹拷锟斤拷</exception>
         PlcInfo AddOrUpdatePlc(PlcInfo plc);
 
         /// <summary>
-        /// 移除指定标识的 PLC 信息。
+        /// 锟狡筹拷指锟斤拷锟斤拷识锟斤拷 PLC 锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="id">要移除的 PLC 的唯一标识符。</param>
-        /// <returns>如果找到并移除成功则返回 true;否则返回 false。</returns>
+        /// <param name="id">要锟狡筹拷锟斤拷 PLC 锟斤拷唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷业锟斤拷锟斤拷瞥锟斤拷晒锟斤拷蚍祷锟� true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool RemovePlc(Guid id);
 
         /// <summary>
-        /// 判断是否包含指定标识的 PLC 配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷锟� PLC 锟斤拷锟矫★拷
         /// </summary>
-        /// <param name="id">PLC 唯一标识符。</param>
-        /// <returns>存在则返回 true,否则返回 false。</returns>
+        /// <param name="id">PLC 唯一锟斤拷识锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷锟津返伙拷 true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool ContainsPlc(Guid id);
 
         // Plc Addresses
@@ -220,118 +220,118 @@ namespace TeamAAS_VP.Interfaces
         // Background TCP
 
         /// <summary>
-        /// 获取后台通讯(TCP/IP)配置。
+        /// 锟斤拷取锟斤拷台通讯锟斤拷TCP/IP锟斤拷锟斤拷锟矫★拷
         /// </summary>
-        /// <returns>当前后台 TCP 配置快照。</returns>
+        /// <returns>锟斤拷前锟斤拷台 TCP 锟斤拷锟矫匡拷锟秸★拷</returns>
         BgTcpIP GetBgTcp();
 
         /// <summary>
-        /// 设置后台通讯(TCP/IP)配置(仅更新内存)。
+        /// 锟斤拷锟矫猴拷台通讯锟斤拷TCP/IP锟斤拷锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟节存)锟斤拷
         /// </summary>
-        /// <param name="cfg">新的后台 TCP 配置,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="cfg"/> 为 null 时抛出。</exception>
+        /// <param name="cfg">锟铰的猴拷台 TCP 锟斤拷锟矫o拷锟斤拷锟斤拷为 null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="cfg"/> 为 null 时锟阶筹拷锟斤拷</exception>
         void SetBgTcp(BgTcpIP cfg);
 
         /// <summary>
-        /// 将当前内存中的后台 TCP 配置保存到持久化存储。
+        /// 锟斤拷锟斤拷前锟节达拷锟叫的猴拷台 TCP 锟斤拷锟矫憋拷锟芥到锟街久伙拷锟芥储锟斤拷
         /// </summary>
         void SaveBgTcp();
 
         // Background Modbus
 
         /// <summary>
-        /// 获取后台 Modbus TCP 配置。
+        /// 锟斤拷取锟斤拷台 Modbus TCP 锟斤拷锟矫★拷
         /// </summary>
-        /// <returns>当前后台 Modbus TCP 配置快照。</returns>
+        /// <returns>锟斤拷前锟斤拷台 Modbus TCP 锟斤拷锟矫匡拷锟秸★拷</returns>
         BgModbusTcp GetBgModbusTcp();
 
         /// <summary>
-        /// 设置后台 Modbus TCP 配置(仅更新内存)。
+        /// 锟斤拷锟矫猴拷台 Modbus TCP 锟斤拷锟矫o拷锟斤拷锟斤拷锟斤拷锟节存)锟斤拷
         /// </summary>
-        /// <param name="cfg">新的后台 Modbus TCP 配置,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="cfg"/> 为 null 时抛出。</exception>
+        /// <param name="cfg">锟铰的猴拷台 Modbus TCP 锟斤拷锟矫o拷锟斤拷锟斤拷为 null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="cfg"/> 为 null 时锟阶筹拷锟斤拷</exception>
         void SetBgModbusTcp(BgModbusTcp cfg);
 
         /// <summary>
-        /// 将当前内存中的后台 Modbus TCP 配置保存到持久化存储。
+        /// 锟斤拷锟斤拷前锟节达拷锟叫的猴拷台 Modbus TCP 锟斤拷锟矫憋拷锟芥到锟街久伙拷锟芥储锟斤拷
         /// </summary>
         void SaveBgModbusTcp();
 
-        //系统设置
+        //系统锟斤拷锟斤拷
 
         /// <summary>
-        /// 获取系统设置
+        /// 锟斤拷取系统锟斤拷锟斤拷
         /// </summary>
         /// <returns></returns>
         SystemConfiguration GetSystemConfiguration();
 
         /// <summary>
-        /// 保存系统设置
+        /// 锟斤拷锟斤拷系统锟斤拷锟斤拷
         /// </summary>
         void SaveSystemConfiguration(SystemConfiguration systemConfiguration);
 
         /// <summary>
-        /// 获取当前语言
+        /// 锟斤拷取锟斤拷前锟斤拷锟斤拷
         /// </summary>
         /// <returns></returns>
         Language GetCurrentLanguage();
 
         /// <summary>
-        /// 设置当前语言
+        /// 锟斤拷锟矫碉拷前锟斤拷锟斤拷
         /// </summary>
         /// <param name="language"></param>
         void SetCurrentLanguage(Language language);
 
-        //清料任务
+        //锟斤拷锟斤拷锟斤拷锟斤拷
 
         /// <summary>
-        /// 获取所有清料任务
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <returns></returns>
         IReadOnlyCollection<FeederClearWork> GetAllClearanceTasks();
 
         /// <summary>
-        /// 新增或更新清料任务
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟�
         /// </summary>
         /// <param name="task"></param>
         /// <returns></returns>
         FeederClearWork AddOrUpdateClearanceTask(FeederClearWork task);
 
         /// <summary>
-        /// 移除清料任务
+        /// 锟狡筹拷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="id"></param>
         /// <returns></returns>
         bool RemoveClearanceTask(Guid id);
 
         /// <summary>
-        /// 清除所有清料任务
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟�
         /// </summary>
         void ClearAllClearanceTasks();
 
         /// <summary>
-        /// 判断是否包含指定标识的清料任务
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷锟斤拷锟斤拷锟斤拷锟斤拷锟�
         /// </summary>
         /// <param name="id"></param>
         /// <returns></returns>
         bool ContainsClearanceTask(Guid id);
 
         /// <summary>
-        /// 根据标识获取单个清料任务信
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="id"></param>
         /// <returns></returns>
         FeederClearWork GetClearanceTask(Guid id);
 
         /// <summary>
-        /// 判断是否包含指定编号的清料任务
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟脚碉拷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="taskNumber"></param>
         /// <returns></returns>
         bool ContainsClearanceTaskByNumber(int taskNumber);
 
         /// <summary>
-        /// 根据编号获取单个清料任务信息
+        /// 锟斤拷锟捷憋拷呕锟饺★拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟较�
         /// </summary>
         /// <param name="taskNumber"></param>
         /// <returns></returns>
@@ -340,166 +340,197 @@ namespace TeamAAS_VP.Interfaces
         // Light controllers and channels
 
         /// <summary>
-        /// 获取所有已注册的灯光控制器信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷牡乒锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷只锟斤拷锟斤拷锟较★拷
         /// </summary>
-        /// <returns>灯光控制器信息的只读集合快照。</returns>
+        /// <returns>锟狡癸拷锟斤拷锟斤拷锟斤拷锟较�拷锟街伙拷锟斤拷锟斤拷峡锟斤拷铡锟�</returns>
         IReadOnlyCollection<LightControllerConfig> GetAllLightControllers();
 
         /// <summary>
-        /// 根据标识获取单个灯光控制器信息。
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟狡癸拷锟斤拷锟斤拷锟斤拷锟较�拷锟�
         /// </summary>
-        /// <param name="id">灯光控制器的唯一标识符。</param>
-        /// <returns>对应的 <see cref="LightControllerConfig"/> 实例;若未找到则返回 null。</returns>
+        /// <param name="id">锟狡癸拷锟斤拷锟斤拷锟斤拷锟轿ㄒ伙拷锟绞讹拷锟斤拷锟�</param>
+        /// <returns>锟斤拷应锟斤拷 <see cref="LightControllerConfig"/> 实锟斤拷锟斤拷锟斤拷未锟揭碉拷锟津返伙拷 null锟斤拷</returns>
         LightControllerConfig GetLightController(int id);
 
         /// <summary>
-        /// 新增或更新指定的灯光控制器信息。
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟街革拷锟斤拷牡乒锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="controller">要新增或更新的灯光控制器信息,不能为 null。</param>
-        /// <exception cref="ArgumentNullException">当 <paramref name="controller"/> 为 null 时抛出。</exception>
+        /// <param name="controller">要锟斤拷锟斤拷锟斤拷锟斤拷碌牡乒锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷锟斤拷锟斤拷为 null锟斤拷</param>
+        /// <exception cref="ArgumentNullException">锟斤拷 <paramref name="controller"/> 为 null 时锟阶筹拷锟斤拷</exception>
         LightControllerConfig AddOrUpdateLightController(LightControllerConfig controller);
 
         /// <summary>
-        /// 移除指定标识的灯光控制器信息。
+        /// 锟狡筹拷指锟斤拷锟斤拷识锟侥灯癸拷锟斤拷锟斤拷锟斤拷锟较�拷锟�
         /// </summary>
-        /// <param name="id">要移除的灯光控制器的唯一标识符。</param>
-        /// <returns>如果找到并移除成功则返回 true;否则返回 false。</returns>
+        /// <param name="id">要锟狡筹拷锟侥灯癸拷锟斤拷锟斤拷锟斤拷锟轿ㄒ伙拷锟绞讹拷锟斤拷锟�</param>
+        /// <returns>锟斤拷锟斤拷业锟斤拷锟斤拷瞥锟斤拷晒锟斤拷蚍祷锟� true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool RemoveLightController(int id);
 
         /// <summary>
-        /// 判断是否包含指定标识的灯光控制器配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷牡乒锟斤拷锟斤拷锟斤拷锟斤拷锟矫★拷
         /// </summary>
-        /// <param name="id">灯光控制器唯一标识符。</param>
-        /// <returns>存在则返回 true,否则返回 false。</returns>
+        /// <param name="id">锟狡癸拷锟斤拷锟斤拷锟轿ㄒ伙拷锟绞讹拷锟斤拷锟�</param>
+        /// <returns>锟斤拷锟斤拷锟津返伙拷 true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool ContainsLightController(int id);
 
         /// <summary>
-        /// 获取所有已注册的灯光通道信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷牡乒锟酵�拷锟斤拷锟较�拷锟街伙拷锟斤拷锟斤拷稀锟�
         /// </summary>
-        /// <returns>灯光通道信息的只读集合快照。</returns>
+        /// <returns>锟狡癸拷通锟斤拷锟斤拷息锟斤拷只锟斤拷锟斤拷锟较匡拷锟秸★拷</returns>
         IReadOnlyCollection<ChannelConfig> GetAllLightChannels();
 
         /// <summary>
-        /// 根据全局索引获取单个灯光通道信息。
+        /// 锟斤拷锟斤拷全锟斤拷锟斤拷锟斤拷锟斤拷取锟斤拷锟斤拷锟狡癸拷通锟斤拷锟斤拷息锟斤拷
         /// </summary>
-        /// <param name="globalIndex">灯光通道的全局索引。</param>
-        /// <returns>对应的 <see cref="ChannelConfig"/> 实例;若未找到则返回 null。</returns>
+        /// <param name="globalIndex">锟狡癸拷通锟斤拷锟斤拷全锟斤拷锟斤拷锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷应锟斤拷 <see cref="ChannelConfig"/> 实锟斤拷锟斤拷锟斤拷未锟揭碉拷锟津返伙拷 null锟斤拷</returns>
         ChannelConfig GetLightChannelByGlobalIndex(int globalIndex);
 
         /// <summary>
-        /// 判断是否包含指定全局索引的灯光通道配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟饺�拷锟斤拷锟斤拷锟斤拷牡乒锟酵�拷锟斤拷锟斤拷谩锟�
         /// </summary>
-        /// <param name="globalIndex">灯光通道的全局索引。</param>
-        /// <returns>存在则返回 true,否则返回 false。</returns>
+        /// <param name="globalIndex">锟狡癸拷通锟斤拷锟斤拷全锟斤拷锟斤拷锟斤拷锟斤拷</param>
+        /// <returns>锟斤拷锟斤拷锟津返伙拷 true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool ContainsLightChannelByGlobalIndex(int globalIndex);
 
         /// <summary>
-        /// 更新指定灯光通道的默认亮度值并保存到持久化存储。
+        /// 锟斤拷锟斤拷指锟斤拷锟狡癸拷通锟斤拷锟斤拷默锟斤拷锟斤拷锟斤拷值锟斤拷锟斤拷锟芥到锟街久伙拷锟芥储锟斤拷
         /// </summary>
-        /// <param name="globalIndex">灯光通道的全局索引。</param>
-        /// <param name="brightness">新的默认亮度值,范围应在合适的区间内。</param>
-        /// <returns>如果找到并更新成功则返回 true;否则返回 false。</returns>
+        /// <param name="globalIndex">锟狡癸拷通锟斤拷锟斤拷全锟斤拷锟斤拷锟斤拷锟斤拷</param>
+        /// <param name="brightness">锟铰碉拷默锟斤拷锟斤拷锟斤拷值锟斤拷锟斤拷围应锟节猴拷锟绞碉拷锟斤拷锟斤拷锟节★拷</param>
+        /// <returns>锟斤拷锟斤拷业锟斤拷锟斤拷锟斤拷鲁晒锟斤拷蚍祷锟� true锟斤拷锟斤拷锟津返伙拷 false锟斤拷</returns>
         bool UpdateChannelDefaultBrightness(int globalIndex, int brightness);
 
         // Screw driver (single device) configuration accessors
         /// <summary>
-        /// 获取电批配置(单设备)。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷锟矫o拷锟斤拷锟借备锟斤拷锟斤拷
         /// </summary>
-        /// <returns>当前的 <see cref="ScrewDriverConfig"/>,若未加载则返回 null。</returns>
+        /// <returns>锟斤拷前锟斤拷 <see cref="ScrewDriverConfig"/>锟斤拷锟斤拷未锟斤拷锟斤拷锟津返伙拷 null锟斤拷</returns>
         ScrewDriverConfig GetScrewDriverConfig();
 
         /// <summary>
-        /// 设置并持久化电批配置(单设备)。
+        /// 锟斤拷锟矫诧拷锟街久伙拷锟斤拷锟斤拷锟斤拷锟矫o拷锟斤拷锟借备锟斤拷锟斤拷
         /// </summary>
-        /// <param name="cfg">新的配置对象,不能为空。</param>
+        /// <param name="cfg">锟铰碉拷锟斤拷锟矫讹拷锟襟,诧拷锟斤拷为锟秸★拷</param>
         void SetScrewDriverConfig(ScrewDriverConfig cfg);
 
-        // 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>
+        /// 锟斤拷锟斤拷锟斤拷前锟节达拷锟叫的碉拷锟斤拷锟斤拷锟矫憋拷锟芥到锟侥硷拷锟斤拷
         /// </summary>
         void SaveScrewDriverConfig();
 
         // Screw feeder (material) management
 
         /// <summary>
-        /// 获取所有已注册的螺丝供料器信息的只读集合。
+        /// 锟斤拷取锟斤拷锟斤拷锟斤拷注锟斤拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷锟较�拷锟街伙拷锟斤拷锟斤拷稀锟�
         /// </summary>
         IReadOnlyCollection<ScrewFeederInfo> GetAllScrewFeeders();
 
         /// <summary>
-        /// 根据标识获取单个螺丝供料器信息。
+        /// 锟斤拷锟捷憋拷识锟斤拷取锟斤拷锟斤拷锟斤拷丝锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
         ScrewFeederInfo GetScrewFeeder(Guid id);
 
         /// <summary>
-        /// 新增或更新指定的螺丝供料器信息。
+        /// 锟斤拷锟斤拷锟斤拷锟斤拷锟街革拷锟斤拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷锟较�拷锟�
         /// </summary>
         ScrewFeederInfo AddOrUpdateScrewFeeder(ScrewFeederInfo feeder);
 
         /// <summary>
-        /// 移除指定标识的螺丝供料器信息。
+        /// 锟狡筹拷指锟斤拷锟斤拷识锟斤拷锟斤拷丝锟斤拷锟斤拷锟斤拷锟斤拷息锟斤拷
         /// </summary>
         bool RemoveScrewFeeder(Guid id);
 
         /// <summary>
-        /// 判断是否包含指定标识的螺丝供料器配置。
+        /// 锟叫讹拷锟角凤拷锟斤拷锟街革拷锟斤拷锟绞讹拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷锟斤拷谩锟�
         /// </summary>
         bool ContainsScrewFeeder(Guid id);
 
         /// <summary>
-        /// 为指定供料器编号递减指定数量(通常为 1),若数量不足返回 false
+        /// 为指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺菁锟街革拷锟斤拷锟斤拷锟斤拷锟酵�拷锟轿� 1锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟姐返锟斤拷 false
         /// </summary>
         int DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1);
 
         /// <summary>
-        /// 清除指定供料器编号的螺丝信息(批次号与剩余数量置空/置0)
+        /// 锟斤拷锟街革拷锟斤拷锟斤拷锟斤拷锟斤拷锟脚碉拷锟斤拷丝锟斤拷息锟斤拷锟斤拷锟轿猴拷锟斤拷剩锟斤拷锟斤拷锟斤拷锟矫匡拷/锟斤拷0锟斤拷
         /// </summary>
         void ClearScrewFeederByNumber(int feederNumber);
 
         /// <summary>
-        /// 为指定供料器编号设置螺丝批次号,并在原有剩余数量上增加一包的数量(PackSize)
+        /// 为指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟剿匡拷锟斤拷魏牛锟斤拷锟斤拷锟皆�拷锟绞o拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟揭伙拷锟斤拷锟斤拷锟斤拷锟斤拷锟絇ackSize锟斤拷
         /// </summary>
         void SetBatchForFeederNumber(int feederNumber, string batchNumber);
 
         /// <summary>
-        /// 获取指定供料器编号的剩余螺丝数量(找不到返回 -1)
+        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺锟绞o拷锟斤拷锟剿匡拷锟斤拷锟斤拷锟斤拷也锟斤拷锟斤拷锟斤拷锟� -1锟斤拷
         /// </summary>
         int GetRemainingCountByFeederNumber(int feederNumber);
 
         /// <summary>
-        /// 获取指定供料器编号的低量报警状态(找不到返回 false)
+        /// 锟斤拷取指锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷诺牡锟斤拷锟斤拷锟斤拷锟阶刺�拷锟斤拷也锟斤拷锟斤拷锟斤拷锟� false锟斤拷
         /// </summary>
         bool GetLowLevelAlarmStatusByFeederNumber(int feederNumber);
 
         /// <summary>
-        /// 当某个螺丝供料器触发低量报警时会触发此事件,订阅者可用于上报或 UI 提示。
+        /// 锟斤拷某锟斤拷锟斤拷丝锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷时锟结触锟斤拷锟斤拷锟铰硷拷锟斤拷锟斤拷锟斤拷锟竭匡拷锟斤拷锟斤拷锟较憋拷锟斤拷 UI 锟斤拷示锟斤拷
         /// </summary>
         event Action<ScrewFeederInfo> ScrewFeederLowLevelAlarmTriggered;
 
         /// <summary>
-        /// 检查剩余螺丝是否报警
+        /// 锟斤拷锟绞o拷锟斤拷锟剿匡拷欠癖ň锟�
         /// </summary>
         bool CheckAlarm(int feederNumber);
 
-        //刷卡登
+        //刷锟斤拷锟斤拷
         /// <summary>
-        /// 获取刷卡器配置
+        /// 锟斤拷取刷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <returns></returns>
         SerialPortConfig GetCardReaderConfig();
         /// <summary>
-        /// 保存刷卡器配置
+        /// 锟斤拷锟斤拷刷锟斤拷锟斤拷锟斤拷锟斤拷
         /// </summary>
         /// <param name="config"></param>
         void SaveCardReaderConfig(SerialPortConfig config);

+ 20 - 5
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,9 +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);
 
-        Task<(bool IsSuccess, string Response)> StationGetExAsync(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 请求,提交过站结果及时间信息。
@@ -65,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 torque, string pressure, string turn, bool isSuccess, DateTime start_time, DateTime stop_time, CancellationToken cancellationToken = default);
+        Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string torque, string pressure, string turn, bool isSuccess, DateTime start_time, DateTime stop_time, CancellationToken cancellationToken = default);
 
         /// <summary>
         /// 使用配置的提交(Submit)URL 发起 GET 请求,提交过站结果及时间信息,并使用指定的超时时间(秒)。
@@ -84,6 +91,14 @@ namespace TeamAAS_VP.Interfaces
         /// - 此重载用于在不传入 <see cref="CancellationToken"/> 的场景中指定超时时间。
         /// - 实现应确保对时间参数与布尔参数进行适当的 URL 编码或格式化。
         /// </remarks>
-        Task<(bool IsSuccess, string Response)> SubmitGetAsync(string sn,string comp, string torque, string pressure, string turn, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds);
+        Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string torque, string pressure, string turn, 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);
     }
 }

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

@@ -5,31 +5,49 @@ 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); }
 
@@ -41,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;

Plik diff jest za duży
+ 337 - 196
TeamAAS-VM/Services/ConfigService.cs


+ 92 - 15
TeamAAS-VM/Services/MesService.cs

@@ -3,6 +3,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;
@@ -79,12 +80,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功能未启用!");
@@ -108,7 +111,7 @@ namespace TeamAAS_VP.Services
             {
                 url = $"{device.MesUrl}?c=QUERY_RECORD&line={device.Line}&station={device.Station}&fixtureid={device.FixtureId}&sn={sn}&p=unit_process_check,message,model,sn,wo";
             }
-            
+
             LogHelper.WriteLogMes($"【询问URL】{url}");
             SendTaskMessage($"【询问URL】{url}", MessageLevel.Info);
             (bool IsSuccess, string Response) result = (false, string.Empty);
@@ -165,12 +168,14 @@ namespace TeamAAS_VP.Services
         /// - 捕获 <see cref="OperationCanceledException"/> 返回 "Canceled",捕获其它异常则返回异常消息。
         /// - 若实例已被释放则抛出 <see cref="ObjectDisposedException"/>。
         /// </remarks>
-        public async Task<(bool IsSuccess, string Response)> StationGetExAsync(string sn, string comp, CancellationToken cancellationToken = default)
+        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();
+            var device = _configService.GetDeviceInfo(deviceNo);
+            if (device == null)
+                device = _configService.GetDeviceInfo();
             if (device == null || !device.EnableMES)
             {
                 return (true, "MES功能未启用!");
@@ -225,19 +230,19 @@ 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);
             }
         }
 
-        public async Task<(bool IsSuccess, string Response)> StationGetExAsync(string sn, string comp, double timeoutInSeconds)
+        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(sn, comp, cts.Token);
+                return await StationGetExAsync(deviceNo, sn, comp, cts.Token);
             }
         }
 
@@ -263,12 +268,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 torque,string pressure,string turn, 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 torque, string pressure, string turn, 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功能未启用!");
@@ -286,7 +293,7 @@ namespace TeamAAS_VP.Services
             //string res = isSuccess ? "PASS" : "FAIL";
             string res = "PASS";
             string url = "";
-            if (device.F=="PTL")
+            if (device.F == "PTL")
             {
                 url = $"{device.MesUrl}?c=ADD_RECORD&line={device.Line}&station={device.Station}&fixtureid={device.FixtureId}&sn={sn}&f=PTL&comp={device.comp}:{comp}" +
                     $"&torque={torque}&pressure={pressure}&turn={turn}&result={res}&start_time={start_time.ToString("yyyy-MM-dd HH:mm:ss")}&stop_time={stop_time.ToString("yyyy-MM-dd HH:mm:ss")}";
@@ -342,13 +349,83 @@ namespace TeamAAS_VP.Services
         /// <param name="start_time">开始时间。</param>
         /// <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 torque, string pressure, string turn, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds)
+        /// <returns>与 <see cref="SubmitGetAsync(int deviceNo, string, bool, DateTime, DateTime, CancellationToken)"/> 相同的返回语义。</returns>
+        public async Task<(bool IsSuccess, string Response)> SubmitGetAsync(int deviceNo, string sn, string comp, string torque, string pressure, string turn, bool isSuccess, DateTime start_time, DateTime stop_time, double timeoutInSeconds)
         {
             using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)))
             {
-                return await SubmitGetAsync(sn, comp,torque,pressure,turn, isSuccess, start_time, stop_time, cts.Token);
+                return await SubmitGetAsync(deviceNo,sn, comp, torque, pressure, turn, 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>

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

@@ -19,6 +19,7 @@ using System.Linq;
 using System.Reflection.Metadata;
 using System.Text;
 using System.Windows;
+using System.Windows.Forms;
 using Team.FFFeederService.Interfaces;
 using TeamAAS_VP.Controls;
 using TeamAAS_VP.Core;
@@ -329,11 +330,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 CTQ_Data _dataInfo;
@@ -446,6 +475,12 @@ namespace TeamAAS_VP.ViewModels
         private DelegateCommand _SaveDeviceInfoCommand;
         public DelegateCommand SaveDeviceInfoCommand => _SaveDeviceInfoCommand ?? (_SaveDeviceInfoCommand = new DelegateCommand(ExecuteSaveDeviceInfoCommand));
 
+        private DelegateCommand _AddDeviceInfoCommand;
+        public DelegateCommand AddDeviceInfoCommand => _AddDeviceInfoCommand ?? (_AddDeviceInfoCommand = new DelegateCommand(ExecuteAddDeviceInfoCommand));
+
+        private DelegateCommand _RemoveDeviceInfoCommand;
+        public DelegateCommand RemoveDeviceInfoCommand => _RemoveDeviceInfoCommand ?? (_RemoveDeviceInfoCommand = new DelegateCommand(ExecuteRemoveDeviceInfoCommand));
+
         private DelegateCommand _SaveDataInfoCommand;
         public DelegateCommand SaveDataInfoCommand => _SaveDataInfoCommand ?? (_SaveDataInfoCommand = new DelegateCommand(ExecuteSaveDataInfoCommand));
 
@@ -480,6 +515,10 @@ namespace TeamAAS_VP.ViewModels
         public DelegateCommand SaveWorkShiftCommand =>
             _SaveWorkShiftCommand ?? (_SaveWorkShiftCommand = new DelegateCommand(ExecuteSaveWorkShiftCommand));
 
+        private DelegateCommand _SelectSaveImagePathCommand;
+        public DelegateCommand CommaSelectSaveImagePathCommandndName =>
+            _SelectSaveImagePathCommand ?? (_SelectSaveImagePathCommand = new DelegateCommand(ExecuteCommaSelectSaveImagePathCommandndName));
+
         #endregion
 
         #region 事件
@@ -1382,10 +1421,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 (DataInfo == null)
@@ -1706,17 +1751,73 @@ 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)
             {
@@ -1739,6 +1840,30 @@ namespace TeamAAS_VP.ViewModels
                 _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
             }
         }
+
+        /// <summary>
+        /// 选择CSV存储路径
+        /// </summary>
+        void ExecuteCommaSelectSaveImagePathCommandndName()
+        {
+            if (SelectDeviceInfo == null)
+            {
+                return;
+            }
+            using (FolderBrowserDialog dialog = new 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
     }
 }

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

@@ -228,237 +228,374 @@
                     </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" />
-
-                        <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" />
-
-                        <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" />
-
-                        <TextBlock Grid.Row="6"
-                                   Grid.Column="0"
-                                   Text="启用 MES:"
-                                   HorizontalAlignment="Right"
-                                   VerticalAlignment="Center" />
-                        <CheckBox Grid.Row="6"
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="auto" />
+                        <ColumnDefinition Width="*" />
+                    </Grid.ColumnDefinitions>
+                    
+                    <!--增删改查按钮-->
+                    <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>
+                    
+                    <!--设备列表-->
+                    <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>
+                    
+                    <!--参数详情-->
+                    <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>
-
-                    <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" />-->
+                                  IsEnabled="{Binding IsAllowEdit}"
+                                  Margin="20"
+                                  Visibility="{Binding SelectDeviceInfo,Converter={StaticResource ObjectToVisibilityConverter}}">
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="auto"/>
+                                <RowDefinition Height="auto"/>
+                            </Grid.RowDefinitions>
+                            
+                            <!--设备信息表单-->
+                            <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="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="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>
 
-                        <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" />
+                            </Grid>
 
-                        <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" />
+                            <!--CTQ 数据信息-->
+                            <Grid Margin="0,16,0,0"
+                                  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>
 
-                        <StackPanel Orientation="Horizontal"
-                                    HorizontalAlignment="Right"
-                                    Grid.Row="10"
-                                    Grid.ColumnSpan="2"
-                                    Margin="6">
-                            <Button Style="{StaticResource MaterialDesignRaisedButton}"
-                                    Command="{Binding SaveDataInfoCommand}">保存</Button>
-                        </StackPanel>
-                    </Grid>
+                                <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="MFG_LOT#:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="6"
+                                         Grid.Column="1"
+                                         Text="{Binding DataInfo.MFG_LOT, Mode=TwoWay}"
+                                         Margin="6" />
+
+                                <TextBlock Grid.Row="7"
+                                           Grid.Column="0"
+                                           Text="MFG_ASSY_LINE:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="7"
+                                         Grid.Column="1"
+                                         Text="{Binding DataInfo.MFG_ASSY_LINE, Mode=TwoWay}"
+                                         Margin="6" />
+
+                                <TextBlock Grid.Row="8"
+                                           Grid.Column="0"
+                                           Text="PROCESS_MACHINE_ID:"
+                                           HorizontalAlignment="Right"
+                                           VerticalAlignment="Center" />
+                                <TextBox Grid.Row="8"
+                                         Grid.Column="1"
+                                         Text="{Binding DataInfo.PROCESS_MACHINE_ID, Mode=TwoWay}"
+                                         Margin="6" />
+
+                                <StackPanel Orientation="Horizontal"
+                                            HorizontalAlignment="Right"
+                                            Grid.Row="9"
+                                            Grid.ColumnSpan="2"
+                                            Margin="6">
+                                    <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                                            Command="{Binding SaveDataInfoCommand}">保存CTQ数据</Button>
+                                </StackPanel>
+                            </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)
             {

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików