diff --git a/App.xaml b/App.xaml index e1da920..c50692e 100644 --- a/App.xaml +++ b/App.xaml @@ -3,5 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Views/MainWindow.xaml"> + + \ No newline at end of file diff --git a/Converters/BoolToStringConverter.cs b/Converters/BoolToStringConverter.cs new file mode 100644 index 0000000..480d5da --- /dev/null +++ b/Converters/BoolToStringConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace MembranePoreTester.Converters +{ + public class BoolToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (value is bool b && b) ? "开" : "关"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString() == "开"; + } + } +} \ No newline at end of file diff --git a/Helpers/ExportHelper.cs b/Helpers/ExportHelper.cs index 510ee05..a771ade 100644 --- a/Helpers/ExportHelper.cs +++ b/Helpers/ExportHelper.cs @@ -3,6 +3,11 @@ using System.IO; public static class ExportHelper { + static ExportHelper() + { + // 设置许可上下文为非商业用途(开源项目/个人学习) + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + } public static void ExportBubblePoint(BubblePointEntity entity, string filePath) { using var package = new ExcelPackage(); diff --git a/Helpers/ITestRecord.cs b/Helpers/ITestRecord.cs index 5bd3f6e..f688397 100644 --- a/Helpers/ITestRecord.cs +++ b/Helpers/ITestRecord.cs @@ -1,57 +1,141 @@ public interface ITestRecord { + /// 主键ID,自增 int Id { get; set; } + + /// 工位编号(1~3) int StationId { get; set; } + + /// 测试日期时间 DateTime TestDate { get; set; } + + /// 测试人员姓名 string Tester { get; set; } // 公共字段 } +/// +/// 泡点法测试记录实体,用于数据库存储。 +/// 对应 GB/T 32361-2015 标准中的最大孔径测试记录。 +/// public class BubblePointEntity : ITestRecord { + /// 主键ID,自增 public int Id { get; set; } + + /// 工位编号(1~3) public int StationId { get; set; } + + /// 测试日期时间 public DateTime TestDate { get; set; } - public string Tester { get; set; } - public string SampleType { get; set; } - public string SampleSpec { get; set; } - public double RoomTemperature { get; set; } - public double SoakingTime { get; set; } - public string LiquidName { get; set; } - public double LiquidSurfaceTension { get; set; } - public string LiquidManufacturer { get; set; } - public double BubblePointPressure { get; set; } - public string PressureUnit { get; set; } - public double MaxPoreSize { get; set; } + + /// 测试人员姓名 + public string? Tester { get; set; } + + /// 膜类型(平板膜/中空纤维膜) + public string? SampleType { get; set; } + + /// 样品规格(如直径、厚度等) + public string? SampleSpec { get; set; } + + /// 测试时室温(°C) + public double? RoomTemperature { get; set; } + + /// 膜在测试液体中的浸润时间(小时) + public double? SoakingTime { get; set; } + + /// 测试液体名称(如“水”、“乙醇”等) + public string? LiquidName { get; set; } + + /// 测试液体的表面张力(mN/m,25°C) + public double? LiquidSurfaceTension { get; set; } + + /// 测试液体生产厂家(可选) + public string? LiquidManufacturer { get; set; } + + /// 泡点压力数值 + public double? BubblePointPressure { get; set; } + + /// 泡点压力单位(Pa / cmHg / psi) + public string? PressureUnit { get; set; } + + /// 计算得出的最大孔径(μm) + public double? MaxPoreSize { get; set; } } +/// +/// 孔分布测试记录实体,用于数据库存储。 +/// 对应 GB/T 32361-2015 标准中的平均流量法测试记录。 +/// public class PoreDistributionEntity : ITestRecord { + /// 主键ID,自增 public int Id { get; set; } + + /// 工位编号(1~3) public int StationId { get; set; } + + /// 测试日期时间 public DateTime TestDate { get; set; } + + /// 测试人员姓名 public string Tester { get; set; } + + /// 膜类型(平板膜/中空纤维膜) public string SampleType { get; set; } + + /// 样品规格(如直径、厚度等) public string SampleSpec { get; set; } + + /// 测试时室温(°C) public double RoomTemperature { get; set; } + + /// 膜在测试液体中的浸润时间(小时) public double SoakingTime { get; set; } + + /// 测试液体名称(如“水”、“乙醇”等) public string LiquidName { get; set; } + + /// 测试液体的表面张力(mN/m,25°C) public double LiquidSurfaceTension { get; set; } + + /// 测试液体生产厂家(可选) public string LiquidManufacturer { get; set; } + + /// 压力单位(Pa / cmHg / psi) public string PressureUnit { get; set; } + + /// 泡点压力(可选,用于记录) public double BubblePointPressure { get; set; } + + /// 计算得出的平均孔径(μm) public double AveragePoreSize { get; set; } - // 导航属性:数据点 + + /// 导航属性:该测试记录对应的所有压力-流量数据点 public List DataPoints { get; set; } } +/// +/// 孔分布测试的数据点实体,存储每个压力点对应的湿膜和干膜流量。 +/// 与 PoreDistributionEntity 构成一对多的关系。 +/// public class DataPointEntity { + /// 主键ID,自增 public int Id { get; set; } + + /// 关联的孔分布测试记录ID public int PoreDistributionId { get; set; } + + /// 测试压力值,单位由所属记录的 PressureUnit 决定 public double Pressure { get; set; } + + /// 湿膜流量(L/min) public double WetFlow { get; set; } + + /// 干膜流量(L/min) public double DryFlow { get; set; } + /// 导航属性:所属的孔分布测试记录 public PoreDistributionEntity PoreDistribution { get; set; } } \ No newline at end of file diff --git a/Models/BubblePointRecord.cs b/Models/BubblePointRecord.cs index 6551047..eb6ef0f 100644 --- a/Models/BubblePointRecord.cs +++ b/Models/BubblePointRecord.cs @@ -45,7 +45,7 @@ namespace MembranePoreTester.Models } } } - public string PressureUnit { get; set; } // Pa/cmHg/psi + public string PressureUnit { get; set; } = "Pa"; // Pa/cmHg/psi public DateTime TestDate { get; set; } = DateTime.Now; public string Tester { get; set; } diff --git a/Models/PoreDistributionRecord.cs b/Models/PoreDistributionRecord.cs index 8646c80..b29735a 100644 --- a/Models/PoreDistributionRecord.cs +++ b/Models/PoreDistributionRecord.cs @@ -57,7 +57,7 @@ namespace MembranePoreTester.Models /// /// 测试人员姓名。 /// - public string Tester { get; set; } + public string Tester { get; set; } = "系统测试员"; // ---------- 计算结果(由计算逻辑填充) ---------- diff --git a/ViewModels/BubblePointViewModel.cs b/ViewModels/BubblePointViewModel.cs index 80c037c..ab46089 100644 --- a/ViewModels/BubblePointViewModel.cs +++ b/ViewModels/BubblePointViewModel.cs @@ -158,16 +158,26 @@ namespace MembranePoreTester.ViewModels }); } - private async Task ReadPlcAsync() { - if (IsDisposed) return; - - Record.BubblePointPressure = Record.BubbleCurrentPressure; - - OnPropertyChanged(nameof(Record.BubblePointPressure)); + try + { + double rawPressure = await _plcService.ReadPressureAsync(StationId); + rawPressure = Math.Round((double)rawPressure, 2); + double pressure = rawPressure * _plcConfig.PressureFactor; + // 更新当前压力显示 + Record.BubbleCurrentPressure = pressure; + // 同时将泡点压力设为该值(符合涨破测试逻辑) + Record.BubblePointPressure = pressure; + OnPropertyChanged(nameof(Record.BubblePointPressure)); + OnPropertyChanged(nameof(Record.BubbleCurrentPressure)); + } + catch (Exception ex) + { + MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } } @@ -207,12 +217,12 @@ namespace MembranePoreTester.ViewModels { StationId = StationId, TestDate = Record.TestDate, - Tester = Record.Tester, - SampleType = Record.SampleType, - SampleSpec = Record.SampleSpec, + Tester = Record.Tester ?? "系统操作员", + SampleType = Record.SampleType ?? "缺省值", + SampleSpec = Record.SampleSpec ?? "缺省值", RoomTemperature = Record.RoomTemperature, SoakingTime = Record.SoakingTime, - LiquidName = Record.Liquid?.Name, + LiquidName = Record.Liquid?.Name ?? "缺省值", LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0, LiquidManufacturer = Record.LiquidManufacturer, BubblePointPressure = Record.BubblePointPressure, @@ -232,12 +242,12 @@ namespace MembranePoreTester.ViewModels Record.SampleType = entity.SampleType; Record.SampleSpec = entity.SampleSpec; - Record.RoomTemperature = entity.RoomTemperature; - Record.SoakingTime = entity.SoakingTime; + Record.RoomTemperature = entity.RoomTemperature ?? 0; + Record.SoakingTime = entity.SoakingTime ?? 0; Record.Liquid = TestLiquid.Predefined.FirstOrDefault(l => l.Name == entity.LiquidName) - ?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension }; + ?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension ?? 0 }; Record.LiquidManufacturer = entity.LiquidManufacturer; - Record.BubblePointPressure = entity.BubblePointPressure; + Record.BubblePointPressure = entity.BubblePointPressure ?? 0; Record.PressureUnit = entity.PressureUnit; Record.TestDate = entity.TestDate; Record.Tester = entity.Tester; @@ -316,8 +326,23 @@ namespace MembranePoreTester.ViewModels }; if (saveFileDialog.ShowDialog() == true) { - // 转换为Entity后导出 - var entity = new BubblePointEntity { /* 从Record复制 */ }; + // 将 Record 转换为 Entity + var entity = new BubblePointEntity + { + StationId = this.StationId, + TestDate = Record.TestDate, + Tester = Record.Tester, + SampleType = Record.SampleType, + SampleSpec = Record.SampleSpec, + RoomTemperature = Record.RoomTemperature, + SoakingTime = Record.SoakingTime, + LiquidName = Record.Liquid?.Name, + LiquidSurfaceTension = Record.Liquid?.SurfaceTension, + LiquidManufacturer = Record.LiquidManufacturer, + BubblePointPressure = Record.BubblePointPressure, + PressureUnit = Record.PressureUnit, + MaxPoreSize = Record.MaxPoreSize + }; ExportHelper.ExportBubblePoint(entity, saveFileDialog.FileName); MessageBox.Show("导出成功"); } diff --git a/ViewModels/PoreDistributionViewModel.cs b/ViewModels/PoreDistributionViewModel.cs index b16d0b5..9f6f7a9 100644 --- a/ViewModels/PoreDistributionViewModel.cs +++ b/ViewModels/PoreDistributionViewModel.cs @@ -91,59 +91,44 @@ namespace MembranePoreTester.ViewModels private async void AutoCollectTimer_Tick(object sender, EventArgs e) { - if (!IsActive) return; // 不在当前标签页,跳过采集 - await SafeExecuteAsync($"AutoCollect_Station{StationId}", async () => + if (!IsActive) return; + + try { - try + // 1. 读取当前压力 + float rawPressure = await _plcService.ReadPressureAsync(StationId); + double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2); + + // 2. 读取当前模式对应的流量 + double flow = 0; + if (TestMode.Contains("湿膜")) { - // 1. 读取当前压力 - float rawPressure = await _plcService.ReadPressureAsync(StationId); - double pressure = Math.Round(rawPressure, 2); - - double flow = 0; - if (TestMode == "湿膜") - { - float rawFlow = await _plcService.ReadWetFlowAsync(); - flow = rawFlow; - } - else - { - float rawFlow = await _plcService.ReadDryFlowAsync(); - flow = rawFlow; - } - - flow = Math.Round(flow, 2); - // 3. 在 DataPoints 中查找是否存在相同压力的行(允许微小误差) - var existing = Record.DataPoints.FirstOrDefault(p => Math.Abs(p.Pressure - pressure) < 0.001); - if (existing != null) - { - // 更新对应列 - if (TestMode == "湿膜") - existing.WetFlow = flow; - else - existing.DryFlow = flow; - } - else - { - // 新增一行 - var newPoint = new Models.DataPoint { Pressure = pressure }; - if (TestMode == "湿膜") - newPoint.WetFlow = flow; - else - newPoint.DryFlow = flow; - Record.DataPoints.Add(newPoint); - } - - // 4. 刷新曲线 - UpdatePlot(); + float rawFlow = await _plcService.ReadWetFlowAsync(); + flow = Math.Round(rawFlow, 2); } - catch (Exception ex) + else { - System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}"); + float rawFlow = await _plcService.ReadDryFlowAsync(); + flow = Math.Round(rawFlow, 2); } - }); + + // 3. 直接新增一行(不查找相同压力) + var newPoint = new Models.DataPoint + { + Pressure = pressure, + WetFlow = TestMode.Contains("湿膜") ? flow : 0, + DryFlow = TestMode.Contains("干膜") ? flow : 0 + }; + Record.DataPoints.Add(newPoint); + + // 4. 刷新曲线 + UpdatePlot(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}"); + } } - /// /// 清空所有数据点,重置表格和曲线 /// @@ -285,7 +270,10 @@ namespace MembranePoreTester.ViewModels Record.DataPoints.CollectionChanged += (s, e) => UpdatePlot(); + // 加到构造函数最后即可 + ClearAllCommand = new RelayCommand(ClearAllData); + RemoveDataPointCommand = new RelayCommand(RemoveSelectedDataPoint, () => SelectedDataPoint != null); // 延迟2秒后读取,确保连接稳定 Task.Delay(2000).ContinueWith(async _ => @@ -295,6 +283,11 @@ namespace MembranePoreTester.ViewModels } + private void ClearAllData() + { + ClearData(); + } + private async Task ReadPressureModeAsync() { await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () => @@ -338,7 +331,7 @@ namespace MembranePoreTester.ViewModels // 始终读取压力 float rawPressure = await _plcService.ReadPressureAsync(StationId); - double pressure = rawPressure; + double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2); if (SelectedDataPoint != null) { @@ -495,14 +488,14 @@ namespace MembranePoreTester.ViewModels { StationId = this.StationId, TestDate = Record.TestDate, - Tester = Record.Tester, + Tester = Record.Tester ?? "系统操作员", SampleType = Record.SampleType, - SampleSpec = Record.SampleSpec, + SampleSpec = Record.SampleSpec ?? "缺省值", RoomTemperature = Record.RoomTemperature, SoakingTime = Record.SoakingTime, LiquidName = Record.Liquid?.Name, LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0, - LiquidManufacturer = Record.LiquidManufacturer, + LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值", PressureUnit = Record.PressureUnit, BubblePointPressure = Record.BubblePointPressure, AveragePoreSize = AveragePoreSize, // 使用计算后的属性 @@ -685,10 +678,22 @@ namespace MembranePoreTester.ViewModels } + public ICommand ClearAllCommand { get; } + // 修改命令初始化(构造函数中) + // 实现删除方法 + private void RemoveSelectedDataPoint() + { + if (SelectedDataPoint != null) + { + Record.DataPoints.Remove(SelectedDataPoint); + SelectedDataPoint = null; // 清除选中状态 + UpdatePlot(); // 刷新曲线 + } + } } } \ No newline at end of file diff --git a/Views/BubblePointView.xaml b/Views/BubblePointView.xaml index b48e0c1..fdc4376 100644 --- a/Views/BubblePointView.xaml +++ b/Views/BubblePointView.xaml @@ -70,8 +70,8 @@