diff --git a/App.xaml.cs b/App.xaml.cs index 68b2b04..c669d68 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -3,6 +3,7 @@ using Microsoft.Data.Sqlite; using OfficeOpenXml; using System; using System.IO; +using System.Text.Json; using System.Windows; using TabletTester2025.Data; using TabletTester2025.Models; @@ -16,6 +17,18 @@ namespace TabletTester2025 public static IPlcService PlcService { get; private set; } public static PlcConfiguration PlcConfig { get; private set; } public static PharmaParameters CurrentPharmaParams { get; set; } = new PharmaParameters(); + private static readonly JsonSerializerOptions PharmaJsonOptions = new() { WriteIndented = true }; + + public static string PharmaParametersFilePath + { + get + { + string directory = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "CSI-Z420-Tablet-Multi-Function-Tester"); + return Path.Combine(directory, "pharma-standard.json"); + } + } protected override void OnStartup(StartupEventArgs e) { @@ -112,6 +125,7 @@ CREATE TABLE IF NOT EXISTS DissolutionSamplePoints ( // 绑定药典参数 configuration.GetSection("PharmaStandard").Bind(CurrentPharmaParams); + LoadLocalPharmaParameters(); // PLC配置 PlcConfig = configuration.GetSection("Plc").Get(); @@ -132,6 +146,34 @@ CREATE TABLE IF NOT EXISTS DissolutionSamplePoints ( mainWindow.Show(); } + public static void SaveCurrentPharmaParameters() + { + string path = PharmaParametersFilePath; + string? directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + + File.WriteAllText(path, JsonSerializer.Serialize(CurrentPharmaParams, PharmaJsonOptions)); + } + + private static void LoadLocalPharmaParameters() + { + string path = PharmaParametersFilePath; + if (!File.Exists(path)) + return; + + try + { + var local = JsonSerializer.Deserialize(File.ReadAllText(path)); + if (local != null) + CurrentPharmaParams = local; + } + catch + { + // Keep appsettings defaults if the local parameter file is damaged. + } + } + private void EnsureColumnsExist(string connectionString) { var requiredColumns = new[] diff --git a/Models/PharmaParameters.cs b/Models/PharmaParameters.cs index ebe2420..30758c2 100644 --- a/Models/PharmaParameters.cs +++ b/Models/PharmaParameters.cs @@ -1,7 +1,8 @@ -namespace TabletTester2025.Models +namespace TabletTester2025.Models { public class PharmaParameters { + public string StandardVersion { get; set; } = "中国药典2025"; public double HardnessMin_N { get; set; } = 40; public double HardnessMax_N { get; set; } = 60; public int HardnessTestCount { get; set; } = 6; @@ -10,6 +11,13 @@ public double FriabilityMaxLossPercent { get; set; } = 1.0; public string DisintegrationDosageForm { get; set; } = "普通片"; public int DisintegrationMaxSeconds { get; set; } = 900; + public double DisintegrationSpeedRpm { get; set; } = 31; + public double DisintegrationTemperatureC { get; set; } = 37; + public double DissolutionTemperatureC { get; set; } = 37; + public int Dissolution1TimeMin { get; set; } = 30; + public int Dissolution2TimeMin { get; set; } = 30; + public double Dissolution1SampleIntervalMin { get; set; } = 5; + public double Dissolution2SampleIntervalMin { get; set; } = 5; public int[] DissolutionSampleTimes { get; set; } = new[] { 5, 10, 15, 30, 45, 60 }; public double DissolutionMinPercentAt30min { get; set; } = 80.0; } diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 0942856..a79dc1c 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -58,8 +58,13 @@ namespace TabletTester2025.ViewModels - // 在构造函数中 - OpenSettingsCommand = new AsyncRelayCommand(() => { new SettingsWindow().ShowDialog(); return Task.CompletedTask; }); + OpenSettingsCommand = new AsyncRelayCommand(() => + { + var window = new SettingsWindow(); + if (window.ShowDialog() == true) + Tester.ApplyPharmaDefaults(); + return Task.CompletedTask; + }); OpenHistoryCommand = new AsyncRelayCommand(() => { new HistoryWindow().ShowDialog(); return Task.CompletedTask; }); // 跳转到 PlcDataPage 页面 diff --git a/ViewModels/StationViewModel.cs b/ViewModels/StationViewModel.cs index 5abf3fc..8fa4303 100644 --- a/ViewModels/StationViewModel.cs +++ b/ViewModels/StationViewModel.cs @@ -143,6 +143,7 @@ namespace TabletTester2025.ViewModels [ObservableProperty] private int _dissolution2TimeMin = 30; [ObservableProperty] private double _dissolution1SampleIntervalMin = 5; [ObservableProperty] private double _dissolution2SampleIntervalMin = 5; + [ObservableProperty] private double _dissolutionMinPercentAt30Min = 80; [ObservableProperty] private double _dissolutionElapsedTime; [ObservableProperty] private double _dissolutionCountdown; [ObservableProperty] private double _dissolutionRSquared; @@ -310,26 +311,54 @@ namespace TabletTester2025.ViewModels ResetDisintegrationCommand = new AsyncRelayCommand(ResetDisintegrationAsync); PrintDisintegrationCommand = new AsyncRelayCommand(async () => await PrintReport("崩解")); - _ = LoadDisintegrationTimeAsync(); - _ = LoadDisintegrationSpeedAsync(); - _ = LoadDissolutionTimesAsync(); _ = LoadFriabilityWeightsAsync(); } + public void ApplyPharmaDefaults() + { + var p = App.CurrentPharmaParams; + _isLoadingDisintegrationSpeed = true; + _isLoadingDisintegrationTime = true; + _isLoadingDissolution1Time = true; + _isLoadingDissolution2Time = true; + _isLoadingDissolution1SampleInterval = true; + _isLoadingDissolution2SampleInterval = true; + try + { + HardnessInternalMin = p.HardnessMin_N; + HardnessInternalMax = p.HardnessMax_N; + HardnessTestCount = Math.Max(1, p.HardnessTestCount); + FriabilityTargetRpm = p.FriabilityTargetRpm > 0 ? p.FriabilityTargetRpm : 25; + FriabilityTargetRounds = p.FriabilityTargetRounds > 0 ? p.FriabilityTargetRounds : 100; + FriabilityMaxLossPercent = p.FriabilityMaxLossPercent; + FriabilityRemainingRounds = FriabilityTargetRounds; + DisintegrationDosageForm = string.IsNullOrWhiteSpace(p.DisintegrationDosageForm) ? "普通片" : p.DisintegrationDosageForm; + DisintegrationSpeedRpm = p.DisintegrationSpeedRpm > 0 ? p.DisintegrationSpeedRpm : 31; + DisintegrationTemp = p.DisintegrationTemperatureC > 0 ? p.DisintegrationTemperatureC : 37; + Dissolution1TimeMin = p.Dissolution1TimeMin > 0 ? p.Dissolution1TimeMin : 30; + Dissolution2TimeMin = p.Dissolution2TimeMin > 0 ? p.Dissolution2TimeMin : 30; + Dissolution1SampleIntervalMin = p.Dissolution1SampleIntervalMin > 0 ? p.Dissolution1SampleIntervalMin : 5; + Dissolution2SampleIntervalMin = p.Dissolution2SampleIntervalMin > 0 ? p.Dissolution2SampleIntervalMin : 5; + DissolutionMinPercentAt30Min = p.DissolutionMinPercentAt30min; + DissolutionSampleInterval = ToCompatibleSampleInterval(Dissolution1SampleIntervalMin); + int seconds = p.DisintegrationMaxSeconds > 0 ? p.DisintegrationMaxSeconds : ResolveDisintegrationLimitSeconds(); + if (seconds > 0) + DisintegrationTimeMin = seconds / 60.0; + } + finally + { + _isLoadingDisintegrationSpeed = false; + _isLoadingDisintegrationTime = false; + _isLoadingDissolution1Time = false; + _isLoadingDissolution2Time = false; + _isLoadingDissolution1SampleInterval = false; + _isLoadingDissolution2SampleInterval = false; + } + } + private void LoadPharmaDefaults() { - var p = App.CurrentPharmaParams; - HardnessInternalMin = p.HardnessMin_N; - HardnessInternalMax = p.HardnessMax_N; - HardnessTestCount = Math.Max(1, p.HardnessTestCount); - FriabilityTargetRpm = p.FriabilityTargetRpm > 0 ? p.FriabilityTargetRpm : 25; - FriabilityTargetRounds = p.FriabilityTargetRounds > 0 ? p.FriabilityTargetRounds : 100; - FriabilityMaxLossPercent = p.FriabilityMaxLossPercent; - FriabilityRemainingRounds = FriabilityTargetRounds; - DisintegrationDosageForm = string.IsNullOrWhiteSpace(p.DisintegrationDosageForm) ? "普通片" : p.DisintegrationDosageForm; - int seconds = ResolveDisintegrationLimitSeconds(); - if (seconds > 0) - DisintegrationTimeMin = seconds / 60.0; + ApplyPharmaDefaults(); } private async Task PrintReport(string testName) @@ -1008,6 +1037,12 @@ namespace TabletTester2025.ViewModels private int ResolveDisintegrationLimitSeconds(string? dosageForm = null) { string form = string.IsNullOrWhiteSpace(dosageForm) ? DisintegrationDosageForm : dosageForm; + if (string.Equals(form, App.CurrentPharmaParams.DisintegrationDosageForm, StringComparison.OrdinalIgnoreCase) + && App.CurrentPharmaParams.DisintegrationMaxSeconds > 0) + { + return App.CurrentPharmaParams.DisintegrationMaxSeconds; + } + return form switch { "薄膜衣片" => 30 * 60, @@ -1528,7 +1563,7 @@ namespace TabletTester2025.ViewModels // 崩解 DisintegrationTimeSec = DisintegrationSeconds, RemainingTubesAtEnd = RemainingTubes, - DisintegrationTargetFreq = 0, + DisintegrationTargetFreq = DisintegrationSpeedRpm, DisintegrationTemp = DisintegrationTemp, DisintegrationDosageForm = DisintegrationDosageForm, DisintegrationLimitSeconds = ResolveDisintegrationLimitSeconds(), diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml index 7918db7..fb0cddc 100644 --- a/Views/MainWindow.xaml +++ b/Views/MainWindow.xaml @@ -267,7 +267,7 @@ - + @@ -341,29 +341,20 @@ - + - + - + - - - - - - - - @@ -414,25 +405,27 @@ - + - + - + - + - + + + + + @@ -538,28 +531,23 @@ - + - - - - - - + - + - + + + + + diff --git a/Views/SettingsWindow.xaml b/Views/SettingsWindow.xaml index 1420532..245f784 100644 --- a/Views/SettingsWindow.xaml +++ b/Views/SettingsWindow.xaml @@ -1,145 +1,214 @@ - + Title="参数设置 - 中国药典2025" + Width="1024" + MinHeight="768" + WindowState="Maximized" + WindowStartupLocation="CenterScreen" + ResizeMode="CanResize" + Background="#EDF1F5"> + + + + + + + + + + + + - - - - + - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - -