Pārlūkot izejas kodu

螺丝/披头/吸嘴批次与计数重构,界面优化

重构批头、吸嘴、螺丝供料器的批次与计数管理,全部通过数据库记录实现,支持批次更换、计数递增、清零和历史追溯。新增批次相关数据模型和服务接口,移除原有报警阈值逻辑。主界面新增“添加螺丝”“更换披头/吸嘴”对话框,操作更直观。锁付结果增加批次、曲线图等字段,支持自动保存曲线图。同步优化设置界面和数据导出,提升系统一致性与可维护性。
孝锋 徐 9 mēneši atpakaļ
vecāks
revīzija
92e73e195f
36 mainītis faili ar 1818 papildinājumiem un 421 dzēšanām
  1. 3 1
      TeamAAS-VM/App.xaml.cs
  2. 0 5
      TeamAAS-VM/Core/FilePath.cs
  3. 62 37
      TeamAAS-VM/Core/Management.cs
  4. 10 4
      TeamAAS-VM/Core/ScrewDriver/XYD_ScrewDriver.cs
  5. 18 1
      TeamAAS-VM/Data/DatabaseInitializer.cs
  6. 250 1
      TeamAAS-VM/Data/SystemDatabaseService.cs
  7. 1 6
      TeamAAS-VM/Interfaces/IConfigService.cs
  8. 96 0
      TeamAAS-VM/Interfaces/ISystemDatabaseService.cs
  9. 34 0
      TeamAAS-VM/Models/HeadChangeRecord.cs
  10. 85 27
      TeamAAS-VM/Models/LockResult.cs
  11. 34 0
      TeamAAS-VM/Models/NozzleChangeRecord.cs
  12. 0 20
      TeamAAS-VM/Models/NumberStatistics.cs
  13. 79 0
      TeamAAS-VM/Models/PhotoCaptureRecord.cs
  14. 10 0
      TeamAAS-VM/Models/ScrewFeederBatchRecord.cs
  15. 20 0
      TeamAAS-VM/Models/SystemConfiguration.cs
  16. 24 27
      TeamAAS-VM/Services/ConfigService.cs
  17. 23 4
      TeamAAS-VM/TeamAAS-VP.csproj
  18. 199 0
      TeamAAS-VM/ViewModels/Home/AddScrewViewModel.cs
  19. 101 0
      TeamAAS-VM/ViewModels/Home/ChangeHeaderViewModel.cs
  20. 100 0
      TeamAAS-VM/ViewModels/Home/ChangeNozzleViewModel.cs
  21. 0 99
      TeamAAS-VM/ViewModels/Home/SetAlarmValueViewModel.cs
  22. 98 34
      TeamAAS-VM/ViewModels/HomeViewModel.cs
  23. 4 1
      TeamAAS-VM/ViewModels/MainWindowViewModel.cs
  24. 3 0
      TeamAAS-VM/ViewModels/Product/ProcedureParamsViewModel.cs
  25. 62 2
      TeamAAS-VM/ViewModels/SettingViewModel.cs
  26. 7 5
      TeamAAS-VM/ViewModels/Statistics/LockResultRecoredQueryViewModel.cs
  27. 145 0
      TeamAAS-VM/Views/Home/AddScrew.xaml
  28. 15 0
      TeamAAS-VM/Views/Home/AddScrew.xaml.cs
  29. 123 0
      TeamAAS-VM/Views/Home/ChangeHeader.xaml
  30. 15 0
      TeamAAS-VM/Views/Home/ChangeHeader.xaml.cs
  31. 123 0
      TeamAAS-VM/Views/Home/ChangeNozzle.xaml
  32. 15 0
      TeamAAS-VM/Views/Home/ChangeNozzle.xaml.cs
  33. 0 94
      TeamAAS-VM/Views/Home/SetAlarmValue.xaml
  34. 0 28
      TeamAAS-VM/Views/Home/SetAlarmValue.xaml.cs
  35. 13 6
      TeamAAS-VM/Views/HomeView.xaml
  36. 46 19
      TeamAAS-VM/Views/SettingView.xaml

+ 3 - 1
TeamAAS-VM/App.xaml.cs

@@ -183,9 +183,11 @@ namespace TeamAAS_VP
             containerRegistry.RegisterDialog<AutoCorrectLockPoint>();
             containerRegistry.RegisterDialog<ImageDisplay>();
             containerRegistry.RegisterDialog<TorqueCheck>();
-            containerRegistry.RegisterDialog<SetAlarmValue>();
             containerRegistry.RegisterDialog<VisionStaticAccuracyAnalyzer>();
             containerRegistry.RegisterDialog<VisionDynamicAccuracyAnalyzer>();
+            containerRegistry.RegisterDialog<AddScrew>();
+            containerRegistry.RegisterDialog<ChangeHeader>();
+            containerRegistry.RegisterDialog<ChangeNozzle>();
             //**************************************************************************************
 
             // 注册 SQLSugar 客户端(单例模式)

+ 0 - 5
TeamAAS-VM/Core/FilePath.cs

@@ -67,11 +67,6 @@ namespace TeamAAS_VP.Core
         /// </summary>
         public static string TorqueCheckPath = "..//Config//TorqueCheckParam.cfg";
 
-        /// <summary>
-        /// 数量参数
-        /// </summary>
-        public static string NumberStatisticsPath = "..//Config//NumberStatisticsParam.cfg";
-
         /// <summary>
         /// 扭力测量仪路径
         /// </summary>

+ 62 - 37
TeamAAS-VM/Core/Management.cs

@@ -235,26 +235,33 @@ namespace TeamAAS_VP.Core
         }
 
         #region 读取配置参数
-        public void ReadConfig()
+        public async Task ReadConfig()
         {
-            ReadNumber();
+            await ReadNumber();
         }
         /// <summary>
         /// 读取数量配置
         /// </summary>
-        public void ReadNumber()
+        public async Task ReadNumber()
         {
             try
             {
-                if (File.Exists(FilePath.NumberStatisticsPath))
+                NumStatistics = new NumberStatistics();
+                var latestHeadChangeRecord = await _systemDatabaseService.GetLatestHeadChangeRecordAsync();
+                if (latestHeadChangeRecord!=null)
                 {
-                    NumStatistics = FileHelper.ReadJsonFile<NumberStatistics>(FilePath.NumberStatisticsPath);
+                    NumStatistics.HeaderNum = latestHeadChangeRecord.CurrentLockCount;
                 }
-                else
+                
+                var latestNozzleChangeRecord = await _systemDatabaseService.GetLatestNozzleChangeRecordAsync();
+                if (latestNozzleChangeRecord!=null)
                 {
-                    NumStatistics = new NumberStatistics();
-                    FileHelper.WriteJsonFile(NumStatistics, FilePath.NumberStatisticsPath);
+                    NumStatistics.NozzleNum = latestNozzleChangeRecord.CurrentLockCount;
                 }
+                
+                NumStatistics.Feeder1Num = _configService.GetRemainingCountByFeederNumber(1);
+                NumStatistics.Feeder2Num = _configService.GetRemainingCountByFeederNumber(2);
+
             }
             catch (Exception ex)
             {
@@ -1625,7 +1632,17 @@ namespace TeamAAS_VP.Core
                             var currentPosition = robot.GetRobotPos();
                             //读取压力值
                             float pressure = plc.ReadNode<float>(addressConfig.In_PressureValue.Address);
-
+                            //当前用户名
+                            string currentUserName = _systemDatabaseService.GetCurrentUser().UserName;
+                            
+                            //吸嘴最后一次的记录
+                            var nozzleRecord = await _systemDatabaseService.GetLatestNozzleChangeRecordAsync();
+                            //最后一次的吸嘴编号
+                            string nozzleNumber = nozzleRecord != null ? nozzleRecord.NozzleBatchNumber : "N/A";
+                            //披头最后一次的记录
+                            var headRecord = await _systemDatabaseService.GetLatestHeadChangeRecordAsync();
+                            //最后一次的披头编号
+                            string headNumber = headRecord != null ? headRecord.HeadBatchNumber : "N/A";
 
                             await Task.Delay(60);
                             //获取电批结果
@@ -1642,40 +1659,55 @@ namespace TeamAAS_VP.Core
                             lockResult.ScrewdriverProgramNumber = ScrewDriver.ProgramNumber;
                             lockResult.ResultDetails = ScrewDriver.ErrorInfo;
                             lockResult.Pressure = pressure;
-                            lockResult.TargetTorque = ScrewDriver.TargetTorque;
+                            lockResult.TargetTorqueUpperLimit = ScrewDriver.TargetTorqueUpperLimit;
+                            lockResult.TargetTorqueLowerLimit = ScrewDriver.TargetTorqueLowerLimit;
                             lockResult.TargetTurnsUpperLimit = ScrewDriver.TargetTurnsUpperLimit;
                             lockResult.TargetTurnsLowerLimit = ScrewDriver.TargetTurnsLowerLimit;
                             lockResult.TargetLockSpeed = ScrewDriver.TargetLockSpeed;
-                            lockResult.StartPositionX = currentPosition.X;
-                            lockResult.StartPositionY = currentPosition.Y;
+                            lockResult.PositionX = currentPosition.X;
+                            lockResult.PositionY = currentPosition.Y;
                             lockResult.StartPositionZ = screwPoint.Z_Position_Start;
                             lockResult.EndPositionZ = currentPosition.Z;
                             lockResult.LockDescentSpeed = screwPoint.Z_Velocity_Stop;
                             lockResult.FeederNumber = screwPoint != null ? screwPoint.Feeder : 0;
-                            lockResult.NozzleNumber = "";
-                            lockResult.OperatorName = _systemDatabaseService.GetCurrentUser().UserName;
+                            lockResult.NozzleNumber = nozzleNumber;
+                            lockResult.HeadNumber = headNumber;
+                            lockResult.OperatorName = currentUserName;
                             lockResult.ProductImagePath = "";
                             lockResult.WaveDatas = ScrewDriver.WaveDatas;
+                            //获取系统参数
+                            var systemConfig = _configService.GetSystemConfiguration();
+                            //判断是否保存锁付曲线
+                            if (systemConfig.IsSaveScrewCurve)
+                            {
+                                //锁付曲线的保存路径为:systemConfig.ScrewCurvePath//年月//年月日//配方名//产品SN//产品SN+螺丝索引
+                                string datePath = DateTime.Now.ToString("yyyy-MM") + "\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\";
+                                string dirPath = System.IO.Path.Combine(systemConfig.ScrewCurvePath, datePath, currentProduct.Name, lockResult.ProductNumber);
+                                if (!Directory.Exists(dirPath))
+                                {
+                                    Directory.CreateDirectory(dirPath);
+                                }
+                                string curveFileName = $"{lockResult.ProductNumber}-{lockResult.Number}.png";
+                                //锁付CSV保存路径
+                                string curveFileFullPath = System.IO.Path.Combine(dirPath, $"{lockResult.ProductNumber}-{lockResult.Number}.csv");
+
+                                //锁付曲线保存路径
+                                lockResult.LockCurveImagePath = System.IO.Path.Combine(dirPath, curveFileName);
+                                var _ = lockResult.SaveToCsvAsync(curveFileFullPath);
+                            }
+
                             App.Current.Dispatcher.Invoke(() =>
                             {
                                 _eventAggregator.GetEvent<LockFinishNotification>().Publish(lockResult);
                             });
+                            //记录锁付结果至数据库
                             await _systemDatabaseService.RecordLockResultAsync(lockResult);
-                            await plc.WriteNodeAsync(addressConfig.In_WorkDone.Address, (Int16)0);
+                            //披头数量+1
+                            NumStatistics.HeaderNum=await _systemDatabaseService.IncrementLatestHeadLockCountAsync(currentUserName);
+                            //吸嘴数量+1
+                            NumStatistics.NozzleNum=await _systemDatabaseService.IncrementLatestNozzleLockCountAsync(currentUserName);
 
-                            Value = lockResult.LockTorque;
-                            //电批吸嘴报警
-                            NumStatistics.HeaderNum++;//批头数量
-                            NumStatistics.NozzleNum++;//吸嘴数量
-                            FileHelper.WriteJsonFile(NumStatistics, FilePath.NumberStatisticsPath);
-                            if (NumStatistics.HeaderNum < NumStatistics.HeaderAlarmValue)
-                            {
-                                System.Windows.MessageBox.Show("Electric Screwdriver Alarm!", "Alarm", MessageBoxButton.OK, MessageBoxImage.Warning);
-                            }
-                            if (NumStatistics.NozzleNum < NumStatistics.NozzleAlarmValue)
-                            {
-                                System.Windows.MessageBox.Show("Nozzle Alarm!", "Alarm", MessageBoxButton.OK, MessageBoxImage.Warning);
-                            }
+                            await plc.WriteNodeAsync(addressConfig.In_WorkDone.Address, (Int16)0);
                         }
                         else
                         {
@@ -1739,7 +1771,6 @@ namespace TeamAAS_VP.Core
                         }
                     }
                 }
-                
                 // 螺丝供料器螺丝递减
                 else if (addressConfig.In_PickINC.Address.Contains(tuple.nodeId))
                 {
@@ -1761,17 +1792,11 @@ namespace TeamAAS_VP.Core
                             var feeders = _configService.GetRemainingCountByFeederNumber(state);
                             if (state == 1)
                             {
-                                NumStatistics.Feeder1Num = feeders;
+                                NumStatistics.Feeder1Num = _configService.DecrementScrewCountByFeederNumber(feeders);
                             }
                             else if (state == 2)
                             {
-                                NumStatistics.Feeder2Num = feeders;
-                            }
-                            FileHelper.WriteJsonFile(NumStatistics, FilePath.NumberStatisticsPath);
-                            var res = _configService.CheckAlarm(state);
-                            if (res)
-                            {
-                                System.Windows.MessageBox.Show("Screw feeder alarm!", "Alarm", MessageBoxButton.OK, MessageBoxImage.Warning);
+                                NumStatistics.Feeder2Num = _configService.DecrementScrewCountByFeederNumber(feeders);
                             }
                         }
                     }

+ 10 - 4
TeamAAS-VM/Core/ScrewDriver/XYD_ScrewDriver.cs

@@ -173,9 +173,14 @@ namespace TeamAAS_VP.Core.ScrewDriver
 
         //设定值
         /// <summary>
-        /// 目标扭矩
+        /// 目标扭矩上限
         /// </summary>
-        public float TargetTorque { get; private set; }
+        public float TargetTorqueUpperLimit { get; private set; }
+
+        /// <summary>
+        /// 目标扭矩下限
+        /// </summary>
+        public float TargetTorqueLowerLimit { get; private set; }
 
         /// <summary>
         /// 目标圈数上限
@@ -525,8 +530,9 @@ namespace TeamAAS_VP.Core.ScrewDriver
                 SectionOverLock = ToInt16(data, 100);
                 SectionFinalEnd = ToInt16(data, 102);
 
-                TargetTorque = ToInt16(data, 238) * 0.001f;//目标扭矩
-                TargetTurnsUpperLimit= ToInt16(data, 246);//最终旋转角度上限
+                TargetTorqueUpperLimit = ToInt16(data, 386) * 0.001f;//结果扭矩上限
+                TargetTorqueLowerLimit = ToInt16(data, 388) * 0.001f;//结果扭矩下限
+                TargetTurnsUpperLimit = ToInt16(data, 246);//最终旋转角度上限
                 TargetTurnsLowerLimit= ToInt16(data, 248);//最终旋转角度下限
                 TargetLockSpeed= ToInt16(data, 394);//停止速度
 

+ 18 - 1
TeamAAS-VM/Data/DatabaseInitializer.cs

@@ -27,7 +27,7 @@ namespace TeamAAS_VP.Data
                 // 创建数据库(如果不存在)
                 _db.DbMaintenance.CreateDatabase();
                 // 创建表
-                _db.CodeFirst.InitTables(typeof(User), typeof(ProductionRecord), typeof(UserLoginRecord), typeof(AlarmRecord), typeof(LockResult));
+                _db.CodeFirst.InitTables(typeof(User), typeof(ProductionRecord), typeof(UserLoginRecord), typeof(AlarmRecord), typeof(LockResult), typeof(ScrewFeederBatchRecord), typeof(PhotoCaptureRecord), typeof(HeadChangeRecord),typeof(NozzleChangeRecord));
 
                 // 创建索引
                 CreateIndexes();
@@ -74,6 +74,23 @@ namespace TeamAAS_VP.Data
 
                 // 你可以在这里为其他表添加索引,例如 ProductionRecord 的 Timestamp 或 ProductName
                 try { _db.DbMaintenance.CreateIndex("ProductionRecord", new string[] { "ProductName", "Timestamp" }, "IX_ProductionRecord_ProductName_Timestamp", false); } catch { }
+                // Screw feeder batch records indexes
+                try { _db.DbMaintenance.CreateIndex("ScrewFeederBatchRecord", new string[] { "ChangeTime" }, "IX_ScrewFeederBatchRecord_ChangeTime", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("ScrewFeederBatchRecord", new string[] { "FeederNumber" }, "IX_ScrewFeederBatchRecord_FeederNumber", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("ScrewFeederBatchRecord", new string[] { "BatchNumber" }, "IX_ScrewFeederBatchRecord_BatchNumber", false); } catch { }
+
+                // Photo capture records indexes
+                try { _db.DbMaintenance.CreateIndex("PhotoCaptureRecord", new string[] { "CaptureTime" }, "IX_PhotoCaptureRecord_CaptureTime", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("PhotoCaptureRecord", new string[] { "RecipeName" }, "IX_PhotoCaptureRecord_RecipeName", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("PhotoCaptureRecord", new string[] { "ProductSN" }, "IX_PhotoCaptureRecord_ProductSN", false); } catch { }
+
+                // Head change records indexes
+                try { _db.DbMaintenance.CreateIndex("HeadChangeRecord", new string[] { "ChangeTime" }, "IX_HeadChangeRecord_ChangeTime", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("HeadChangeRecord", new string[] { "HeadBatchNumber" }, "IX_HeadChangeRecord_HeadBatchNumber", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("PhotoCaptureRecord", new string[] { "CameraName" }, "IX_PhotoCaptureRecord_CameraName", false); } catch { }
+                // Nozzle change records indexes (same as head)
+                try { _db.DbMaintenance.CreateIndex("NozzleChangeRecord", new string[] { "ChangeTime" }, "IX_NozzleChangeRecord_ChangeTime", false); } catch { }
+                try { _db.DbMaintenance.CreateIndex("NozzleChangeRecord", new string[] { "NozzleBatchNumber" }, "IX_NozzleChangeRecord_NozzleBatchNumber", false); } catch { }
             }
             catch
             {

+ 250 - 1
TeamAAS-VM/Data/SystemDatabaseService.cs

@@ -26,6 +26,8 @@ namespace TeamAAS_VP.Data
             _db = db;
         }
 
+        
+
         #region 用户
         // User CRUD
 
@@ -603,6 +605,7 @@ namespace TeamAAS_VP.Data
 
         #endregion
 
+        #region 锁付结果记录
         // Lock result methods
         /// <summary>
         /// 记录单次锁付结果(WaveDatas 不写入数据库)
@@ -653,15 +656,261 @@ namespace TeamAAS_VP.Data
             var list = await q.OrderBy(r => r.Timestamp, OrderByType.Desc).ToListAsync();
             return list;
         }
-        
+
+        #endregion
+
+        #region 螺丝供料器批次记录
         /// <summary>
         /// 记录一次螺丝供料器批次更换记录
         /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
         public async Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record)
         {
             if (record == null) throw new ArgumentNullException(nameof(record));
             if (record.ChangeTime == default(DateTime)) record.ChangeTime = DateTime.UtcNow;
+            record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
+            await _db.Insertable(record).ExecuteCommandAsync();
+        }
+
+        /// <summary>
+        /// 查询螺丝供料器批次记录。
+        /// 可按时间段、批次号精确匹配及供料器编号过滤。所有参数均为可选,传入 null 表示不作为过滤条件。
+        /// </summary>
+        /// <param name="start">可选:起始时间(包含)</param>
+        /// <param name="end">可选:结束时间(包含)</param>
+        /// <param name="batchNumber">可选:批次号(精确匹配)</param>
+        /// <param name="feederNumber">可选:供料器编号</param>
+        /// <returns>满足条件的记录集合(按 ChangeTime 降序)。</returns>
+        public async Task<IEnumerable<ScrewFeederBatchRecord>> QueryScrewFeederBatchRecordsAsync(DateTime? start = null, DateTime? end = null, string batchNumber = null, int? feederNumber = null)
+        {
+            var q = _db.Queryable<ScrewFeederBatchRecord>();
+            if (start.HasValue)
+                q = q.Where(r => r.ChangeTime >= start.Value);
+            if (end.HasValue)
+                q = q.Where(r => r.ChangeTime <= end.Value);
+            if (!string.IsNullOrEmpty(batchNumber))
+                q = q.Where(r => r.BatchNumber == batchNumber);
+            if (feederNumber.HasValue)
+                q = q.Where(r => r.FeederNumber == feederNumber.Value);
+
+            var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToListAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 获取指定供料器编号的最新一条批次更换记录
+        /// </summary>
+        /// <param name="feederNumber"></param>
+        /// <returns></returns>
+        public async Task<ScrewFeederBatchRecord> GetLatestScrewFeederBatchRecordAsync(int feederNumber)
+        {
+            var recs = await _db.Queryable<ScrewFeederBatchRecord>()
+                .Where(r => r.FeederNumber == feederNumber)
+                .OrderBy(r => r.ChangeTime, OrderByType.Desc)
+                .Take(1)
+                .ToListAsync();
+            return recs.FirstOrDefault();
+        }
+
+        #endregion
+
+        #region 拍照记录
+        /// <summary>
+        /// 记录产品拍照结果
+        /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public async Task RecordPhotoCaptureAsync(PhotoCaptureRecord record)
+        {
+            if (record == null) throw new ArgumentNullException(nameof(record));
+            if (record.CaptureTime == default(DateTime)) record.CaptureTime = DateTime.UtcNow;
+            record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
+            await _db.Insertable(record).ExecuteCommandAsync();
+        }
+
+        /// <summary>
+        /// 查询拍照记录,支持按配方名、产品SN和时间段过滤,参数均为可选。
+        /// </summary>
+        /// <param name="recipeName"></param>
+        /// <param name="cameraName"></param>
+        /// <param name="productSN"></param>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <returns></returns>
+        public async Task<IEnumerable<PhotoCaptureRecord>> QueryPhotoCapturesAsync(string recipeName = null, string cameraName = null, string productSN = null, DateTime? start = null, DateTime? end = null)
+        {
+            var q = _db.Queryable<PhotoCaptureRecord>();
+            if (!string.IsNullOrEmpty(recipeName)) q = q.Where(r => r.RecipeName == recipeName);
+            if (!string.IsNullOrEmpty(productSN)) q = q.Where(r => r.ProductSN == productSN);
+            if (start.HasValue) q = q.Where(r => r.CaptureTime >= start.Value);
+            if (end.HasValue) q = q.Where(r => r.CaptureTime <= end.Value);
+            var list = await q.OrderBy(r => r.CaptureTime, OrderByType.Desc).ToListAsync();
+            return list;
+        }
+        #endregion
+
+        #region 披头更换记录
+        /// <summary>
+        /// 记录披头更换
+        /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public async Task RecordHeadChangeAsync(HeadChangeRecord record)
+        {
+            if (record == null) throw new ArgumentNullException(nameof(record));
+            if (record.ChangeTime == default(DateTime)) record.ChangeTime = DateTime.UtcNow;
+            record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
             await _db.Insertable(record).ExecuteCommandAsync();
         }
+
+        /// <summary>
+        /// 查询披头更换记录
+        /// </summary>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <param name="headBatchNumber"></param>
+        /// <returns></returns>
+        public async Task<IEnumerable<HeadChangeRecord>> QueryHeadChangeRecordsAsync(DateTime? start = null, DateTime? end = null, string headBatchNumber = null)
+        {
+            var q = _db.Queryable<HeadChangeRecord>();
+            if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
+            if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
+            if (!string.IsNullOrEmpty(headBatchNumber)) q = q.Where(r => r.HeadBatchNumber == headBatchNumber);
+            var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToListAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 获取最新的一条披头更换记录
+        /// </summary>
+        /// <returns></returns>
+        public async Task<HeadChangeRecord> GetLatestHeadChangeRecordAsync()
+        {
+            var recs = await _db.Queryable<HeadChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).ToListAsync();
+            return recs.FirstOrDefault();
+        }
+
+        /// <summary>
+        /// 对最新披头的锁付次数进行递增并返回更新后的次数(原子操作)。
+        /// 若不存在记录则创建一条新记录并返回 1。
+        /// </summary>
+        public async Task<int> IncrementLatestHeadLockCountAsync(string operatorName = null)
+        {
+            // 使用事务保证原子性
+            try
+            {
+                _db.Ado.BeginTran();
+                // 获取最新记录(排他查询)
+                var latest = _db.Queryable<HeadChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).First();
+                if (latest == null)
+                {
+                    var newRec = new HeadChangeRecord
+                    {
+                        ChangeTime = DateTime.UtcNow,
+                        HeadBatchNumber = string.Empty,
+                        CurrentLockCount = 1,
+                        OperatorName = operatorName
+                    };
+                    await _db.Insertable(newRec).ExecuteCommandAsync();
+                    _db.Ado.CommitTran();
+                    return 1;
+                }
+
+                latest.CurrentLockCount += 1;
+                if (!string.IsNullOrEmpty(operatorName)) latest.OperatorName = operatorName;
+                await _db.Updateable(latest).ExecuteCommandAsync();
+                _db.Ado.CommitTran();
+                return latest.CurrentLockCount;
+            }
+            catch
+            {
+                try { _db.Ado.RollbackTran(); } catch { }
+                throw;
+            }
+        }
+        #endregion
+
+        #region 吸嘴更换记录
+        /// <summary>
+        /// 记录喷嘴更换记录。
+        /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
+        public async Task RecordNozzleChangeAsync(NozzleChangeRecord record)
+        {
+            if (record == null) throw new ArgumentNullException(nameof(record));
+            if (record.ChangeTime == default(DateTime)) record.ChangeTime = DateTime.UtcNow;
+            record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
+            await _db.Insertable(record).ExecuteCommandAsync();
+        }
+
+        /// <summary>
+        /// 查询喷嘴更换记录,可按时间段或喷嘴批号过滤(所有参数可选)。
+        /// </summary>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <param name="nozzleBatchNumber"></param>
+        /// <returns></returns>
+        public async Task<IEnumerable<NozzleChangeRecord>> QueryNozzleChangeRecordsAsync(DateTime? start = null, DateTime? end = null, string nozzleBatchNumber = null)
+        {
+            var q = _db.Queryable<NozzleChangeRecord>();
+            if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
+            if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
+            if (!string.IsNullOrEmpty(nozzleBatchNumber)) q = q.Where(r => r.NozzleBatchNumber == nozzleBatchNumber);
+            var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToListAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 获取最新的一条喷嘴更换记录(按 ChangeTime 降序)。
+        /// </summary>
+        /// <returns></returns>
+        public async Task<NozzleChangeRecord> GetLatestNozzleChangeRecordAsync()
+        {
+            var recs = await _db.Queryable<NozzleChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).ToListAsync();
+            return recs.FirstOrDefault();
+        }
+
+        /// <summary>
+        /// 吸嘴的锁付次数进行递增并返回更新后的次数(如果不存在最新记录,则创建一条新的记录并返回 1)。
+        /// </summary>
+        /// <param name="operatorName"></param>
+        /// <returns></returns>
+        public async Task<int> IncrementLatestNozzleLockCountAsync(string operatorName = null)
+        {
+            try
+            {
+                _db.Ado.BeginTran();
+                var latest = _db.Queryable<NozzleChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).First();
+                if (latest == null)
+                {
+                    var newRec = new NozzleChangeRecord
+                    {
+                        ChangeTime = DateTime.UtcNow,
+                        NozzleBatchNumber = string.Empty,
+                        CurrentLockCount = 1,
+                        OperatorName = operatorName
+                    };
+                    await _db.Insertable(newRec).ExecuteCommandAsync();
+                    _db.Ado.CommitTran();
+                    return 1;
+                }
+
+                latest.CurrentLockCount += 1;
+                if (!string.IsNullOrEmpty(operatorName)) latest.OperatorName = operatorName;
+                await _db.Updateable(latest).ExecuteCommandAsync();
+                _db.Ado.CommitTran();
+                return latest.CurrentLockCount;
+            }
+            catch
+            {
+                try { _db.Ado.RollbackTran(); } catch { }
+                throw;
+            }
+        }
+        #endregion
     }
 }

+ 1 - 6
TeamAAS-VM/Interfaces/IConfigService.cs

@@ -446,7 +446,7 @@ namespace TeamAAS_VP.Interfaces
         /// <summary>
         /// 为指定供料器编号递减指定数量(通常为 1),若数量不足返回 false
         /// </summary>
-        bool DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1);
+        int DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1);
 
         /// <summary>
         /// 清除指定供料器编号的螺丝信息(批次号与剩余数量置空/置0)
@@ -473,11 +473,6 @@ namespace TeamAAS_VP.Interfaces
         /// </summary>
         event Action<ScrewFeederInfo> ScrewFeederLowLevelAlarmTriggered;
 
-        /// <summary>
-        /// 记录一次螺丝供料器批次更换记录(写入数据库)。
-        /// </summary>
-        Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record);
-
         /// <summary>
         /// 检查剩余螺丝是否报警
         /// </summary>

+ 96 - 0
TeamAAS-VM/Interfaces/ISystemDatabaseService.cs

@@ -321,6 +321,102 @@ namespace TeamAAS_VP.Interfaces
         /// <summary>
         /// 记录一次螺丝供料器批次更换记录
         /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
         Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record);
+
+        /// <summary>
+        /// 查询螺丝供料器批次记录。
+        /// 可按时间段、批次号精确匹配及供料器编号过滤。所有参数均为可选,传入 null 表示不作为过滤条件。
+        /// </summary>
+        /// <param name="start">可选:起始时间(包含)</param>
+        /// <param name="end">可选:结束时间(包含)</param>
+        /// <param name="batchNumber">可选:批次号(精确匹配)</param>
+        /// <param name="feederNumber">可选:供料器编号</param>
+        /// <returns>满足条件的记录集合(按 ChangeTime 降序)。</returns>
+        Task<IEnumerable<ScrewFeederBatchRecord>> QueryScrewFeederBatchRecordsAsync(DateTime? start = null, DateTime? end = null, string batchNumber = null, int? feederNumber = null);
+
+        /// <summary>
+        /// 获取指定供料器编号的最新一条批次更换记录
+        /// </summary>
+        /// <param name="feederNumber"></param>
+        /// <returns></returns>
+        Task<ScrewFeederBatchRecord> GetLatestScrewFeederBatchRecordAsync(int feederNumber);
+
+        /// <summary>
+        /// 记录产品拍照结果(含图片路径、机器人位置、像素与绝对坐标等)。
+        /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
+        Task RecordPhotoCaptureAsync(PhotoCaptureRecord record);
+
+        /// <summary>
+        /// 查询拍照记录,支持按配方名、产品序列号和时间段过滤(所有参数可选)。
+        /// </summary>
+        /// <param name="recipeName"></param>
+        /// <param name="cameraName"></param>
+        /// <param name="productSN"></param>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <returns></returns>
+        Task<IEnumerable<PhotoCaptureRecord>> QueryPhotoCapturesAsync(string recipeName = null, string cameraName = null, string productSN = null, DateTime? start = null, DateTime? end = null);
+
+        /// <summary>
+        /// 记录披头更换记录。
+        /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
+        Task RecordHeadChangeAsync(HeadChangeRecord record);
+
+        /// <summary>
+        /// 查询披头更换记录,可按时间段或披头批号过滤(所有参数可选)。
+        /// </summary>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <param name="headBatchNumber"></param>
+        /// <returns></returns>
+        Task<IEnumerable<HeadChangeRecord>> QueryHeadChangeRecordsAsync(DateTime? start = null, DateTime? end = null, string headBatchNumber = null);
+
+        /// <summary>
+        /// 获取最新的一条披头更换记录(按 ChangeTime 降序)。
+        /// </summary>
+        /// <returns></returns>
+        Task<HeadChangeRecord> GetLatestHeadChangeRecordAsync();
+
+        /// <summary>
+        /// 对最新披头的锁付次数进行递增并返回更新后的次数(如果不存在最新记录,则创建一条新的记录并返回 1)。
+        /// </summary>
+        /// <param name="operatorName"></param>
+        /// <returns></returns>
+        Task<int> IncrementLatestHeadLockCountAsync(string operatorName = null);
+
+        /// <summary>
+        /// 记录喷嘴更换记录。
+        /// </summary>
+        /// <param name="record"></param>
+        /// <returns></returns>
+        Task RecordNozzleChangeAsync(NozzleChangeRecord record);
+
+        /// <summary>
+        /// 查询喷嘴更换记录,可按时间段或喷嘴批号过滤(所有参数可选)。
+        /// </summary>
+        /// <param name="start"></param>
+        /// <param name="end"></param>
+        /// <param name="nozzleBatchNumber"></param>
+        /// <returns></returns>
+        Task<IEnumerable<NozzleChangeRecord>> QueryNozzleChangeRecordsAsync(DateTime? start = null, DateTime? end = null, string nozzleBatchNumber = null);
+
+        /// <summary>
+        /// 获取最新的一条喷嘴更换记录(按 ChangeTime 降序)。
+        /// </summary>
+        /// <returns></returns>
+        Task<NozzleChangeRecord> GetLatestNozzleChangeRecordAsync();
+
+        /// <summary>
+        /// 吸嘴的锁付次数进行递增并返回更新后的次数(如果不存在最新记录,则创建一条新的记录并返回 1)。
+        /// </summary>
+        /// <param name="operatorName"></param>
+        /// <returns></returns>
+        Task<int> IncrementLatestNozzleLockCountAsync(string operatorName = null);
     }
 }

+ 34 - 0
TeamAAS-VM/Models/HeadChangeRecord.cs

@@ -0,0 +1,34 @@
+using SqlSugar;
+using System;
+
+namespace TeamAAS_VP.Models
+{
+    [SugarTable("HeadChangeRecord")]
+    public class HeadChangeRecord
+    {
+        [SugarColumn(IsPrimaryKey = true)]
+        public Guid Id { get; set; } = Guid.NewGuid();
+
+        /// <summary>
+        /// 更换时间
+        /// </summary>
+        public DateTime ChangeTime { get; set; } = DateTime.UtcNow;
+
+        /// <summary>
+        /// 披头批号
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string HeadBatchNumber { get; set; }
+
+        /// <summary>
+        /// 当前披头锁付次数
+        /// </summary>
+        public int CurrentLockCount { get; set; }
+
+        /// <summary>
+        /// 操作员名称
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string OperatorName { get; set; }
+    }
+}

+ 85 - 27
TeamAAS-VM/Models/LockResult.cs

@@ -124,14 +124,24 @@ namespace TeamAAS_VP.Models
             set { SetProperty(ref __Pressure, value); }
         }
 
-        private float __TargetTorque;
+        private float __TargetTorqueUpperLimit;
         /// <summary>
-        /// 目标扭矩
+        /// 目标扭矩上限
         /// </summary>
-        public float TargetTorque
+        public float TargetTorqueUpperLimit
         {
-            get { return __TargetTorque; }
-            set { SetProperty(ref __TargetTorque, value); }
+            get { return __TargetTorqueUpperLimit; }
+            set { SetProperty(ref __TargetTorqueUpperLimit, value); }
+        }
+
+        private float __TargetTorqueLowerLimit;
+        /// <summary>
+        /// 目标扭矩下限
+        /// </summary>
+        public float TargetTorqueLowerLimit
+        {
+            get { return __TargetTorqueLowerLimit; }
+            set { SetProperty(ref __TargetTorqueLowerLimit, value); }
         }
 
         private float __TargetTurnsUpperLimit;
@@ -174,24 +184,24 @@ namespace TeamAAS_VP.Models
             set { SetProperty(ref _ScrewdriverGetDataSucceed, value); }
         }
 
-        private float __StartPositionX;
+        private float __PositionX;
         /// <summary>
-        /// 锁付位置坐标 X 起始值
+        /// 锁付位置坐标 X 值
         /// </summary>
-        public float StartPositionX
+        public float PositionX
         {
-            get { return __StartPositionX; }
-            set { SetProperty(ref __StartPositionX, value); }
+            get { return __PositionX; }
+            set { SetProperty(ref __PositionX, value); }
         }
 
-        private float __StartPositionY;
+        private float __PositionY;
         /// <summary>
-        /// 锁付位置坐标 Y 起始值
+        /// 锁付位置坐标 Y 值
         /// </summary>
-        public float StartPositionY
+        public float PositionY
         {
-            get { return __StartPositionY; }
-            set { SetProperty(ref __StartPositionY, value); }
+            get { return __PositionY; }
+            set { SetProperty(ref __PositionY, value); }
         }
 
         private float __StartPositionZ;
@@ -206,7 +216,7 @@ namespace TeamAAS_VP.Models
 
         private float __EndPositionZ;
         /// <summary>
-        /// Z 终点值
+        /// 锁付位置坐标 Z 终点值
         /// </summary>
         public float EndPositionZ
         {
@@ -246,7 +256,7 @@ namespace TeamAAS_VP.Models
 
         private string __NozzleNumber;
         /// <summary>
-        /// 披头编号
+        /// 吸嘴编号
         /// </summary>
         [SugarColumn(IsNullable = true)]
         public string NozzleNumber
@@ -255,6 +265,17 @@ namespace TeamAAS_VP.Models
             set { SetProperty(ref __NozzleNumber, value); }
         }
 
+        private string _HeadNumber;
+        /// <summary>
+        /// 披头编号
+        /// </summary>
+        [SugarColumn(IsNullable = true)]
+        public string HeadNumber
+            {
+            get { return _HeadNumber; }
+            set { SetProperty(ref _HeadNumber, value); }
+        }
+
         //当前的操作员名称
         private string _OperatorName;
         [SugarColumn(IsNullable = true)]
@@ -275,6 +296,17 @@ namespace TeamAAS_VP.Models
             set { SetProperty(ref _ProductImagePath, value); }
         }
 
+        private string _LockCurveImagePath;
+        /// <summary>
+        /// 锁付曲线图路径
+        /// </summary>
+        [SugarColumn(IsNullable = true)]
+        public string LockCurveImagePath
+        {
+            get { return _LockCurveImagePath; }
+            set { SetProperty(ref _LockCurveImagePath, value); }
+        }
+
         /// <summary>
         /// 锁付曲线数据列表
         /// </summary>
@@ -310,21 +342,24 @@ namespace TeamAAS_VP.Models
                 LockPassed = this.LockPassed,
                 ScrewdriverProgramNumber = this.ScrewdriverProgramNumber,
                 Pressure = this.Pressure,
-                TargetTorque = this.TargetTorque,
+                TargetTorqueUpperLimit = this.TargetTorqueUpperLimit,
+                TargetTorqueLowerLimit = this.TargetTorqueLowerLimit,
                 TargetTurnsUpperLimit = this.TargetTurnsUpperLimit,
                 TargetTurnsLowerLimit = this.TargetTurnsLowerLimit,
                 TargetLockSpeed = this.TargetLockSpeed,
-                StartPositionX = this.StartPositionX,
-                StartPositionY = this.StartPositionY,
+                PositionX = this.PositionX,
+                PositionY = this.PositionY,
                 StartPositionZ = this.StartPositionZ,
                 EndPositionZ = this.EndPositionZ,
                 LockDescentSpeed = this.LockDescentSpeed,
                 FeederNumber = this.FeederNumber,
                 NozzleNumber = this.NozzleNumber,
+                HeadNumber= this.HeadNumber,
                 OperatorName = this.OperatorName,
                 ProductImagePath = this.ProductImagePath,
                 ScrewdriverGetDataSucceed = this.ScrewdriverGetDataSucceed,
-                WaveDatas = new List<WaveData>(this.WaveDatas)
+                WaveDatas = new List<WaveData>(this.WaveDatas),
+                LockCurveImagePath = this.LockCurveImagePath,
             };
         }
 
@@ -353,8 +388,8 @@ namespace TeamAAS_VP.Models
             using (var writer = new System.IO.StreamWriter(filePath, false, System.Text.Encoding.UTF8))
             {
                 // 写入锁付结果信息表头(已新增字段)
-                writer.WriteLine("Number,ProductNumber,Timestamp,LockTurns,LockTorque,LockDuration,LockPassed,ScrewdriverProgramNumber,Pressure,TargetTorque,TargetTurnsUpperLimit,TargetTurnsLowerLimit,TargetLockSpeed,StartPositionX,StartPositionY,StartPositionZ,EndPositionZ,LockDescentSpeed,FeederNumber,FeederBatchNumber,NozzleNumber,ResultDetails,ScrewdriverGetDataSucceed,OperatorName,ProductImagePath");
-                
+                writer.WriteLine("Number,ProductNumber,Timestamp,LockTurns,LockTorque,LockDuration,LockPassed,ScrewdriverProgramNumber,Pressure,TargetTorque,TargetTurnsUpperLimit,TargetTurnsLowerLimit,TargetLockSpeed,StartPositionX,StartPositionY,StartPositionZ,EndPositionZ,LockDescentSpeed,FeederNumber,FeederBatchNumber,NozzleNumber,HeadNumber,ResultDetails,ScrewdriverGetDataSucceed,OperatorName,ProductImagePath,LockCurveImagePath");
+
                 // 写入锁付结果数据行(所有字段均做 CSV 转义)
                 writer.WriteLine(string.Join(",",
                     EscapeCsv(Number),
@@ -366,22 +401,25 @@ namespace TeamAAS_VP.Models
                     EscapeCsv(LockPassed),
                     EscapeCsv(ScrewdriverProgramNumber),
                     EscapeCsv(Pressure),
-                    EscapeCsv(TargetTorque),
+                    EscapeCsv(TargetTorqueUpperLimit),
+                    EscapeCsv(TargetTorqueLowerLimit),
                     EscapeCsv(TargetTurnsUpperLimit),
                     EscapeCsv(TargetTurnsLowerLimit),
                     EscapeCsv(TargetLockSpeed),
-                    EscapeCsv(StartPositionX),
-                    EscapeCsv(StartPositionY),
+                    EscapeCsv(PositionX),
+                    EscapeCsv(PositionY),
                     EscapeCsv(StartPositionZ),
                     EscapeCsv(EndPositionZ),
                     EscapeCsv(LockDescentSpeed),
                     EscapeCsv(FeederNumber),
                     EscapeCsv(FeederBatchNumber),
                     EscapeCsv(NozzleNumber),
+                    EscapeCsv(HeadNumber),
                     EscapeCsv(ResultDetails),
                     EscapeCsv(ScrewdriverGetDataSucceed),
                     EscapeCsv(OperatorName),
-                    EscapeCsv(ProductImagePath)
+                    EscapeCsv(ProductImagePath),
+                    EscapeCsv(LockCurveImagePath)
                 ));
 
                 // 写入波形数据
@@ -402,5 +440,25 @@ namespace TeamAAS_VP.Models
             }
         }
 
+        /// <summary>
+        /// 异步保存到csv
+        /// </summary>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        public async System.Threading.Tasks.Task SaveToCsvAsync(string filePath)
+        {
+            await System.Threading.Tasks.Task.Run(() =>
+            {
+                try
+                {
+                    SaveToCsv(filePath);
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.WriteLogError("保存锁付数据至CSV时出错!", ex);
+                }
+            });
+        }
+
     }
 }

+ 34 - 0
TeamAAS-VM/Models/NozzleChangeRecord.cs

@@ -0,0 +1,34 @@
+using SqlSugar;
+using System;
+
+namespace TeamAAS_VP.Models
+{
+    [SugarTable("NozzleChangeRecord")]
+    public class NozzleChangeRecord
+    {
+        [SugarColumn(IsPrimaryKey = true)]
+        public Guid Id { get; set; } = Guid.NewGuid();
+
+        /// <summary>
+        /// 更换时间
+        /// </summary>
+        public DateTime ChangeTime { get; set; } = DateTime.UtcNow;
+
+        /// <summary>
+        /// 吸嘴批号
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string NozzleBatchNumber { get; set; }
+
+        /// <summary>
+        /// 当前吸嘴锁付次数
+        /// </summary>
+        public int CurrentLockCount { get; set; }
+
+        /// <summary>
+        /// 操作员名称
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string OperatorName { get; set; }
+    }
+}

+ 0 - 20
TeamAAS-VM/Models/NumberStatistics.cs

@@ -52,25 +52,5 @@ namespace TeamAAS_VP.Models
             get { return _Feeder2Num; }
             set { SetProperty(ref _Feeder2Num, value); }
         }
-
-        private int _NozzleAlarmValue;
-        /// <summary>
-        /// 吸嘴报警值
-        /// </summary>
-        public int NozzleAlarmValue
-        {
-            get { return _NozzleAlarmValue; }
-            set { SetProperty(ref _NozzleAlarmValue, value); }
-        }
-
-        private int _HeaderAlarmValue;
-        /// <summary>
-        /// 批头报警值
-        /// </summary>
-        public int HeaderAlarmValue
-        {
-            get { return _HeaderAlarmValue; }
-            set { SetProperty(ref _HeaderAlarmValue, value); }
-        }
     }
 }

+ 79 - 0
TeamAAS-VM/Models/PhotoCaptureRecord.cs

@@ -0,0 +1,79 @@
+using SqlSugar;
+using System;
+
+namespace TeamAAS_VP.Models
+{
+    [SugarTable("PhotoCaptureRecord")]
+    public class PhotoCaptureRecord
+    {
+        [SugarColumn(IsPrimaryKey = true)]
+        public Guid Id { get; set; } = Guid.NewGuid();
+
+        /// <summary>
+        /// 拍照时间
+        /// </summary>
+        public DateTime CaptureTime { get; set; } = DateTime.UtcNow;
+
+        /// <summary>
+        /// 配方名
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string RecipeName { get; set; }
+
+        /// <summary>
+        /// 产品序列号
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string ProductSN { get; set; }
+
+        /// <summary>
+        /// 相机名
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string CameraName { get; set; }
+
+        /// <summary>
+        /// 流程名
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string ProcessName { get; set; }
+
+        /// <summary>
+        /// 是否成功
+        /// </summary>
+        public bool IsSuccess { get; set; }
+
+        /// <summary>
+        /// 机器人拍照位置
+        /// </summary>
+        public string RobotPosition { get; set; }
+
+        /// <summary>
+        /// 像素坐标
+        /// </summary>
+        public string PixelPosition { get; set; }
+
+        /// <summary>
+        /// 绝对坐标
+        /// </summary>
+        public string AbsolutePosition { get; set; }
+
+        /// <summary>
+        /// 图片路径
+        /// </summary>
+        [SugarColumn(Length = 1000, IsNullable = true)]
+        public string ImagePath { get; set; }
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        [SugarColumn(Length = 1000, IsNullable = true)]
+        public string Remarks { get; set; }
+
+        /// <summary>
+        /// 当前的操作员名称
+        /// </summary>
+        [SugarColumn(Length = 255, IsNullable = true)]
+        public string OperatorName { get; set; }
+    }
+}

+ 10 - 0
TeamAAS-VM/Models/ScrewFeederBatchRecord.cs

@@ -31,6 +31,9 @@ namespace TeamAAS_VP.Models
         [SugarColumn(Length = 255, IsNullable = true)]
         public string OperatorName { get; set; }
 
+        //添加前的螺丝供料器中螺丝数量
+        public int PreviousCount { get; set; }
+
         /// <summary>
         /// 添加后的螺丝供料器中螺丝数量
         /// </summary>
@@ -46,5 +49,12 @@ namespace TeamAAS_VP.Models
         /// </summary>
         [SugarColumn(Length = 255, IsNullable = true)]
         public string FeederName { get; set; }
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        [SugarColumn(Length = 500, IsNullable = true)]
+        public string Remarks { get; set; }
+
     }
 }

+ 20 - 0
TeamAAS-VM/Models/SystemConfiguration.cs

@@ -196,6 +196,26 @@ namespace TeamAAS_VP.Models
             set => SetProperty(ref _ScrewTeachNum, value);
         }
 
+        private bool _IsSaveScrewCurve = false;
+        /// <summary>
+        /// 是否保存锁付曲线图
+        /// </summary>
+        public bool IsSaveScrewCurve
+        {
+            get => _IsSaveScrewCurve;
+            set => SetProperty(ref _IsSaveScrewCurve, value);
+        }
+
+        private string _ScrewCurvePath = "";
+        /// <summary>
+        /// 锁付曲线图保存的文件夹路径
+        /// </summary>
+        public string ScrewCurvePath
+        {
+            get => _ScrewCurvePath;
+            set => SetProperty(ref _ScrewCurvePath, value);
+        }
+
         /// <summary>
         /// 字体大小子设置
         /// </summary>

+ 24 - 27
TeamAAS-VM/Services/ConfigService.cs

@@ -83,25 +83,6 @@ namespace TeamAAS_VP.Services
             }
         }
 
-        /// <summary>
-        /// 将螺丝供料器批次变更记录写入数据库(若已注入 ISystemDatabaseService)。
-        /// </summary>
-        public async System.Threading.Tasks.Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record)
-        {
-            if (record == null) throw new ArgumentNullException(nameof(record));
-            try
-            {
-                if (_systemDatabaseService != null)
-                {
-                    await _systemDatabaseService.RecordScrewFeederBatchAsync(record);
-                }
-            }
-            catch
-            {
-                // 忽略数据库写入错误,记录可添加日志
-            }
-        }
-
         /// <summary>
         /// 初始化一个新的 <see cref="ConfigService"/> 实例。
         /// 构造函数不会自动加载配置文件(避免在构造期间进行 I/O 操作);可调用 <see cref="LoadAll"/> 或 <see cref="LoadAllAsync"/> 显式加载。
@@ -1192,21 +1173,21 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 为指定供料器编号递减指定数量,若数量不足返回 false
+        /// 为指定供料器编号递减指定数量,若数量不足返回 -1
         /// </summary>
-        public bool DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1)
+        public int DecrementScrewCountByFeederNumber(int feederNumber, int decrement = 1)
         {
             lock (_sync)
             {
                 var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
-                if (f == null) return false;
+                if (f == null) return -1;
                 if (decrement <= 0) decrement = 1;
-                if (f.RemainingCount < decrement) return false;
+                if (f.RemainingCount < decrement) return -1;
                 f.RemainingCount -= decrement;
                 // check alarm
                 CheckAndTriggerLowLevelAlarm(f);
                 FileHelper.WriteJsonFile(ScrewFeeders, ConfigPaths.ScrewFeedersConfigurationPath);
-                return true;
+                return f.RemainingCount;
             }
         }
 
@@ -1234,16 +1215,21 @@ namespace TeamAAS_VP.Services
         {
             if (string.IsNullOrWhiteSpace(batchNumber)) return;
 
+            //获取当前的操作员用户名
+            string operatorName = _systemDatabaseService.GetCurrentUser()?.UserName;
+
             // capture values under lock, then record to DB outside lock
             ScrewFeederInfo target = null;
             int remainingAfter = 0;
             int packSize = 0;
             string feederName = null;
+            int previousCount = 0;
             lock (_sync)
             {
                 var f = ScrewFeeders.FirstOrDefault(s => s.FeederNumber == feederNumber);
                 if (f == null) return;
                 f.BatchNumber = batchNumber;
+                previousCount= f.RemainingCount;
                 f.RemainingCount += Math.Max(0, f.PackSize);
 
                 // prepare capture values
@@ -1266,7 +1252,9 @@ namespace TeamAAS_VP.Services
                     ChangeTime = DateTime.UtcNow,
                     RemainingCount = remainingAfter,
                     FeederNumber = feederNumber,
-                    FeederName = feederName
+                    FeederName = feederName,
+                    PreviousCount = previousCount,
+                    OperatorName = operatorName,
                 };
 
                 // try get operator name from system database service if available
@@ -1281,8 +1269,17 @@ namespace TeamAAS_VP.Services
                     // ignore getting user failure
                 }
 
-                // fire-and-forget recording (ConfigService will forward to DB service if set)
-                _ = RecordScrewFeederBatchAsync(record);
+                try
+                {
+                    if (_systemDatabaseService != null)
+                    {
+                        _ = _systemDatabaseService.RecordScrewFeederBatchAsync(record);
+                    }
+                }
+                catch
+                {
+                    // 忽略数据库写入错误,记录可添加日志
+                }
             }
             catch
             {

+ 23 - 4
TeamAAS-VM/TeamAAS-VP.csproj

@@ -534,7 +534,10 @@
     <Compile Include="Interfaces\IMesService.cs" />
     <Compile Include="Models\DeviceInfo.cs" />
     <Compile Include="Models\Feeder\ScrewFeederInfo.cs" />
+    <Compile Include="Models\HeadChangeRecord.cs" />
+    <Compile Include="Models\NozzleChangeRecord.cs" />
     <Compile Include="Models\NumberStatistics.cs" />
+    <Compile Include="Models\PhotoCaptureRecord.cs" />
     <Compile Include="Models\ScrewFeederBatchRecord.cs" />
     <Compile Include="Models\TorqueTest.cs" />
     <Compile Include="Resources\Languages\Lang.Designer.cs">
@@ -543,7 +546,9 @@
       <DependentUpon>Lang.resx</DependentUpon>
     </Compile>
     <Compile Include="Services\MesService.cs" />
-    <Compile Include="ViewModels\Home\SetAlarmValueViewModel.cs" />
+    <Compile Include="ViewModels\Home\ChangeNozzleViewModel.cs" />
+    <Compile Include="ViewModels\Home\ChangeHeaderViewModel.cs" />
+    <Compile Include="ViewModels\Home\AddScrewViewModel.cs" />
     <Compile Include="ViewModels\Home\TorqueCheckViewModel.cs" />
     <Compile Include="ViewModels\Product\ImageDisplayViewModel.cs" />
     <Compile Include="ViewModels\Setting\AddScrewFeederViewModel.cs" />
@@ -937,8 +942,14 @@
     <Compile Include="Views\DebugView.xaml.cs">
       <DependentUpon>DebugView.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Views\Home\SetAlarmValue.xaml.cs">
-      <DependentUpon>SetAlarmValue.xaml</DependentUpon>
+    <Compile Include="Views\Home\AddScrew.xaml.cs">
+      <DependentUpon>AddScrew.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Views\Home\ChangeHeader.xaml.cs">
+      <DependentUpon>ChangeHeader.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Views\Home\ChangeNozzle.xaml.cs">
+      <DependentUpon>ChangeNozzle.xaml</DependentUpon>
     </Compile>
     <Compile Include="Views\Home\ShowVisionRender.xaml.cs">
       <DependentUpon>ShowVisionRender.xaml</DependentUpon>
@@ -1259,7 +1270,15 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Views\Home\SetAlarmValue.xaml">
+    <Page Include="Views\Home\AddScrew.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Views\Home\ChangeHeader.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Views\Home\ChangeNozzle.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>

+ 199 - 0
TeamAAS-VM/ViewModels/Home/AddScrewViewModel.cs

@@ -0,0 +1,199 @@
+using Prism.Commands;
+using Prism.Ioc;
+using Prism.Mvvm;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Models.Feeder;
+
+namespace TeamAAS_VP.ViewModels.Home
+{
+    public class AddScrewViewModel : BindableBase, IDialogAware
+    {
+        private readonly IConfigService _configService;
+        private readonly ISystemDatabaseService _systemDatabaseService;
+        IContainerProvider _container;
+
+        public AddScrewViewModel(IConfigService configService, ISystemDatabaseService systemDatabaseService, IContainerProvider containerProvider)
+        {
+            _configService = configService;
+            _systemDatabaseService = systemDatabaseService;
+            _container = containerProvider;
+
+            ScrewFeeders = new ObservableCollection<ScrewFeederInfo>();
+            AddCommand = new DelegateCommand(ExecuteAddCommand, CanExecuteAdd).ObservesProperty(() => SelectedFeeder).ObservesProperty(() => NewBatchNumber);
+            ClearCommand = new DelegateCommand(ExecuteClearCommand, () => SelectedFeeder != null).ObservesProperty(() => SelectedFeeder);
+            CloseCommand = new DelegateCommand(ExecuteCloseCommand);
+        }
+
+        public DelegateCommand AddCommand { get; }
+        public DelegateCommand ClearCommand { get; }
+        public DelegateCommand CloseCommand { get; }
+
+        private DelegateCommand _FeederSelectedCommand;
+        public DelegateCommand FeederSelectedCommand =>
+            _FeederSelectedCommand ?? (_FeederSelectedCommand = new DelegateCommand(ExecuteFeederSelectedCommand));
+
+        public ObservableCollection<ScrewFeederInfo> ScrewFeeders { get; }
+
+        private ScrewFeederInfo _selectedFeeder;
+        public ScrewFeederInfo SelectedFeeder
+        {
+            get => _selectedFeeder;
+            set
+            {
+                SetProperty(ref _selectedFeeder, value);
+            }
+        }
+
+        private string _newBatchNumber;
+        public string NewBatchNumber { get => _newBatchNumber; set => SetProperty(ref _newBatchNumber, value); }
+
+        private string _currentBatchNumber;
+        public string CurrentBatchNumber { get => _currentBatchNumber; set => SetProperty(ref _currentBatchNumber, value); }
+
+        private int _remainingCount;
+        public int RemainingCount { get => _remainingCount; set => SetProperty(ref _remainingCount, value); }
+
+        private string _lastAddTime;
+        public string LastAddTime { get => _lastAddTime; set => SetProperty(ref _lastAddTime, value); }
+
+        public string Title => "添加螺丝";
+
+        public event Action<IDialogResult> RequestClose;
+
+        public bool CanCloseDialog()
+        {
+            return true;
+        }
+
+        public void OnDialogClosed()
+        {
+            
+        }
+
+        public void OnDialogOpened(IDialogParameters parameters)
+        {
+            // load screw feeders
+            try
+            {
+                var list = _configService.GetAllScrewFeeders();
+                if (list != null)
+                {
+                    ScrewFeeders.Clear();
+                    foreach (var s in list)
+                    {
+                        ScrewFeeders.Add(s);
+                    }
+                }
+            }
+            catch { }
+        }
+
+        void ExecuteFeederSelectedCommand()
+        {
+            UpdateSelectedFeederInfo();
+        }
+
+        private async void UpdateSelectedFeederInfo()
+        {
+            if (SelectedFeeder != null)
+            {
+                CurrentBatchNumber = SelectedFeeder.BatchNumber;
+                RemainingCount = SelectedFeeder.RemainingCount;
+                //获取最后一次的螺丝添加时间,这里简单设置为未知,实际应用中可从数据库记录中获取
+                var latestRecord = await _systemDatabaseService.GetLatestScrewFeederBatchRecordAsync(SelectedFeeder.FeederNumber);
+                if (latestRecord != null)
+                {
+                    LastAddTime = latestRecord.ChangeTime.ToString("u");
+                }
+                else
+                    LastAddTime = "(unknown)"; // could be enriched from DB records
+            }
+            else
+            {
+                CurrentBatchNumber = string.Empty;
+                RemainingCount = 0;
+                LastAddTime = string.Empty;
+            }
+        }
+
+        private bool CanExecuteAdd()
+        {
+            return SelectedFeeder != null && !string.IsNullOrWhiteSpace(NewBatchNumber);
+        }
+
+        private async void ExecuteAddCommand()
+        {
+            if (SelectedFeeder == null) return;
+            // set batch and add pack size
+            _configService.SetBatchForFeederNumber(SelectedFeeder.FeederNumber, NewBatchNumber);
+            // refresh
+            var refreshed = _configService.GetScrewFeeder(SelectedFeeder.Id);
+            if (refreshed != null)
+            {
+                SelectedFeeder.RemainingCount = refreshed.RemainingCount;
+                SelectedFeeder.BatchNumber = refreshed.BatchNumber;
+                CurrentBatchNumber = refreshed.BatchNumber;
+                RemainingCount = refreshed.RemainingCount;
+                LastAddTime = DateTime.UtcNow.ToString("u");
+            }
+            Management management = _container.Resolve<Management>();
+            if (SelectedFeeder.FeederNumber == 1)
+                management.NumStatistics.Feeder1Num = refreshed.RemainingCount;
+            else if (SelectedFeeder.FeederNumber == 2)
+                management.NumStatistics.Feeder2Num = refreshed.RemainingCount;
+
+            // try to record batch change to system DB asynchronously
+            try
+            {
+                var rec = new ScrewFeederBatchRecord
+                {
+                    BatchNumber = NewBatchNumber,
+                    PackSize = SelectedFeeder.PackSize,
+                    ChangeTime = DateTime.UtcNow,
+                    RemainingCount = SelectedFeeder.RemainingCount,
+                    FeederNumber = SelectedFeeder.FeederNumber,
+                    FeederName = SelectedFeeder.Name,
+                    PreviousCount = SelectedFeeder.RemainingCount - SelectedFeeder.PackSize,
+                    OperatorName = _systemDatabaseService.GetCurrentUser()?.UserName
+                };
+                await _systemDatabaseService.RecordScrewFeederBatchAsync(rec);
+            }
+            catch(Exception ex)
+            {
+                LogHelper.WriteLogError("Failed to record screw feeder batch change.", ex);
+            }
+        }
+
+        private void ExecuteClearCommand()
+        {
+            if (SelectedFeeder == null) return;
+            _configService.ClearScrewFeederByNumber(SelectedFeeder.FeederNumber);
+            var refreshed = _configService.GetScrewFeeder(SelectedFeeder.Id);
+            if (refreshed != null)
+            {
+                SelectedFeeder.RemainingCount = refreshed.RemainingCount;
+                SelectedFeeder.BatchNumber = refreshed.BatchNumber;
+                CurrentBatchNumber = refreshed.BatchNumber;
+                RemainingCount = refreshed.RemainingCount;
+                LastAddTime = DateTime.UtcNow.ToString("u");
+            }
+            Management management = _container.Resolve<Management>();
+            if (SelectedFeeder.FeederNumber==1)
+                management.NumStatistics.Feeder1Num = 0;
+            else if (SelectedFeeder.FeederNumber == 2)
+                management.NumStatistics.Feeder2Num = 0;
+        }
+
+        private void ExecuteCloseCommand()
+        {
+            RequestClose?.Invoke(new DialogResult(ButtonResult.OK));
+        }
+    }
+}

+ 101 - 0
TeamAAS-VM/ViewModels/Home/ChangeHeaderViewModel.cs

@@ -0,0 +1,101 @@
+using Prism.Commands;
+using Prism.Ioc;
+using Prism.Mvvm;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+
+namespace TeamAAS_VP.ViewModels.Home
+{
+    public class ChangeHeaderViewModel : BindableBase, IDialogAware
+    {
+        ISystemDatabaseService _systemDatabaseService;
+        IContainerProvider _container;
+
+        private string _currentHeadBatchNumber;
+        private int _currentLockCount;
+        private string _lastChangeTime;
+        private string _newHeadBatchNumber;
+
+        public ChangeHeaderViewModel(ISystemDatabaseService systemDatabaseService, IContainerProvider containerProvider)
+        {
+            _systemDatabaseService = systemDatabaseService;
+            _container = containerProvider;
+            ReplaceCommand = new DelegateCommand(ExecuteReplaceCommand, ()=>string.IsNullOrEmpty(NewHeadBatchNumber)).ObservesProperty(() => NewHeadBatchNumber);
+            CloseCommand = new DelegateCommand(ExecuteCloseCommand);
+        }
+
+        public DelegateCommand ReplaceCommand { get; }
+        public DelegateCommand CloseCommand { get; }
+
+        public string CurrentHeadBatchNumber { get => _currentHeadBatchNumber; set => SetProperty(ref _currentHeadBatchNumber, value); }
+        public int CurrentLockCount { get => _currentLockCount; set => SetProperty(ref _currentLockCount, value); }
+        public string LastChangeTime { get => _lastChangeTime; set => SetProperty(ref _lastChangeTime, value); }
+        public string NewHeadBatchNumber { get => _newHeadBatchNumber; set => SetProperty(ref _newHeadBatchNumber, value); }
+
+        public string Title => "更换披头";
+
+        public event Action<IDialogResult> RequestClose;
+
+        public bool CanCloseDialog()
+        {
+            return true;
+        }
+
+        public void OnDialogClosed()
+        {
+            
+        }
+
+        public async void OnDialogOpened(IDialogParameters parameters)
+        {
+            var latestRecord = await _systemDatabaseService.GetLatestHeadChangeRecordAsync();
+            if (latestRecord != null)
+            {
+                CurrentHeadBatchNumber = latestRecord.HeadBatchNumber;
+                CurrentLockCount = latestRecord.CurrentLockCount;
+                LastChangeTime = latestRecord.ChangeTime.ToString("yyyy-MM-dd HH:mm:ss");
+            }
+        }
+
+        private void ExecuteCloseCommand()
+        {
+            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, null));
+        }
+
+        private async void ExecuteReplaceCommand()
+        {
+            try
+            {
+                //当前用户
+                var currentUser = await _systemDatabaseService.GetCurrentUserAsync();
+                //当前用户名
+                string operatorName = currentUser != null ? currentUser.UserName : "未知用户";
+
+                await _systemDatabaseService.RecordHeadChangeAsync(new HeadChangeRecord()
+                {
+                    HeadBatchNumber = NewHeadBatchNumber,
+                    ChangeTime = DateTime.Now,
+                    CurrentLockCount = 0,
+                    OperatorName = operatorName
+                });
+                Management management= _container.Resolve<Management>();
+                management.NumStatistics.HeaderNum = 0;
+
+                RequestClose?.Invoke(new DialogResult(ButtonResult.OK, null));
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("更换披头时出错!", ex);
+                MessageBox.Show("更换披头时出错!"+ex.Message, "AAS", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+
+        }
+    }
+}

+ 100 - 0
TeamAAS-VM/ViewModels/Home/ChangeNozzleViewModel.cs

@@ -0,0 +1,100 @@
+using Prism.Commands;
+using Prism.Ioc;
+using Prism.Mvvm;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using TeamAAS_VP.Core;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+
+namespace TeamAAS_VP.ViewModels.Home
+{
+    public class ChangeNozzleViewModel : BindableBase, IDialogAware
+    {
+        ISystemDatabaseService _systemDatabaseService;
+        IContainerProvider _container;
+
+        private string _currentNozzleBatchNumber;
+        private int _currentLockCount;
+        private string _lastChangeTime;
+        private string _newNozzleBatchNumber;
+
+        public ChangeNozzleViewModel(ISystemDatabaseService systemDatabaseService, IContainerProvider containerProvider)
+        {
+            _systemDatabaseService = systemDatabaseService;
+            _container = containerProvider;
+            ReplaceCommand = new DelegateCommand(ExecuteReplaceCommand, () => string.IsNullOrEmpty(NewNozzleBatchNumber)).ObservesProperty(() => NewNozzleBatchNumber);
+            CloseCommand = new DelegateCommand(ExecuteCloseCommand);
+        }
+
+        public string Title => "更换吸嘴";
+
+        public event Action<IDialogResult> RequestClose;
+
+        public bool CanCloseDialog()
+        {
+            return true;
+        }
+
+        public void OnDialogClosed()
+        {
+
+        }
+
+        public async void OnDialogOpened(IDialogParameters parameters)
+        {
+            var latestRecord = await _systemDatabaseService.GetLatestNozzleChangeRecordAsync();
+            if (latestRecord != null)
+            {
+                CurrentNozzleBatchNumber = latestRecord.NozzleBatchNumber;
+                CurrentLockCount = latestRecord.CurrentLockCount;
+                LastChangeTime = latestRecord.ChangeTime.ToString("yyyy-MM-dd HH:mm:ss");
+            }
+        }
+
+        public DelegateCommand ReplaceCommand { get; }
+        public DelegateCommand CloseCommand { get; }
+
+        public string CurrentNozzleBatchNumber { get => _currentNozzleBatchNumber; set => SetProperty(ref _currentNozzleBatchNumber, value); }
+        public int CurrentLockCount { get => _currentLockCount; set => SetProperty(ref _currentLockCount, value); }
+        public string LastChangeTime { get => _lastChangeTime; set => SetProperty(ref _lastChangeTime, value); }
+        public string NewNozzleBatchNumber { get => _newNozzleBatchNumber; set => SetProperty(ref _newNozzleBatchNumber, value); }
+
+
+        private async void ExecuteReplaceCommand()
+        {
+            try
+            {
+                //当前用户
+                var currentUser = await _systemDatabaseService.GetCurrentUserAsync();
+                //当前用户名
+                string operatorName = currentUser != null ? currentUser.UserName : "未知用户";
+
+                await _systemDatabaseService.RecordNozzleChangeAsync(new NozzleChangeRecord()
+                {
+                    NozzleBatchNumber = NewNozzleBatchNumber,
+                    ChangeTime = DateTime.Now,
+                    CurrentLockCount = 0,
+                    OperatorName = operatorName
+                });
+                Management management = _container.Resolve<Management>();
+                management.NumStatistics.NozzleNum = 0;
+                RequestClose?.Invoke(new DialogResult(ButtonResult.OK, null));
+            }
+            catch (Exception ex)
+            {
+                LogHelper.WriteLogError("更换吸嘴时出错!", ex);
+                MessageBox.Show("更换吸嘴时出错!" + ex.Message, "AAS", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+
+        private void ExecuteCloseCommand()
+        {
+            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, null));
+        }
+    }
+}

+ 0 - 99
TeamAAS-VM/ViewModels/Home/SetAlarmValueViewModel.cs

@@ -1,99 +0,0 @@
-using Prism.Commands;
-using Prism.Mvvm;
-using Prism.Services.Dialogs;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using TeamAAS_VP.Core;
-using TeamAAS_VP.Models;
-
-namespace TeamAAS_VP.ViewModels.Home
-{
-    public class SetAlarmValueViewModel : BindableBase, IDialogAware
-    {
-        #region 属性
-        private int _BitAlarmValue;
-        public int BitAlarmValue
-        {
-            get { return _BitAlarmValue; }
-            set { SetProperty(ref _BitAlarmValue, value); }
-        }
-
-        private int _NozzleAlarmValue;
-        public int NozzleAlarmValue
-        {
-            get { return _NozzleAlarmValue; }
-            set { SetProperty(ref _NozzleAlarmValue, value); }
-        }
-
-        private NumberStatistics _NumStatistics = new NumberStatistics();
-        /// <summary>
-        /// 数量(批头、吸嘴、供料器)
-        /// </summary>
-        public NumberStatistics NumStatistics
-        {
-            get { return _NumStatistics; }
-            set { SetProperty(ref _NumStatistics, value); }
-        }
-        #endregion
-
-        #region 命令
-        private DelegateCommand _ConfirmCommand;
-        public DelegateCommand ConfirmCommand =>
-            _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand));
-
-        private DelegateCommand _CancelCommand;
-        public DelegateCommand CancelCommand =>
-            _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand));
-
-        #endregion
-
-        void ExecuteCancelCommand()
-        {
-            IDialogParameters parameters = new DialogParameters();
-            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
-        }
-        void ExecuteConfirmCommand()
-        {
-            NumStatistics.NozzleAlarmValue = NozzleAlarmValue;
-            NumStatistics.HeaderAlarmValue = BitAlarmValue;
-            FileHelper.WriteJsonFile(NumStatistics, FilePath.NumberStatisticsPath);
-            IDialogParameters parameters = new DialogParameters();
-            RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
-        }
-
-
-        public string Title => "Set Bit/Nozzle Alarm Value";
-
-        public event Action<IDialogResult> RequestClose;
-
-        public bool CanCloseDialog()
-        {
-            return true;
-        }
-
-        public void OnDialogClosed()
-        {
-            
-        }
-
-        public void OnDialogOpened(IDialogParameters parameters)
-        {
-            try
-            {
-                var res = FileHelper.ReadJsonFile<NumberStatistics>(FilePath.NumberStatisticsPath);
-                if (res != null)
-                {
-                    BitAlarmValue = res.HeaderAlarmValue;
-                    NozzleAlarmValue = res.NozzleAlarmValue;
-                }
-            }
-            catch (Exception)
-            {
-
-            }
-        }
-    }
-}

+ 98 - 34
TeamAAS-VM/ViewModels/HomeViewModel.cs

@@ -181,6 +181,23 @@ namespace TeamAAS_VP.ViewModels
         public DelegateCommand TorqueInspectionCommand =>
             _TorqueInspectionCommand ?? (_TorqueInspectionCommand = new DelegateCommand(ExecuteTorqueInspectionCommand));
 
+        //更换吸嘴
+        private DelegateCommand _ChangeNozzleCommand;
+        public DelegateCommand ChangeNozzleCommand =>
+            _ChangeNozzleCommand ?? (_ChangeNozzleCommand = new DelegateCommand(ExecuteChangeNozzleCommand));
+
+        //更换披头
+        private DelegateCommand _ChangeHeaderCommand;
+        public DelegateCommand ChangeHeaderCommand =>
+            _ChangeHeaderCommand ?? (_ChangeHeaderCommand = new DelegateCommand(ExecuteChangeHeaderCommand));
+
+        //添加螺丝
+        private DelegateCommand _AddScrewCommand;
+        public DelegateCommand AddScrewCommand =>
+            _AddScrewCommand ?? (_AddScrewCommand = new DelegateCommand(ExecuteAddScrewCommand));
+
+        
+
         private DelegateCommand _NozzleCounterResetCommand;
         public DelegateCommand NozzleCounterResetCommand =>
             _NozzleCounterResetCommand ?? (_NozzleCounterResetCommand = new DelegateCommand(ExecuteNozzleCounterResetCommand));
@@ -287,20 +304,6 @@ namespace TeamAAS_VP.ViewModels
             Clipboard.SetText(textToCopy);
         }
 
-        void ExecuteTorqueInspectionCommand()
-        {
-            _dialogService.ShowDialog("TorqueCheck", rst =>
-            {
-                //对话框关闭之后的回调函数,可以在这解析结果。
-                ButtonResult result1 = rst.Result;
-
-                if (result1 == ButtonResult.OK)
-                {
-                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
-                }
-            });
-        }
-
         /// <summary>
         /// 添加消息至消息队列
         /// </summary>
@@ -420,7 +423,9 @@ namespace TeamAAS_VP.ViewModels
             }
         }
 
-        //初始化曲线图
+        /// <summary>
+        /// 初始化曲线图
+        /// </summary>
         private void InitLockCurvePlotModel()
         {
             var model = new PlotModel { Title = Lang.锁付曲线, TitleFontSize = 16};
@@ -483,33 +488,91 @@ namespace TeamAAS_VP.ViewModels
 
             //更新曲线图
             App.Current.Dispatcher.BeginInvoke(new Action(() => {
-                var series = LockCurvePlotModel.Series[0] as LineSeries;
-                //var yaxis = LockCurvePlotModel.Axes.FirstOrDefault(p=>p.Key=="Y");
-                //yaxis.Maximum = result.TargetTorque;
-                series.Points.Clear();
-                foreach (var waveData in result.WaveDatas)
+                try
+                {
+                    var series = LockCurvePlotModel.Series[0] as LineSeries;
+                    //var yaxis = LockCurvePlotModel.Axes.FirstOrDefault(p=>p.Key=="Y");
+                    //yaxis.Maximum = result.TargetTorque;
+                    series.Points.Clear();
+                    foreach (var waveData in result.WaveDatas)
+                    {
+                        series.Points.Add(new OxyPlot.DataPoint(waveData.LockAngle, waveData.Torque));
+                    }
+                    LockCurvePlotModel.InvalidatePlot(true);
+                    if (!string.IsNullOrEmpty(result.LockCurveImagePath))
+                    {
+                        PngExporter.Export(LockCurvePlotModel, result.LockCurveImagePath, (int)LockCurvePlotModel.Width, (int)LockCurvePlotModel.Height);
+                    }
+                }
+                catch (Exception ex)
                 {
-                    series.Points.Add(new OxyPlot.DataPoint(waveData.LockAngle, waveData.Torque));
+                    LogHelper.WriteLogError("更新曲线图和保存曲线图时出错!", ex);
                 }
-                LockCurvePlotModel.InvalidatePlot(true);
             }));
 
         }
-        private void SaveAsPng(PlotModel LockCurvePlotModel)
+
+        /// <summary>
+        /// 添加螺丝
+        /// </summary>
+        private void ExecuteAddScrewCommand()
+        {
+            _dialogService.ShowDialog("AddScrew", rst =>
+            {
+                //对话框关闭之后的回调函数,可以在这解析结果。
+                ButtonResult result1 = rst.Result;
+
+                if (result1 == ButtonResult.OK)
+                {
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                }
+            });
+        }
+
+        /// <summary>
+        /// 更换披头
+        /// </summary>
+        void ExecuteChangeHeaderCommand()
         {
-            string savePath = $"E:\\Data\\螺丝锁付曲线图\\{DateTime.Now:yyyyMMdd_HHmmss}.png";
+            _dialogService.ShowDialog("ChangeHeader", rst =>
+            {
+                //对话框关闭之后的回调函数,可以在这解析结果。
+                ButtonResult result1 = rst.Result;
+
+                if (result1 == ButtonResult.OK)
+                {
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
+                }
+            });
+        }
 
-            Application.Current.Dispatcher.Invoke(() =>
+        /// <summary>
+        /// 更换吸嘴
+        /// </summary>
+        void ExecuteChangeNozzleCommand()
+        {
+            _dialogService.ShowDialog("ChangeNozzle", rst =>
             {
-                try
+                //对话框关闭之后的回调函数,可以在这解析结果。
+                ButtonResult result1 = rst.Result;
+
+                if (result1 == ButtonResult.OK)
                 {
-                    var exporter = new PngExporter { Width = 1200, Height = 800 };
-                    exporter.ExportToFile(LockCurvePlotModel, savePath);
-                    //SaveStatus = $"已保存到:{savePath}";
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
                 }
-                catch (Exception ex)
+            });
+        }
+
+        void ExecuteTorqueInspectionCommand()
+        {
+            _dialogService.ShowDialog("TorqueCheck", rst =>
+            {
+                //对话框关闭之后的回调函数,可以在这解析结果。
+                ButtonResult result1 = rst.Result;
+
+                if (result1 == ButtonResult.OK)
                 {
-                    //SaveStatus = $"保存失败:{ex.Message}";
+                    _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
                 }
             });
         }
@@ -527,11 +590,12 @@ namespace TeamAAS_VP.ViewModels
                 }
             });
         }
+
         void ExecuteNozzleCounterResetCommand()
         {
-            management.NumStatistics.NozzleNum = 0;
-            management.NumStatistics.HeaderNum = 0;
-            FileHelper.WriteJsonFile(management.NumStatistics, FilePath.NumberStatisticsPath);
+            //management.NumStatistics.NozzleNum = 0;
+            //management.NumStatistics.HeaderNum = 0;
+            //FileHelper.WriteJsonFile(management.NumStatistics, FilePath.NumberStatisticsPath);
             _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.完成, Duration = 1 });
         }
         #endregion

+ 4 - 1
TeamAAS-VM/ViewModels/MainWindowViewModel.cs

@@ -297,6 +297,9 @@ namespace TeamAAS_VP.ViewModels
             SendTaskMessage($"{Lang.数据库读取完成}!", MessageLevel.Info);
             curvalue += 1;
 
+            //
+            await management.ReadConfig();
+
             //-------------------SETP 2: 初始化Feeder-----------------------------------------------------------------------
             curvalue += 1;
             _ = App.Current.Dispatcher.BeginInvoke(new Action(() =>
@@ -386,7 +389,7 @@ namespace TeamAAS_VP.ViewModels
                 _eventAggregator.GetEvent<ProgressNotification>().Publish(new UProgressBar.ProgressParameter { Maximum = max, Minimum = 0, SubTitle = $"{Lang.初始化中}...", Message = $"{Lang.正在开启}...", Value = curvalue });
             }));
             await management.InitSignal();
-            management.ReadConfig();
+            
 
             //当前日期
             CurrentTime = Guid.NewGuid();

+ 3 - 0
TeamAAS-VM/ViewModels/Product/ProcedureParamsViewModel.cs

@@ -309,6 +309,9 @@ namespace TeamAAS_VP.ViewModels.Product
             });
         }
 
+        /// <summary>
+        /// 选择保存图片路径
+        /// </summary>
         void ExecuteSelectSaveImagePathCommand()
         {
             if (SelectProcedure == null)

+ 62 - 2
TeamAAS-VM/ViewModels/SettingViewModel.cs

@@ -332,7 +332,6 @@ namespace TeamAAS_VP.ViewModels
             get { return _DeviceInfo; }
             set { SetProperty(ref _DeviceInfo, value); }
         }
-
         #endregion
 
         #region 命令
@@ -412,6 +411,9 @@ namespace TeamAAS_VP.ViewModels
         private DelegateCommand _SaveDeviceInfoCommand;
         public DelegateCommand SaveDeviceInfoCommand => _SaveDeviceInfoCommand ?? (_SaveDeviceInfoCommand = new DelegateCommand(ExecuteSaveDeviceInfoCommand));
 
+        private DelegateCommand _BrowseScrewCurvePathCommand;
+        public DelegateCommand BrowseScrewCurvePathCommand => _BrowseScrewCurvePathCommand ?? (_BrowseScrewCurvePathCommand = new DelegateCommand(ExecuteBrowseScrewCurvePathCommand));
+
         private DelegateCommand _AddScrewFeederCommand;
         public DelegateCommand AddScrewFeederCommand =>
             _AddScrewFeederCommand ?? (_AddScrewFeederCommand = new DelegateCommand(ExecuteAddScrewFeederCommand));
@@ -521,7 +523,7 @@ namespace TeamAAS_VP.ViewModels
                         if (robot != null)
                         {
                             //更新连接参数
-                            robot.SafeZ=item.SafeZ;
+                            robot.SafeZ = item.SafeZ;
                         }
                         _configService.AddOrUpdateRobot(item);
                     }
@@ -1321,6 +1323,12 @@ namespace TeamAAS_VP.ViewModels
                 DeviceInfo = _configService.GetDeviceInfo();
             }
 
+            if (ScrewFeederList==null)
+            {
+                var screwFeeders = _configService.GetAllScrewFeeders();
+                ScrewFeederList = new ObservableCollection<ScrewFeederInfo>(screwFeeders);
+            }
+
             CurrentLanguage = _configService.GetSystemConfiguration().CurrentLanguage;
 
             // load system parameters into UI
@@ -1340,6 +1348,8 @@ namespace TeamAAS_VP.ViewModels
                 FixedCameraPutNum = sys.FixedCameraPutNum;
                 FixedCameracheckNum = sys.FixedCameracheckNum;
                 ScrewTeachNum = sys.ScrewTeachNum;
+                IsSaveScrewCurve= sys.IsSaveScrewCurve;
+                ScrewCurvePath= sys.ScrewCurvePath;
             }
 
             if (isFirstLoad)
@@ -1502,6 +1512,54 @@ namespace TeamAAS_VP.ViewModels
             set => SetProperty(ref _ScrewTeachNum, value);
         }
 
+        private bool _IsSaveScrewCurve;
+        /// <summary>
+        /// 是否保存锁付曲线图
+        /// </summary>
+        public bool IsSaveScrewCurve
+        {
+            get { return _IsSaveScrewCurve; }
+            set { SetProperty(ref _IsSaveScrewCurve, value); }
+        }
+
+        private string _ScrewCurvePath;
+        /// <summary>
+        /// 锁付曲线图保存路径
+        /// </summary>
+        public string ScrewCurvePath
+        {
+            get { return _ScrewCurvePath; }
+            set { SetProperty(ref _ScrewCurvePath, value); }
+        }
+
+        /// <summary>
+        /// 选择锁付曲线图保存路径
+        /// </summary>
+        void ExecuteBrowseScrewCurvePathCommand()
+        {
+            try
+            {
+                using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
+                {
+                    dlg.Description = "选择锁付曲线图保存路径";
+                    dlg.ShowNewFolderButton = true;
+                    if (!string.IsNullOrEmpty(ScrewCurvePath)) dlg.SelectedPath = ScrewCurvePath;
+                    var result = dlg.ShowDialog();
+                    if (result == System.Windows.Forms.DialogResult.OK || result == System.Windows.Forms.DialogResult.Yes)
+                    {
+                        ScrewCurvePath = dlg.SelectedPath;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 2 });
+            }
+        }
+
+        /// <summary>
+        /// 保存系统参数
+        /// </summary>
         async void ExecuteSaveSystemParametersCommand()
         {
             var view = new ShowMessage($"AAS", Lang.是否保存系统参数);
@@ -1521,6 +1579,8 @@ namespace TeamAAS_VP.ViewModels
                     sysConfig.FixedCameraPutNum = FixedCameraPutNum;
                     sysConfig.FixedCameracheckNum = FixedCameracheckNum;
                     sysConfig.ScrewTeachNum = ScrewTeachNum;
+                    sysConfig.IsSaveScrewCurve = IsSaveScrewCurve;
+                    sysConfig.ScrewCurvePath = ScrewCurvePath;
 
                     _configService.SaveSystemConfiguration(sysConfig);
                     await management.WriteSystemParameterToPlcAsync(sysConfig);

+ 7 - 5
TeamAAS-VM/ViewModels/Statistics/LockResultRecoredQueryViewModel.cs

@@ -212,7 +212,7 @@ namespace TeamAAS_VP.ViewModels.Statistics
                 // write in streaming manner to support large sets
                 using (var sw = new StreamWriter(path, false, Encoding.UTF8))
                 {
-                    sw.WriteLine("Id,Number,ProductNumber,Timestamp,LockTurns,LockTorque,LockDuration,LockPassed,ResultDetails,ScrewdriverProgramNumber,Pressure,TargetTorque,TargetTurnsUpperLimit,TargetTurnsLowerLimit,TargetLockSpeed,StartPositionX,StartPositionY,StartPositionZ,EndPositionZ,LockDescentSpeed,FeederNumber,NozzleNumber,OperatorName,ProductImagePath");
+                    sw.WriteLine("Id,Number,ProductNumber,Timestamp,LockTurns,LockTorque,LockDuration,LockPassed,ResultDetails,ScrewdriverProgramNumber,Pressure,TargetTorqueUpperLimit,TargetTorqueLowerLimit,TargetTurnsUpperLimit,TargetTurnsLowerLimit,TargetLockSpeed,StartPositionX,StartPositionY,StartPositionZ,EndPositionZ,LockDescentSpeed,FeederNumber,NozzleNumber,OperatorName,ProductImagePath,LockCurveImagePath");
                     foreach (var r in all)
                     {
                         var line = string.Join(",",
@@ -227,19 +227,21 @@ namespace TeamAAS_VP.ViewModels.Statistics
                             EscapeCsv(r.ResultDetails),
                             EscapeCsv(r.ScrewdriverProgramNumber.ToString()),
                             EscapeCsv(r.Pressure.ToString()),
-                            EscapeCsv(r.TargetTorque.ToString()),
+                            EscapeCsv(r.TargetTorqueUpperLimit.ToString()),
+                            EscapeCsv(r.TargetTorqueLowerLimit.ToString()),
                             EscapeCsv(r.TargetTurnsUpperLimit.ToString()),
                             EscapeCsv(r.TargetTurnsLowerLimit.ToString()),
                             EscapeCsv(r.TargetLockSpeed.ToString()),
-                            EscapeCsv(r.StartPositionX.ToString()),
-                            EscapeCsv(r.StartPositionY.ToString()),
+                            EscapeCsv(r.PositionX.ToString()),
+                            EscapeCsv(r.PositionY.ToString()),
                             EscapeCsv(r.StartPositionZ.ToString()),
                             EscapeCsv(r.EndPositionZ.ToString()),
                             EscapeCsv(r.LockDescentSpeed.ToString()),
                             EscapeCsv(r.FeederNumber.ToString()),
                             EscapeCsv(r.NozzleNumber),
                             EscapeCsv(r.OperatorName),
-                            EscapeCsv(r.ProductImagePath)
+                            EscapeCsv(r.ProductImagePath),
+                            EscapeCsv(r.LockCurveImagePath)
                         );
                         sw.WriteLine(line);
                     }

+ 145 - 0
TeamAAS-VM/Views/Home/AddScrew.xaml

@@ -0,0 +1,145 @@
+<UserControl x:Class="TeamAAS_VP.Views.Home.AddScrew"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:prism="http://prismlibrary.com/"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:enums="clr-namespace:TeamAAS_VP.Enums"
+             xmlns:valRules="clr-namespace:TeamAAS_VP.Identity"
+             xmlns:ext="clr-namespace:TeamAAS_VP.Extension"
+             xmlns:c="clr-namespace:TeamAAS_VP.ValueConverter"
+             xmlns:lex="http://wpflocalizeextension.codeplex.com"
+             xmlns:vm="clr-namespace:TeamAAS_VP.ViewModels.Home"
+             lex:LocalizeDictionary.DesignCulture="zh-CN"
+             lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
+             lex:ResxLocalizationProvider.DefaultDictionary="Lang"
+             mc:Ignorable="d"
+             d:Background="White"
+             d:DataContext="{d:DesignInstance Type=vm:AddScrewViewModel}"
+             prism:ViewModelLocator.AutoWireViewModel="True"
+             FontFamily="{DynamicResource DefaultFont}">
+    <prism:Dialog.WindowStyle>
+        <Style TargetType="Window">
+            <Setter Property="prism:Dialog.WindowStartupLocation"
+                    Value="CenterScreen" />
+            <!--<Setter Property="WindowChrome.WindowChrome">
+            <Setter.Value>
+                <WindowChrome CaptionHeight="0"
+                              ResizeBorderThickness="1" />
+            </Setter.Value>
+        </Setter>-->
+            <Setter Property="WindowStyle"
+                    Value="None" />
+            <Setter Property="AllowDrop"
+                    Value="True" />
+            <Setter Property="BorderThickness"
+                    Value="0" />
+            <Setter Property="ShowInTaskbar"
+                    Value="False" />
+            <Setter Property="SizeToContent"
+                    Value="WidthAndHeight" />
+            <Setter Property="WindowState"
+                    Value="Normal" />
+            <Setter Property="Topmost"
+                    Value="True" />
+        </Style>
+    </prism:Dialog.WindowStyle>
+
+    <Grid Margin="16">
+        <Grid.RowDefinitions>
+            <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="选择供料器:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <ComboBox Grid.Row="0"
+                  Grid.Column="1"
+                  ItemsSource="{Binding ScrewFeeders}"
+                  SelectedItem="{Binding SelectedFeeder}"
+                  DisplayMemberPath="Name"
+                  Margin="4">
+            <b:Interaction.Triggers>
+                <b:EventTrigger EventName="SelectionChanged">
+                    <b:InvokeCommandAction Command="{Binding FeederSelectedCommand}" />
+                </b:EventTrigger>
+            </b:Interaction.Triggers>
+        </ComboBox>
+
+        <TextBlock Grid.Row="1"
+                   Grid.Column="0"
+                   Text="当前批号:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="1"
+                   Grid.Column="1"
+                   Text="{Binding CurrentBatchNumber}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="2"
+                   Grid.Column="0"
+                   Text="剩余数量:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="2"
+                   Grid.Column="1"
+                   Text="{Binding RemainingCount}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="3"
+                   Grid.Column="0"
+                   Text="上次添加时间:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="3"
+                   Grid.Column="1"
+                   Text="{Binding LastAddTime}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="4"
+                   Grid.Column="0"
+                   Text="添加批号:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBox Grid.Row="4"
+                 Grid.Column="1"
+                 Text="{Binding NewBatchNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+                 Margin="4" />
+
+        <StackPanel Grid.Row="5"
+                    Grid.ColumnSpan="2"
+                    Orientation="Horizontal"
+                    HorizontalAlignment="Right"
+                    Margin="4">
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="清零"
+                    Command="{Binding ClearCommand}"
+                    Margin="4" />
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="添加"
+                    Command="{Binding AddCommand}"
+                    Margin="4" />
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="关闭"
+                    Command="{Binding CloseCommand}"
+                    Margin="4" />
+        </StackPanel>
+    </Grid>
+</UserControl>

+ 15 - 0
TeamAAS-VM/Views/Home/AddScrew.xaml.cs

@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace TeamAAS_VP.Views.Home
+{
+    /// <summary>
+    /// Interaction logic for AddScrew
+    /// </summary>
+    public partial class AddScrew : UserControl
+    {
+        public AddScrew()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 123 - 0
TeamAAS-VM/Views/Home/ChangeHeader.xaml

@@ -0,0 +1,123 @@
+<UserControl x:Class="TeamAAS_VP.Views.Home.ChangeHeader"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:prism="http://prismlibrary.com/"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:enums="clr-namespace:TeamAAS_VP.Enums"
+             xmlns:valRules="clr-namespace:TeamAAS_VP.Identity"
+             xmlns:ext="clr-namespace:TeamAAS_VP.Extension"
+             xmlns:c="clr-namespace:TeamAAS_VP.ValueConverter"
+             xmlns:lex="http://wpflocalizeextension.codeplex.com"
+             xmlns:vm="clr-namespace:TeamAAS_VP.ViewModels.Home"
+             lex:LocalizeDictionary.DesignCulture="zh-CN"
+             lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
+             lex:ResxLocalizationProvider.DefaultDictionary="Lang"
+             mc:Ignorable="d"
+             d:Background="White"
+             d:DataContext="{d:DesignInstance Type=vm:ChangeHeaderViewModel}"
+             prism:ViewModelLocator.AutoWireViewModel="True"
+             FontFamily="{DynamicResource DefaultFont}">
+    <prism:Dialog.WindowStyle>
+        <Style TargetType="Window">
+            <Setter Property="prism:Dialog.WindowStartupLocation"
+                    Value="CenterScreen" />
+            <!--<Setter Property="WindowChrome.WindowChrome">
+        <Setter.Value>
+            <WindowChrome CaptionHeight="0"
+                          ResizeBorderThickness="1" />
+        </Setter.Value>
+    </Setter>-->
+            <Setter Property="WindowStyle"
+                    Value="None" />
+            <Setter Property="AllowDrop"
+                    Value="True" />
+            <Setter Property="BorderThickness"
+                    Value="0" />
+            <Setter Property="ShowInTaskbar"
+                    Value="False" />
+            <Setter Property="SizeToContent"
+                    Value="WidthAndHeight" />
+            <Setter Property="WindowState"
+                    Value="Normal" />
+            <Setter Property="Topmost"
+                    Value="True" />
+        </Style>
+    </prism:Dialog.WindowStyle>
+
+    <Grid Margin="16">
+        <Grid.RowDefinitions>
+            <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="当前披头批号:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="0"
+                   Grid.Column="1"
+                   Text="{Binding CurrentHeadBatchNumber}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="1"
+                   Grid.Column="0"
+                   Text="当前锁付次数:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="1"
+                   Grid.Column="1"
+                   Text="{Binding CurrentLockCount}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="2"
+                   Grid.Column="0"
+                   Text="上次更换时间:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="2"
+                   Grid.Column="1"
+                   Text="{Binding LastChangeTime}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="3"
+                   Grid.Column="0"
+                   Text="新披头批号:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBox Grid.Row="3"
+                 Grid.Column="1"
+                 MinWidth="150"
+                 Text="{Binding NewHeadBatchNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+                 Margin="4" />
+
+        <StackPanel Grid.Row="4"
+                    Grid.ColumnSpan="2"
+                    Orientation="Horizontal"
+                    HorizontalAlignment="Right"
+                    Margin="4">
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="更换"
+                    Command="{Binding ReplaceCommand}"
+                    Margin="4" />
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="关闭"
+                    Command="{Binding CloseCommand}"
+                    Margin="4" />
+        </StackPanel>
+    </Grid>
+</UserControl>

+ 15 - 0
TeamAAS-VM/Views/Home/ChangeHeader.xaml.cs

@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace TeamAAS_VP.Views.Home
+{
+    /// <summary>
+    /// Interaction logic for ChangeHeader
+    /// </summary>
+    public partial class ChangeHeader : UserControl
+    {
+        public ChangeHeader()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 123 - 0
TeamAAS-VM/Views/Home/ChangeNozzle.xaml

@@ -0,0 +1,123 @@
+<UserControl x:Class="TeamAAS_VP.Views.Home.ChangeNozzle"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:prism="http://prismlibrary.com/"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:enums="clr-namespace:TeamAAS_VP.Enums"
+             xmlns:valRules="clr-namespace:TeamAAS_VP.Identity"
+             xmlns:ext="clr-namespace:TeamAAS_VP.Extension"
+             xmlns:c="clr-namespace:TeamAAS_VP.ValueConverter"
+             xmlns:lex="http://wpflocalizeextension.codeplex.com"
+             xmlns:vm="clr-namespace:TeamAAS_VP.ViewModels.Home"
+             lex:LocalizeDictionary.DesignCulture="zh-CN"
+             lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
+             lex:ResxLocalizationProvider.DefaultDictionary="Lang"
+             mc:Ignorable="d"
+             d:Background="White"
+             d:DataContext="{d:DesignInstance Type=vm:ChangeNozzleViewModel}"
+             prism:ViewModelLocator.AutoWireViewModel="True"
+             FontFamily="{DynamicResource DefaultFont}">
+    <prism:Dialog.WindowStyle>
+        <Style TargetType="Window">
+            <Setter Property="prism:Dialog.WindowStartupLocation"
+                    Value="CenterScreen" />
+            <!--<Setter Property="WindowChrome.WindowChrome">
+    <Setter.Value>
+        <WindowChrome CaptionHeight="0"
+                      ResizeBorderThickness="1" />
+    </Setter.Value>
+</Setter>-->
+            <Setter Property="WindowStyle"
+                    Value="None" />
+            <Setter Property="AllowDrop"
+                    Value="True" />
+            <Setter Property="BorderThickness"
+                    Value="0" />
+            <Setter Property="ShowInTaskbar"
+                    Value="False" />
+            <Setter Property="SizeToContent"
+                    Value="WidthAndHeight" />
+            <Setter Property="WindowState"
+                    Value="Normal" />
+            <Setter Property="Topmost"
+                    Value="True" />
+        </Style>
+    </prism:Dialog.WindowStyle>
+
+    <Grid Margin="16">
+        <Grid.RowDefinitions>
+            <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="当前吸嘴批号:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="0"
+                   Grid.Column="1"
+                   Text="{Binding CurrentNozzleBatchNumber}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="1"
+                   Grid.Column="0"
+                   Text="当前锁付次数:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="1"
+                   Grid.Column="1"
+                   Text="{Binding CurrentLockCount}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="2"
+                   Grid.Column="0"
+                   Text="上次更换时间:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBlock Grid.Row="2"
+                   Grid.Column="1"
+                   Text="{Binding LastChangeTime}"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+
+        <TextBlock Grid.Row="3"
+                   Grid.Column="0"
+                   Text="新吸嘴批号:"
+                   VerticalAlignment="Center"
+                   Margin="4" />
+        <TextBox Grid.Row="3"
+                 Grid.Column="1"
+                 MinWidth="150"
+                 Text="{Binding NewNozzleBatchNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+                 Margin="4" />
+
+        <StackPanel Grid.Row="4"
+                    Grid.ColumnSpan="2"
+                    Orientation="Horizontal"
+                    HorizontalAlignment="Right"
+                    Margin="4">
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="更换"
+                    Command="{Binding ReplaceCommand}"
+                    Margin="4" />
+            <Button Style="{StaticResource MaterialDesignRaisedButton}"
+                    Content="关闭"
+                    Command="{Binding CloseCommand}"
+                    Margin="4" />
+        </StackPanel>
+    </Grid>
+</UserControl>

+ 15 - 0
TeamAAS-VM/Views/Home/ChangeNozzle.xaml.cs

@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace TeamAAS_VP.Views.Home
+{
+    /// <summary>
+    /// Interaction logic for ChangeNozzle
+    /// </summary>
+    public partial class ChangeNozzle : UserControl
+    {
+        public ChangeNozzle()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 0 - 94
TeamAAS-VM/Views/Home/SetAlarmValue.xaml

@@ -1,94 +0,0 @@
-<UserControl x:Class="TeamAAS_VP.Views.Home.SetAlarmValue"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-             xmlns:local="clr-namespace:TeamAAS_VP.Views.Home"
-             mc:Ignorable="d" 
-             xmlns:prism="http://prismlibrary.com/"
-             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
-             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
-             xmlns:sys="clr-namespace:System;assembly=mscorlib"
-             xmlns:enums="clr-namespace:TeamAAS_VP.Enums"
-             xmlns:uControl="clr-namespace:TeamAAS_VP.Controls"
-             xmlns:convert="clr-namespace:TeamAAS_VP.ValueConverter"
-             xmlns:valRules="clr-namespace:TeamAAS_VP.Identity"
-             xmlns:validationRule="clr-namespace:TeamAAS_VP.ValidationRules"
-             xmlns:ext="clr-namespace:TeamAAS_VP.Extension"
-             xmlns:lex="http://wpflocalizeextension.codeplex.com"
-             xmlns:vm="clr-namespace:TeamAAS_VP.ViewModels.Home"
-             lex:LocalizeDictionary.DesignCulture="zh-CN"
-             lex:ResxLocalizationProvider.DefaultAssembly="TeamAAS-VP"
-             lex:ResxLocalizationProvider.DefaultDictionary="Lang"
-             d:Background="White"
-             d:DataContext="{d:DesignInstance Type=vm:SetAlarmValueViewModel}"
-             prism:ViewModelLocator.AutoWireViewModel="True"
-             FontFamily="{DynamicResource DefaultFont}">
-    <Grid HorizontalAlignment="Center">
-        <Grid.RowDefinitions>
-            <RowDefinition />
-            <RowDefinition />
-            <RowDefinition />
-            <RowDefinition />
-        </Grid.RowDefinitions>
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition />
-            <ColumnDefinition />
-        </Grid.ColumnDefinitions>
-        <TextBlock Text="{lex:Loc 设置批头吸嘴报警值}"
-                   FontSize="20" 
-                   HorizontalAlignment="Center"
-                   Margin="50,30"
-                   Grid.ColumnSpan="2"/>
-
-        <TextBlock Text="{lex:Loc 设置批头报警值}"
-                   FontSize="14"
-                   HorizontalAlignment="Right"
-                   Margin="10"
-                   Grid.Row="1"
-                   Grid.Column="0"/>
-
-        <TextBlock Text="{lex:Loc 设置吸嘴报警值}"
-                   FontSize="14"
-                   HorizontalAlignment="Right"
-                   Margin="10"
-                   Grid.Row="2"
-                   Grid.Column="0" />
-
-        <TextBox Text="{Binding BitAlarmValue}"
-                 FontSize="14"
-                 HorizontalAlignment="Left"
-                 Margin="10"
-                 Grid.Row="1"
-                 Grid.Column="1"
-                 Width="100"/>
-
-        <TextBox Text="{Binding NozzleAlarmValue}"
-                 FontSize="14"
-                 HorizontalAlignment="Left"
-                 Margin="10"
-                 Grid.Row="2"
-                 Grid.Column="1"
-                 Width="100" />
-
-        <StackPanel Orientation="Horizontal"
-                    Margin="10,3"
-                    HorizontalAlignment="Right"
-                    Grid.Row="3"
-                    Grid.ColumnSpan="2">
-            <Button Content="{lex:Loc 确定}"
-                    VerticalContentAlignment="Center"
-                    materialDesign:ButtonAssist.CornerRadius="10"
-                    Style="{StaticResource MaterialDesignRaisedDarkButton}"
-                    Command="{Binding ConfirmCommand}"
-                    Margin="10,3" />
-            <Button Content="{lex:Loc 取消}"
-                    VerticalContentAlignment="Center"
-                    materialDesign:ButtonAssist.CornerRadius="10"
-                    Style="{StaticResource MaterialDesignRaisedDarkButton}"
-                    Command="{Binding CancelCommand}"
-                    Margin="10,3" />
-        </StackPanel>
-
-    </Grid>
-</UserControl>

+ 0 - 28
TeamAAS-VM/Views/Home/SetAlarmValue.xaml.cs

@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace TeamAAS_VP.Views.Home
-{
-    /// <summary>
-    /// SetAlarmValue.xaml 的交互逻辑
-    /// </summary>
-    public partial class SetAlarmValue : UserControl
-    {
-        public SetAlarmValue()
-        {
-            InitializeComponent();
-        }
-    }
-}

+ 13 - 6
TeamAAS-VM/Views/HomeView.xaml

@@ -15,8 +15,8 @@
              lex:ResxLocalizationProvider.DefaultDictionary="Lang"
              prism:ViewModelLocator.AutoWireViewModel="True"
              mc:Ignorable="d"
-             d:Height="600"
-             d:Width="1024"
+             d:Height="1080"
+             d:Width="1920"
              d:Background="White"
              d:DataContext="{d:DesignInstance Type=vm:HomeViewModel}"
              Background="Transparent"
@@ -351,17 +351,24 @@
                             Style="{StaticResource MaterialDesignRaisedButton}"
                             Command="{Binding ResetCounterCommand}"
                             IsEnabled="{Binding IsAllowEdit}" />
-                    <Button Content="{lex:Loc 批头吸嘴计数清零}"
+                    <Button Content="添加螺丝"
                             Margin="3"
                             materialDesign:ButtonAssist.CornerRadius="10"
                             Style="{StaticResource MaterialDesignRaisedButton}"
-                            Command="{Binding NozzleCounterResetCommand}"
+                            Command="{Binding AddScrewCommand}"
+                            IsEnabled="{Binding IsAllowEdit}" /> 
+
+                    <Button Content="更换披头"
+                            Margin="3"
+                            materialDesign:ButtonAssist.CornerRadius="10"
+                            Style="{StaticResource MaterialDesignRaisedButton}"
+                            Command="{Binding ChangeHeaderCommand}"
                             IsEnabled="{Binding IsAllowEdit}" />
-                    <Button Content="{lex:Loc 设置批头吸嘴报警值}"
+                    <Button Content="更换吸嘴"
                             Margin="3"
                             materialDesign:ButtonAssist.CornerRadius="10"
                             Style="{StaticResource MaterialDesignRaisedButton}"
-                            Command="{Binding SetAlarmValueCommand}"
+                            Command="{Binding ChangeNozzleCommand}"
                             IsEnabled="{Binding IsAllowEdit}" />
                 </StackPanel>
 

+ 46 - 19
TeamAAS-VM/Views/SettingView.xaml

@@ -1735,7 +1735,7 @@
                         </Button>
 
                     </StackPanel>
-                    <!--PLC列表-->
+                    <!--螺丝供料器列表-->
                     <TreeView Grid.Row="1"
                               Grid.Column="0"
                               SelectedItemChanged="TreeView_SelectedItemChanged">
@@ -1812,7 +1812,7 @@
                             </Grid.RowDefinitions>
                             <Grid.ColumnDefinitions>
                                 <ColumnDefinition Width="auto" />
-                                <ColumnDefinition Width="200" />
+                                <ColumnDefinition MinWidth="200" />
                                 <ColumnDefinition Width="*" />
                             </Grid.ColumnDefinitions>
 
@@ -1864,8 +1864,8 @@
                                        VerticalAlignment="Center" />
                             <mah:NumericUpDown Grid.Row="3"
                                                Grid.Column="1"
-                                               Margin="10,0"
-                                               Width="200"
+                                               Margin="10,5"
+                                               MinWidth="200"
                                                Minimum="0"
                                                Maximum="10000"
                                                Value="{Binding SelectScrewFeeder.PackSize,Mode=TwoWay}"
@@ -1881,7 +1881,7 @@
                             <mah:NumericUpDown Grid.Row="4"
                                                Grid.Column="1"
                                                Margin="10,0"
-                                               Width="200"
+                                               MinWidth="200"
                                                Minimum="0"
                                                Maximum="10000"
                                                Value="{Binding SelectScrewFeeder.LowLevelThreshold,Mode=TwoWay}"
@@ -1896,7 +1896,7 @@
                                        Text="是否启用低量报警:" />
                             <CheckBox Grid.Row="5"
                                       Grid.Column="1"
-                                      Margin="10,0"
+                                      Margin="10,5"
                                       IsChecked="{Binding SelectScrewFeeder.LowLevelAlarmEnabled,Mode=TwoWay}"
                                       VerticalAlignment="Center" />
 
@@ -2179,6 +2179,7 @@
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
+                            <RowDefinition Height="Auto" />
                         </Grid.RowDefinitions>
 
                         <!-- 相机拍照次数 -->
@@ -2339,6 +2340,27 @@
                                            Maximum="100"
                                            FontSize="{DynamicResource Font.Size.Body3}" />
 
+                        <CheckBox Grid.Row="10"
+                                  Grid.Column="0"
+                                  IsChecked="{Binding IsSaveScrewCurve,Mode=TwoWay}"
+                                  VerticalAlignment="Center"
+                                  HorizontalAlignment="Right"
+                                  Margin="0,0,6,0">保存锁付曲线图</CheckBox>
+                        <!-- 保存锁付曲线设置 -->
+                        <StackPanel Grid.Row="10"
+                                    Grid.Column="1"
+                                    Grid.ColumnSpan="2"
+                                    Orientation="Horizontal"
+                                    VerticalAlignment="Center"
+                                    IsEnabled="{Binding IsSaveScrewCurve,Converter={StaticResource InvertBooleanConverter}}">
+                            <TextBox MinWidth="200"
+                                     Text="{Binding ScrewCurvePath,Mode=TwoWay}" />
+                            <Button Style="{StaticResource MaterialDesignFloatingActionMiniDarkButton}"
+                                    Command="{Binding BrowseScrewCurvePathCommand}"
+                                    Margin="6,0">
+                                <materialDesign:PackIcon Kind="FolderOpen" />
+                            </Button>
+                        </StackPanel>
                         <!-- 保存按钮 -->
                         <StackPanel Grid.Row="10"
                                     Grid.ColumnSpan="2"
@@ -2346,20 +2368,25 @@
                                     HorizontalAlignment="Right"
                                     VerticalAlignment="Center"
                                     Margin="5">
-                            <Button Style="{StaticResource MaterialDesignRaisedButton}"
-                                    materialDesign:ButtonAssist.CornerRadius="10"
-                                    MinWidth="120"
-                                    Command="{Binding SaveSystemParametersCommand}"
-                                    IsEnabled="{Binding IsAllowEdit}">
-                                <StackPanel Orientation="Horizontal"
-                                            HorizontalAlignment="Center">
-                                    <materialDesign:PackIcon Kind="ContentSaveCheck"
-                                                             Margin="0,0,6,0" />
-                                    <TextBlock VerticalAlignment="Center"
-                                               Text="{lex:Loc 保存系统参数}" />
-                                </StackPanel>
-                            </Button>
+
+
                         </StackPanel>
+                        <Button Grid.Row="11"
+                                Grid.Column="1"
+                                Margin="0,6"
+                                Style="{StaticResource MaterialDesignRaisedButton}"
+                                materialDesign:ButtonAssist.CornerRadius="10"
+                                MinWidth="120"
+                                Command="{Binding SaveSystemParametersCommand}"
+                                IsEnabled="{Binding IsAllowEdit}">
+                            <StackPanel Orientation="Horizontal"
+                                        HorizontalAlignment="Center">
+                                <materialDesign:PackIcon Kind="ContentSaveCheck"
+                                                         Margin="0,0,6,0" />
+                                <TextBlock VerticalAlignment="Center"
+                                           Text="{lex:Loc 保存系统参数}" />
+                            </StackPanel>
+                        </Button>
                     </Grid>
                 </StackPanel>
             </TabItem>