This commit is contained in:
GukSang.Jin
2026-05-18 15:20:13 +08:00
8 changed files with 372 additions and 183 deletions

View File

@@ -3,6 +3,7 @@ using Microsoft.Data.Sqlite;
using OfficeOpenXml; using OfficeOpenXml;
using System; using System;
using System.IO; using System.IO;
using System.Text.Json;
using System.Windows; using System.Windows;
using TabletTester2025.Data; using TabletTester2025.Data;
using TabletTester2025.Models; using TabletTester2025.Models;
@@ -16,6 +17,18 @@ namespace TabletTester2025
public static IPlcService PlcService { get; private set; } public static IPlcService PlcService { get; private set; }
public static PlcConfiguration PlcConfig { get; private set; } public static PlcConfiguration PlcConfig { get; private set; }
public static PharmaParameters CurrentPharmaParams { get; set; } = new PharmaParameters(); 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) protected override void OnStartup(StartupEventArgs e)
{ {
@@ -112,6 +125,7 @@ CREATE TABLE IF NOT EXISTS DissolutionSamplePoints (
// 绑定药典参数 // 绑定药典参数
configuration.GetSection("PharmaStandard").Bind(CurrentPharmaParams); configuration.GetSection("PharmaStandard").Bind(CurrentPharmaParams);
LoadLocalPharmaParameters();
// PLC配置 // PLC配置
PlcConfig = configuration.GetSection("Plc").Get<PlcConfiguration>(); PlcConfig = configuration.GetSection("Plc").Get<PlcConfiguration>();
@@ -132,6 +146,34 @@ CREATE TABLE IF NOT EXISTS DissolutionSamplePoints (
mainWindow.Show(); 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<PharmaParameters>(File.ReadAllText(path));
if (local != null)
CurrentPharmaParams = local;
}
catch
{
// Keep appsettings defaults if the local parameter file is damaged.
}
}
private void EnsureColumnsExist(string connectionString) private void EnsureColumnsExist(string connectionString)
{ {
var requiredColumns = new[] var requiredColumns = new[]

View File

@@ -1,7 +1,8 @@
namespace TabletTester2025.Models namespace TabletTester2025.Models
{ {
public class PharmaParameters public class PharmaParameters
{ {
public string StandardVersion { get; set; } = "中国药典2025";
public double HardnessMin_N { get; set; } = 40; public double HardnessMin_N { get; set; } = 40;
public double HardnessMax_N { get; set; } = 60; public double HardnessMax_N { get; set; } = 60;
public int HardnessTestCount { get; set; } = 6; public int HardnessTestCount { get; set; } = 6;
@@ -10,6 +11,13 @@
public double FriabilityMaxLossPercent { get; set; } = 1.0; public double FriabilityMaxLossPercent { get; set; } = 1.0;
public string DisintegrationDosageForm { get; set; } = "普通片"; public string DisintegrationDosageForm { get; set; } = "普通片";
public int DisintegrationMaxSeconds { get; set; } = 900; 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 int[] DissolutionSampleTimes { get; set; } = new[] { 5, 10, 15, 30, 45, 60 };
public double DissolutionMinPercentAt30min { get; set; } = 80.0; public double DissolutionMinPercentAt30min { get; set; } = 80.0;
} }

View File

@@ -58,8 +58,13 @@ namespace TabletTester2025.ViewModels
// 在构造函数中 OpenSettingsCommand = new AsyncRelayCommand(() =>
OpenSettingsCommand = new AsyncRelayCommand(() => { new SettingsWindow().ShowDialog(); return Task.CompletedTask; }); {
var window = new SettingsWindow();
if (window.ShowDialog() == true)
Tester.ApplyPharmaDefaults();
return Task.CompletedTask;
});
OpenHistoryCommand = new AsyncRelayCommand(() => { new HistoryWindow().ShowDialog(); return Task.CompletedTask; }); OpenHistoryCommand = new AsyncRelayCommand(() => { new HistoryWindow().ShowDialog(); return Task.CompletedTask; });
// 跳转到 PlcDataPage 页面 // 跳转到 PlcDataPage 页面

View File

@@ -143,6 +143,7 @@ namespace TabletTester2025.ViewModels
[ObservableProperty] private int _dissolution2TimeMin = 30; [ObservableProperty] private int _dissolution2TimeMin = 30;
[ObservableProperty] private double _dissolution1SampleIntervalMin = 5; [ObservableProperty] private double _dissolution1SampleIntervalMin = 5;
[ObservableProperty] private double _dissolution2SampleIntervalMin = 5; [ObservableProperty] private double _dissolution2SampleIntervalMin = 5;
[ObservableProperty] private double _dissolutionMinPercentAt30Min = 80;
[ObservableProperty] private double _dissolutionElapsedTime; [ObservableProperty] private double _dissolutionElapsedTime;
[ObservableProperty] private double _dissolutionCountdown; [ObservableProperty] private double _dissolutionCountdown;
[ObservableProperty] private double _dissolutionRSquared; [ObservableProperty] private double _dissolutionRSquared;
@@ -310,26 +311,54 @@ namespace TabletTester2025.ViewModels
ResetDisintegrationCommand = new AsyncRelayCommand(ResetDisintegrationAsync); ResetDisintegrationCommand = new AsyncRelayCommand(ResetDisintegrationAsync);
PrintDisintegrationCommand = new AsyncRelayCommand(async () => await PrintReport("崩解")); PrintDisintegrationCommand = new AsyncRelayCommand(async () => await PrintReport("崩解"));
_ = LoadDisintegrationTimeAsync();
_ = LoadDisintegrationSpeedAsync();
_ = LoadDissolutionTimesAsync();
_ = LoadFriabilityWeightsAsync(); _ = 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() private void LoadPharmaDefaults()
{ {
var p = App.CurrentPharmaParams; ApplyPharmaDefaults();
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;
} }
private async Task PrintReport(string testName) private async Task PrintReport(string testName)
@@ -1008,6 +1037,12 @@ namespace TabletTester2025.ViewModels
private int ResolveDisintegrationLimitSeconds(string? dosageForm = null) private int ResolveDisintegrationLimitSeconds(string? dosageForm = null)
{ {
string form = string.IsNullOrWhiteSpace(dosageForm) ? DisintegrationDosageForm : dosageForm; 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 return form switch
{ {
"薄膜衣片" => 30 * 60, "薄膜衣片" => 30 * 60,
@@ -1528,7 +1563,7 @@ namespace TabletTester2025.ViewModels
// 崩解 // 崩解
DisintegrationTimeSec = DisintegrationSeconds, DisintegrationTimeSec = DisintegrationSeconds,
RemainingTubesAtEnd = RemainingTubes, RemainingTubesAtEnd = RemainingTubes,
DisintegrationTargetFreq = 0, DisintegrationTargetFreq = DisintegrationSpeedRpm,
DisintegrationTemp = DisintegrationTemp, DisintegrationTemp = DisintegrationTemp,
DisintegrationDosageForm = DisintegrationDosageForm, DisintegrationDosageForm = DisintegrationDosageForm,
DisintegrationLimitSeconds = ResolveDisintegrationLimitSeconds(), DisintegrationLimitSeconds = ResolveDisintegrationLimitSeconds(),

View File

@@ -267,7 +267,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GroupBox Header="预设参数设置" Grid.Row="0" Margin="0,5"> <GroupBox Header="当前参数" Grid.Row="0" Margin="0,5">
<UniformGrid Columns="3" Margin="10"> <UniformGrid Columns="3" Margin="10">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,18,0"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,18,0">
<TextBlock Text="内控下限(N)" Width="130" VerticalAlignment="Center"/> <TextBlock Text="内控下限(N)" Width="130" VerticalAlignment="Center"/>
@@ -341,29 +341,20 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GroupBox Header="预设参数设置" Grid.Row="0"> <GroupBox Header="当前参数" Grid.Row="0">
<WrapPanel> <WrapPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="转速设置(r/min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="转速设置(r/min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding FriabilityTargetRpm, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding FriabilityTargetRpm, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验转数(转)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="试验转数(转)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding FriabilityTargetRounds, UpdateSourceTrigger=PropertyChanged}" <TextBlock Text="{Binding FriabilityTargetRounds}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
helpers:NumericInput.AllowDecimal="False"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="失重率限度(%)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="失重率限度(%)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding FriabilityMaxLossPercent, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/> <TextBlock Text="{Binding FriabilityMaxLossPercent, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="初始重量(g)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding WeightBefore, UpdateSourceTrigger=PropertyChanged, StringFormat=F3}"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="最终重量(g)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding WeightAfter, UpdateSourceTrigger=PropertyChanged, StringFormat=F3}"/>
</StackPanel>
</WrapPanel> </WrapPanel>
</GroupBox> </GroupBox>
@@ -414,25 +405,27 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GroupBox Header="预设参数设置" Grid.Row="0"> <GroupBox Header="当前参数" Grid.Row="0">
<WrapPanel> <WrapPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1时间(min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="溶出1时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding Dissolution1TimeMin, UpdateSourceTrigger=PropertyChanged}" <TextBlock Text="{Binding Dissolution1TimeMin}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
helpers:NumericInput.AllowDecimal="False"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2时间(min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="溶出2时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding Dissolution2TimeMin, UpdateSourceTrigger=PropertyChanged}" <TextBlock Text="{Binding Dissolution2TimeMin}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
helpers:NumericInput.AllowDecimal="False"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1间隔取样时间(min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="溶出1间隔取样时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding Dissolution1SampleIntervalMin, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding Dissolution1SampleIntervalMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2间隔取样时间(min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="溶出2间隔取样时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding Dissolution2SampleIntervalMin, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding Dissolution2SampleIntervalMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="30min最低溶出度Q(%)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DissolutionMinPercentAt30Min, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</WrapPanel> </WrapPanel>
</GroupBox> </GroupBox>
@@ -538,28 +531,23 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GroupBox Header="预设参数设置" Grid.Row="0"> <GroupBox Header="当前参数" Grid.Row="0">
<WrapPanel> <WrapPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="剂型规则:" Style="{StaticResource ParamLabel}"/> <TextBlock Text="剂型规则:" Style="{StaticResource ParamLabel}"/>
<ComboBox SelectedValue="{Binding DisintegrationDosageForm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" <TextBlock Text="{Binding DisintegrationDosageForm}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
SelectedValuePath="Content"
Width="150"
Height="40"
FontSize="15">
<ComboBoxItem Content="普通片"/>
<ComboBoxItem Content="薄膜衣片"/>
<ComboBoxItem Content="糖衣片"/>
<ComboBoxItem Content="胶囊"/>
</ComboBox>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="崩解速度(r/min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="崩解速度(r/min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding DisintegrationSpeedRpm, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding DisintegrationSpeedRpm, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Style="{StaticResource ParamRow}"> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="崩解时间(min)" Style="{StaticResource ParamLabel}"/> <TextBlock Text="崩解时间(min)" Style="{StaticResource ParamLabel}"/>
<TextBox Text="{Binding DisintegrationTimeMin, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Text="{Binding DisintegrationTimeMin, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="介质温度(℃)" Style="{StaticResource ParamLabel}"/>
<TextBlock Text="{Binding DisintegrationTemp, StringFormat=F1}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</WrapPanel> </WrapPanel>
</GroupBox> </GroupBox>

View File

@@ -1,145 +1,214 @@
<Window x:Class="TabletTester2025.SettingsWindow" <Window x:Class="TabletTester2025.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:helpers="clr-namespace:TabletTester2025.Helpers" xmlns:helpers="clr-namespace:TabletTester2025.Helpers"
Title="参数设置 - 中国药典2025" Title="参数设置 - 中国药典2025"
Width="1024" MinHeight="768" WindowState="Maximized" Width="1024"
WindowStartupLocation="CenterScreen" MinHeight="768"
WindowState="Maximized"
ResizeMode="CanResize" Background="#F5F5F5"> WindowStartupLocation="CenterScreen"
ResizeMode="CanResize"
Background="#EDF1F5">
<Window.Resources> <Window.Resources>
<SolidColorBrush x:Key="PanelBorderBrush" Color="#D8E1EC"/>
<SolidColorBrush x:Key="PrimaryBrush" Color="#1565A9"/>
<Style TargetType="GroupBox">
<Setter Property="Margin" Value="0,0,0,14"/>
<Setter Property="Padding" Value="14"/>
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style>
<Style TargetType="TextBlock" x:Key="ParamLabel">
<Setter Property="Width" Value="220"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="#526273"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
<Style TargetType="TextBox"> <Style TargetType="TextBox">
<Setter Property="Width" Value="180"/>
<Setter Property="Height" Value="40"/> <Setter Property="Height" Value="40"/>
<Setter Property="Padding" Value="8,2"/> <Setter Property="Padding" Value="8,2"/>
<Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="15"/> <Setter Property="FontSize" Value="15"/>
<Setter Property="helpers:NumericInput.IsEnabled" Value="True"/> <Setter Property="helpers:NumericInput.IsEnabled" Value="True"/>
</Style> </Style>
<Style TargetType="StackPanel" x:Key="ParamRow">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="Margin" Value="0,6,32,6"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="TextBlock" x:Key="StandardNote">
<Setter Property="Foreground" Value="#6A7888"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,10,0,0"/>
</Style>
</Window.Resources> </Window.Resources>
<Grid Margin="20"> <Grid Margin="20">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="药典参数设置" FontSize="20" FontWeight="Bold" Margin="0,0,0,20" HorizontalAlignment="Center"/>
<!-- 硬度 --> <Border Background="#0F3D68" CornerRadius="6" Padding="14,10" Margin="0,0,0,14">
<GroupBox Header="硬度测试" Grid.Row="1" Margin="0,5"> <TextBlock Text="药典与内控参数设置"
<Grid Margin="10"> Foreground="White"
<Grid.ColumnDefinitions> FontSize="22"
<ColumnDefinition Width="150"/> FontWeight="Bold"/>
<ColumnDefinition Width="*"/> </Border>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="内控下限(N):" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/>
<TextBox x:Name="HardnessMinBox" Grid.Row="0" Grid.Column="1" Margin="5" Text="40"/>
<TextBlock Text="内控上限(N):" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"/>
<TextBox x:Name="HardnessMaxBox" Grid.Row="1" Grid.Column="1" Margin="5" Text="60"/>
<TextBlock Text="测试次数:" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"/>
<TextBox x:Name="HardnessCountBox"
Grid.Row="2"
Grid.Column="1"
Margin="5"
Text="6"
helpers:NumericInput.AllowDecimal="False"/>
</Grid>
</GroupBox>
<!-- 脆碎度 --> <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<GroupBox Header="脆碎度" Grid.Row="2" Margin="0,5"> <StackPanel>
<Grid Margin="10"> <GroupBox Header="硬度内控">
<Grid.ColumnDefinitions> <StackPanel>
<ColumnDefinition Width="150"/> <WrapPanel>
<ColumnDefinition Width="*"/> <StackPanel Style="{StaticResource ParamRow}">
</Grid.ColumnDefinitions> <TextBlock Text="内控下限(N):" Style="{StaticResource ParamLabel}"/>
<Grid.RowDefinitions> <TextBox x:Name="HardnessMinBox"/>
<RowDefinition Height="Auto"/> </StackPanel>
<RowDefinition Height="Auto"/> <StackPanel Style="{StaticResource ParamRow}">
<RowDefinition Height="Auto"/> <TextBlock Text="内控上限(N):" Style="{StaticResource ParamLabel}"/>
</Grid.RowDefinitions> <TextBox x:Name="HardnessMaxBox"/>
<TextBlock Text="转速(r/min):" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/> </StackPanel>
<TextBox x:Name="FriabilityRpmBox" Grid.Row="0" Grid.Column="1" Margin="5" Text="25.0"/> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="试验转数:" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"/> <TextBlock Text="测试次数:" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="FriabilityRoundsBox" <TextBox x:Name="HardnessCountBox" helpers:NumericInput.AllowDecimal="False"/>
Grid.Row="1" </StackPanel>
Grid.Column="1" </WrapPanel>
Margin="5" <TextBlock Text="硬度没有统一药典数值限度,此处作为企业内控范围和本机测试次数。"
Text="100" Style="{StaticResource StandardNote}"/>
helpers:NumericInput.AllowDecimal="False"/> </StackPanel>
<TextBlock Text="最大失重率(%):" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"/> </GroupBox>
<TextBox x:Name="FriabilityMaxLossBox" Grid.Row="2" Grid.Column="1" Margin="5" Text="1.0"/>
</Grid>
</GroupBox>
<!-- 崩解时限 --> <GroupBox Header="脆碎度 - 通则0923">
<GroupBox Header="崩解时限" Grid.Row="3" Margin="0,5"> <StackPanel>
<Grid Margin="10"> <WrapPanel>
<Grid.ColumnDefinitions> <StackPanel Style="{StaticResource ParamRow}">
<ColumnDefinition Width="150"/> <TextBlock Text="转速(r/min):" Style="{StaticResource ParamLabel}"/>
<ColumnDefinition Width="*"/> <TextBox x:Name="FriabilityRpmBox"/>
</Grid.ColumnDefinitions> </StackPanel>
<Grid.RowDefinitions> <StackPanel Style="{StaticResource ParamRow}">
<RowDefinition Height="Auto"/> <TextBlock Text="试验转数:" Style="{StaticResource ParamLabel}"/>
<RowDefinition Height="Auto"/> <TextBox x:Name="FriabilityRoundsBox" helpers:NumericInput.AllowDecimal="False"/>
</Grid.RowDefinitions> </StackPanel>
<TextBlock Text="剂型规则:" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/> <StackPanel Style="{StaticResource ParamRow}">
<ComboBox x:Name="DisintegrationDosageFormBox" <TextBlock Text="最大失重率(%):" Style="{StaticResource ParamLabel}"/>
Grid.Row="0" <TextBox x:Name="FriabilityMaxLossBox"/>
Grid.Column="1" </StackPanel>
Margin="5" </WrapPanel>
Height="40" <TextBlock Text="通则默认转速25±1 r/min转动100次减失重量不得过1.0%,且不得检出断裂、龟裂或粉碎的片。"
FontSize="15" Style="{StaticResource StandardNote}"/>
SelectionChanged="DisintegrationDosageFormBox_SelectionChanged"> </StackPanel>
<ComboBoxItem Content="普通片" Tag="900"/> </GroupBox>
<ComboBoxItem Content="薄膜衣片" Tag="1800"/>
<ComboBoxItem Content="糖衣片" Tag="3600"/>
<ComboBoxItem Content="胶囊" Tag="1800"/>
</ComboBox>
<TextBlock Text="最长崩解时间(秒):" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"/>
<TextBox x:Name="DisintegrationMaxSecBox"
Grid.Row="1"
Grid.Column="1"
Margin="5"
Text="900"
helpers:NumericInput.AllowDecimal="False"/>
</Grid>
</GroupBox>
<!-- 溶出度 --> <GroupBox Header="崩解时限 - 通则0921">
<GroupBox Header="溶出度" Grid.Row="4" Margin="0,5"> <StackPanel>
<Grid Margin="10"> <WrapPanel>
<Grid.RowDefinitions> <StackPanel Style="{StaticResource ParamRow}">
<RowDefinition Height="Auto"/> <TextBlock Text="剂型规则:" Style="{StaticResource ParamLabel}"/>
<RowDefinition Height="Auto"/> <ComboBox x:Name="DisintegrationDosageFormBox"
</Grid.RowDefinitions> Width="180"
<Grid.ColumnDefinitions> Height="40"
<ColumnDefinition Width="150"/> FontSize="15"
<ColumnDefinition Width="*"/> SelectionChanged="DisintegrationDosageFormBox_SelectionChanged">
</Grid.ColumnDefinitions> <ComboBoxItem Content="普通片" Tag="900"/>
<TextBlock Text="30分钟最低溶出度(%):" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/> <ComboBoxItem Content="薄膜衣片" Tag="1800"/>
<TextBox x:Name="DissolutionMinPercentBox" Grid.Row="0" Grid.Column="1" Margin="5" Text="80.0"/> <ComboBoxItem Content="糖衣片" Tag="3600"/>
<TextBlock Text="取样时间点(分钟,逗号分隔):" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="0,5,0,0"/> <ComboBoxItem Content="胶囊" Tag="1800"/>
<TextBox x:Name="SampleTimesBox" </ComboBox>
Grid.Row="1" </StackPanel>
Grid.Column="1" <StackPanel Style="{StaticResource ParamRow}">
Margin="5" <TextBlock Text="最长崩解时间(秒):" Style="{StaticResource ParamLabel}"/>
Text="5,10,15,30,45,60" <TextBox x:Name="DisintegrationMaxSecBox" helpers:NumericInput.AllowDecimal="False"/>
helpers:NumericInput.IsEnabled="False"/> </StackPanel>
</Grid> <StackPanel Style="{StaticResource ParamRow}">
</GroupBox> <TextBlock Text="升降频率(次/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationSpeedBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="介质温度(℃):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationTempBox"/>
</StackPanel>
</WrapPanel>
<TextBlock Text="通则默认升降频率30-32次/min介质温度37±1℃。不同剂型按药典或品种正文规定时限执行。"
Style="{StaticResource StandardNote}"/>
</StackPanel>
</GroupBox>
<!-- 按钮 --> <GroupBox Header="溶出度 - 通则0931">
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,20"> <StackPanel>
<Button x:Name="SaveButton" Content="保存" Width="80" Height="30" Margin="5" Click="SaveButton_Click" Background="#2196F3" Foreground="White" BorderThickness="0"/> <WrapPanel>
<Button x:Name="CancelButton" Content="取消" Width="80" Height="30" Margin="5" Click="CancelButton_Click" Background="#9E9E9E" Foreground="White" BorderThickness="0"/> <StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="30分钟最低溶出度Q(%):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DissolutionMinPercentBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出介质温度(℃):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DissolutionTempBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1运行时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1TimeBox" helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2运行时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2TimeBox" helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1取样间隔(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1IntervalBox"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2取样间隔(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2IntervalBox"/>
</StackPanel>
</WrapPanel>
<StackPanel Style="{StaticResource ParamRow}" Margin="0,8,0,0">
<TextBlock Text="取样时间点(分钟,逗号分隔):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="SampleTimesBox"
Width="430"
helpers:NumericInput.IsEnabled="False"/>
</StackPanel>
<TextBlock Text="通则默认普通制剂通常取6片溶出介质温度37±0.5℃Q值、介质、转速和取样点应按具体品种正文或企业批准标准录入。"
Style="{StaticResource StandardNote}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
<Button x:Name="SaveButton"
Content="保存"
Width="110"
Height="40"
Margin="6"
Click="SaveButton_Click"
Background="{StaticResource PrimaryBrush}"
Foreground="White"
BorderThickness="0"
FontWeight="SemiBold"/>
<Button x:Name="CancelButton"
Content="取消"
Width="110"
Height="40"
Margin="6"
Click="CancelButton_Click"
Background="#687789"
Foreground="White"
BorderThickness="0"
FontWeight="SemiBold"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </Window>

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -25,7 +25,14 @@ namespace TabletTester2025
FriabilityMaxLossBox.Text = p.FriabilityMaxLossPercent.ToString(); FriabilityMaxLossBox.Text = p.FriabilityMaxLossPercent.ToString();
SelectDisintegrationDosageForm(p.DisintegrationDosageForm); SelectDisintegrationDosageForm(p.DisintegrationDosageForm);
DisintegrationMaxSecBox.Text = p.DisintegrationMaxSeconds.ToString(); DisintegrationMaxSecBox.Text = p.DisintegrationMaxSeconds.ToString();
DisintegrationSpeedBox.Text = p.DisintegrationSpeedRpm.ToString();
DisintegrationTempBox.Text = p.DisintegrationTemperatureC.ToString();
DissolutionMinPercentBox.Text = p.DissolutionMinPercentAt30min.ToString(); DissolutionMinPercentBox.Text = p.DissolutionMinPercentAt30min.ToString();
DissolutionTempBox.Text = p.DissolutionTemperatureC.ToString();
Dissolution1TimeBox.Text = p.Dissolution1TimeMin.ToString();
Dissolution2TimeBox.Text = p.Dissolution2TimeMin.ToString();
Dissolution1IntervalBox.Text = p.Dissolution1SampleIntervalMin.ToString();
Dissolution2IntervalBox.Text = p.Dissolution2SampleIntervalMin.ToString();
SampleTimesBox.Text = string.Join(",", p.DissolutionSampleTimes); SampleTimesBox.Text = string.Join(",", p.DissolutionSampleTimes);
} }
@@ -42,11 +49,23 @@ namespace TabletTester2025
p.FriabilityMaxLossPercent = double.Parse(FriabilityMaxLossBox.Text); p.FriabilityMaxLossPercent = double.Parse(FriabilityMaxLossBox.Text);
p.DisintegrationDosageForm = GetSelectedDisintegrationDosageForm(); p.DisintegrationDosageForm = GetSelectedDisintegrationDosageForm();
p.DisintegrationMaxSeconds = int.Parse(DisintegrationMaxSecBox.Text); p.DisintegrationMaxSeconds = int.Parse(DisintegrationMaxSecBox.Text);
p.DisintegrationSpeedRpm = double.Parse(DisintegrationSpeedBox.Text);
p.DisintegrationTemperatureC = double.Parse(DisintegrationTempBox.Text);
p.DissolutionMinPercentAt30min = double.Parse(DissolutionMinPercentBox.Text); p.DissolutionMinPercentAt30min = double.Parse(DissolutionMinPercentBox.Text);
p.DissolutionSampleTimes = SampleTimesBox.Text.Split(',').Select(s => int.Parse(s.Trim())).ToArray(); p.DissolutionTemperatureC = double.Parse(DissolutionTempBox.Text);
p.Dissolution1TimeMin = int.Parse(Dissolution1TimeBox.Text);
p.Dissolution2TimeMin = int.Parse(Dissolution2TimeBox.Text);
p.Dissolution1SampleIntervalMin = double.Parse(Dissolution1IntervalBox.Text);
p.Dissolution2SampleIntervalMin = double.Parse(Dissolution2IntervalBox.Text);
p.DissolutionSampleTimes = SampleTimesBox.Text
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(s => int.Parse(s.Trim()))
.ToArray();
MessageBox.Show("参数已保存(重启后生效)", "提示", MessageBoxButton.OK, MessageBoxImage.Information); ValidateParameters(p);
// 可以进一步将参数写入配置文件 appsettings.json 以便持久化(可选,此处省略) App.SaveCurrentPharmaParameters();
MessageBox.Show("参数已保存并立即生效", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
DialogResult = true; DialogResult = true;
Close(); Close();
} }
@@ -62,11 +81,6 @@ namespace TabletTester2025
Close(); Close();
} }
private void HardnessMinBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
}
private void DisintegrationDosageFormBox_SelectionChanged(object sender, SelectionChangedEventArgs e) private void DisintegrationDosageFormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (DisintegrationDosageFormBox.SelectedItem is ComboBoxItem item && item.Tag is string seconds) if (DisintegrationDosageFormBox.SelectedItem is ComboBoxItem item && item.Tag is string seconds)
@@ -93,5 +107,25 @@ namespace TabletTester2025
? item.Content?.ToString() ?? "普通片" ? item.Content?.ToString() ?? "普通片"
: "普通片"; : "普通片";
} }
private static void ValidateParameters(PharmaParameters p)
{
if (p.HardnessMin_N < 0 || p.HardnessMax_N <= p.HardnessMin_N)
throw new InvalidOperationException("硬度内控上限必须大于下限。");
if (p.HardnessTestCount <= 0)
throw new InvalidOperationException("硬度测试次数必须大于0。");
if (p.FriabilityTargetRpm <= 0 || p.FriabilityTargetRounds <= 0 || p.FriabilityMaxLossPercent <= 0)
throw new InvalidOperationException("脆碎度参数必须大于0。");
if (p.DisintegrationMaxSeconds <= 0 || p.DisintegrationSpeedRpm <= 0 || p.DisintegrationTemperatureC <= 0)
throw new InvalidOperationException("崩解参数必须大于0。");
if (p.DissolutionMinPercentAt30min < 0 || p.DissolutionMinPercentAt30min > 150)
throw new InvalidOperationException("溶出度Q值应在0到150之间。");
if (p.DissolutionTemperatureC <= 0 || p.Dissolution1TimeMin <= 0 || p.Dissolution2TimeMin <= 0)
throw new InvalidOperationException("溶出温度和运行时间必须大于0。");
if (p.Dissolution1SampleIntervalMin <= 0 || p.Dissolution2SampleIntervalMin <= 0)
throw new InvalidOperationException("溶出取样间隔必须大于0。");
if (p.DissolutionSampleTimes == null || p.DissolutionSampleTimes.Length == 0 || p.DissolutionSampleTimes.Any(t => t <= 0))
throw new InvalidOperationException("溶出取样时间点必须为大于0的分钟数。");
}
} }
} }

View File

@@ -59,14 +59,22 @@
"Dissolution2Time": 440 "Dissolution2Time": 440
}, },
"PharmaStandard": { "PharmaStandard": {
"StandardVersion": "中国药典2025",
"HardnessMin_N": 40,
"HardnessMax_N": 60,
"HardnessTestCount": 6, "HardnessTestCount": 6,
"FriabilityTargetRpm": 25.0, "FriabilityTargetRpm": 25.0,
"FriabilityTargetRounds": 100, "FriabilityTargetRounds": 100,
"FriabilityMaxLossPercent": 1.0, "FriabilityMaxLossPercent": 1.0,
"DisintegrationDosageForm": "普通片", "DisintegrationDosageForm": "普通片",
"DisintegrationMaxSeconds": 900, "DisintegrationMaxSeconds": 900,
"DisintegrationSpeedRpm": 31.0,
"DisintegrationTemperatureC": 37.0,
"DissolutionTemperatureC": 37.0,
"Dissolution1TimeMin": 30,
"Dissolution2TimeMin": 30,
"Dissolution1SampleIntervalMin": 5.0,
"Dissolution2SampleIntervalMin": 5.0,
"DissolutionSampleTimes": [ 5, 10, 15, 30, 45, 60 ], "DissolutionSampleTimes": [ 5, 10, 15, 30, 45, 60 ],
"DissolutionMinPercentAt30min": 80.0 "DissolutionMinPercentAt30min": 80.0
} }