更新2026

This commit is contained in:
GukSang.Jin
2026-05-16 16:58:57 +08:00
parent c4966677b9
commit 9de217d2ff
15 changed files with 785 additions and 309 deletions

View File

@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Collections.Generic;
@@ -22,9 +23,22 @@ namespace TabletTester2025.ViewModels
private readonly AlarmService _alarm;
private readonly BalanceService _balance; // ✅ 新增天平服务
private DispatcherTimer _disintegrationTimer;
private bool _isLoadingDissolution1Time;
private bool _isLoadingDissolution2Time;
private bool _isLoadingDisintegrationTime;
private bool _isLoadingDisintegrationSpeed;
private List<double> _dissolutionTimes = new List<double>();
private List<double> _dissolutionValues = new List<double>();
private readonly List<double> _dissolution1Times = new();
private readonly List<double> _dissolution1Values = new();
private readonly List<double> _dissolution2Times = new();
private readonly List<double> _dissolution2Values = new();
private DateTime _dissolution1StartTime = DateTime.MinValue;
private DateTime _dissolution2StartTime = DateTime.MinValue;
private bool _isDissolution1Running;
private bool _isDissolution2Running;
private string _dissolutionResultChannel = "";
private double _dissolutionResultRate30Min;
private double _dissolutionResultRSquared;
public int StationId { get; }
@@ -54,6 +68,8 @@ namespace TabletTester2025.ViewModels
// 溶出
[ObservableProperty] private double _dissolutionRpm;
[ObservableProperty] private double _dissolutionPercent;
[ObservableProperty] private double _dissolution1Percent;
[ObservableProperty] private double _dissolution2Percent;
// 硬度相关新增
[ObservableProperty] private int _hardnessTestCount = 6;
@@ -87,23 +103,34 @@ namespace TabletTester2025.ViewModels
[ObservableProperty] private double _dissolutionUpDownFreq = 32;
[ObservableProperty] private int _dissolutionSampleInterval = 5;
[ObservableProperty] private double _dissolutionTargetRpm = 50;
[ObservableProperty] private int _dissolution1TimeMin = 30;
[ObservableProperty] private int _dissolution2TimeMin = 30;
[ObservableProperty] private double _dissolutionElapsedTime;
[ObservableProperty] private double _dissolutionCountdown;
[ObservableProperty] private double _dissolutionRSquared;
[ObservableProperty] private double _dissolution1RSquared;
[ObservableProperty] private double _dissolution2RSquared;
[ObservableProperty] private string _dissolutionCurveStatus = "";
public IAsyncRelayCommand DissolutionUpCommand { get; }
public IAsyncRelayCommand DissolutionDownCommand { get; }
public IAsyncRelayCommand StopDissolutionCommand { get; }
public IAsyncRelayCommand PrintDissolutionCommand { get; }
public IAsyncRelayCommand StartDissolution1Command { get; }
public IAsyncRelayCommand StopDissolution1Command { get; }
public IAsyncRelayCommand StartDissolution2Command { get; }
public IAsyncRelayCommand StopDissolution2Command { get; }
// 崩解新增
[ObservableProperty] private double _disintegrationTargetFreq = 31;
[ObservableProperty] private double _disintegrationSpeedRpm = 31;
[ObservableProperty] private double _disintegrationTimeMin = 15;
public IAsyncRelayCommand StopDisintegrationCommand { get; }
public IAsyncRelayCommand PrintDisintegrationCommand { get; }
public PlotModel DissolutionPlotModel { get; }
private LineSeries _dissolutionSeries;
private DateTime _dissolutionStartTime;
private readonly LineSeries _dissolution1Series;
private readonly LineSeries _dissolution2Series;
// 命令
public IAsyncRelayCommand StartHardnessCommand { get; }
@@ -128,14 +155,33 @@ namespace TabletTester2025.ViewModels
StartFriabilityCommand = new AsyncRelayCommand(RunFriabilityAsync);
StartDisintegrationCommand = new AsyncRelayCommand(RunDisintegrationAsync);
StartDissolutionCommand = new AsyncRelayCommand(RunDissolutionAsync);
StartDissolutionCommand = new AsyncRelayCommand(StartDissolution1Async);
ExportHistoryCommand = new AsyncRelayCommand(ExportHistoryAsync);
// 溶出曲线
DissolutionPlotModel = new PlotModel { Title = $"工位{StationId} 溶出曲线" };
_dissolutionSeries = new LineSeries { Title = "溶出度 (%)", Color = OxyColors.Green };
DissolutionPlotModel.Series.Add(_dissolutionSeries);
DissolutionPlotModel = new PlotModel { Title = "溶出曲线" };
DissolutionPlotModel.Axes.Add(new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "时间 (min)",
Minimum = 0,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
});
DissolutionPlotModel.Axes.Add(new LinearAxis
{
Position = AxisPosition.Left,
Title = "溶出度 (%)",
Minimum = 0,
Maximum = 150,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
});
_dissolution1Series = new LineSeries { Title = "溶出1", Color = OxyColors.SeaGreen, StrokeThickness = 2 };
_dissolution2Series = new LineSeries { Title = "溶出2", Color = OxyColors.DodgerBlue, StrokeThickness = 2 };
DissolutionPlotModel.Series.Add(_dissolution1Series);
DissolutionPlotModel.Series.Add(_dissolution2Series);
// 硬度命令
//HardnessUpCommand = new AsyncRelayCommand(async () =>
@@ -202,19 +248,27 @@ namespace TabletTester2025.ViewModels
// 溶出度命令
DissolutionUpCommand = new AsyncRelayCommand(async () => await _plc.WriteCoilAsync(0x22, true));
DissolutionDownCommand = new AsyncRelayCommand(async () => await _plc.WriteCoilAsync(0x23, true));
StopDissolutionCommand = new AsyncRelayCommand(() => { Phase = TestPhase.Idle; return Task.CompletedTask; });
StartDissolution1Command = new AsyncRelayCommand(StartDissolution1Async);
StopDissolution1Command = new AsyncRelayCommand(StopDissolution1Async);
StartDissolution2Command = new AsyncRelayCommand(StartDissolution2Async);
StopDissolution2Command = new AsyncRelayCommand(StopDissolution2Async);
StopDissolutionCommand = new AsyncRelayCommand(StopDissolution1Async);
PrintDissolutionCommand = new AsyncRelayCommand(async () => await PrintReport("溶出度"));
// 崩解命令
StopDisintegrationCommand = new AsyncRelayCommand(() => { Phase = TestPhase.Idle; return Task.CompletedTask; });
StopDisintegrationCommand = new AsyncRelayCommand(StopDisintegrationAsync);
PrintDisintegrationCommand = new AsyncRelayCommand(async () => await PrintReport("崩解"));
_ = LoadDisintegrationTimeAsync();
_ = LoadDisintegrationSpeedAsync();
_ = LoadDissolutionTimesAsync();
}
private async Task PrintReport(string testName)
{
await App.Current.Dispatcher.InvokeAsync(() =>
{
MessageBox.Show($"打印{testName}报告 - 工位{StationId}", "打印", MessageBoxButton.OK, MessageBoxImage.Information);
MessageBox.Show($"打印{testName}报告", "打印", MessageBoxButton.OK, MessageBoxImage.Information);
});
}
@@ -242,21 +296,271 @@ namespace TabletTester2025.ViewModels
}
break;
case TestType.Dissolution:
DissolutionRpm = await _plc.ReadFloatAsync(_plcConfig.DissolutionRpm);
DissolutionPercent = await _plc.ReadFloatAsync(_plcConfig.DissolutionPercent);
if (_dissolutionStartTime != DateTime.MinValue)
{
double minutes = (DateTime.Now - _dissolutionStartTime).TotalMinutes;
_dissolutionSeries.Points.Add(new DataPoint(minutes, DissolutionPercent));
if (_dissolutionSeries.Points.Count > 200) _dissolutionSeries.Points.RemoveAt(0);
DissolutionPlotModel.InvalidatePlot(true);
}
await UpdateDissolutionDataAsync();
break;
}
}
catch { }
}
private async Task UpdateDissolutionDataAsync()
{
bool updated = false;
DissolutionRpm = await _plc.ReadFloatAsync(_plcConfig.DissolutionRpm);
if (_plcConfig.DisintegrationTemp != 0)
DisintegrationTemp = await _plc.ReadFloatAsync(_plcConfig.DisintegrationTemp);
if (_isDissolution1Running)
updated |= await ReadDissolutionChannelAsync(1);
if (_isDissolution2Running)
updated |= await ReadDissolutionChannelAsync(2);
UpdateDissolutionClock();
if (updated)
DissolutionPlotModel.InvalidatePlot(true);
}
private async Task<bool> ReadDissolutionChannelAsync(int channel)
{
ushort registerAddress = channel == 1
? ResolveDissolution1PercentAddress()
: _plcConfig.Dissolution2Percent;
if (registerAddress == 0)
{
DissolutionCurveStatus = $"溶出{channel}溶出度寄存器未配置";
return false;
}
double value = await _plc.ReadFloatAsync(registerAddress);
if (!IsValidDissolutionPercent(value))
{
DissolutionCurveStatus = $"溶出{channel}溶出度数据异常";
return false;
}
DateTime startTime = channel == 1 ? _dissolution1StartTime : _dissolution2StartTime;
if (startTime == DateTime.MinValue)
return false;
double minutes = (DateTime.Now - startTime).TotalMinutes;
if (channel == 1)
{
Dissolution1Percent = value;
DissolutionPercent = value;
AddDissolutionPoint(_dissolution1Times, _dissolution1Values, _dissolution1Series, minutes, value);
Dissolution1RSquared = CalculateRSquared(_dissolution1Times, _dissolution1Values);
DissolutionRSquared = Dissolution1RSquared;
}
else
{
Dissolution2Percent = value;
DissolutionPercent = value;
AddDissolutionPoint(_dissolution2Times, _dissolution2Values, _dissolution2Series, minutes, value);
Dissolution2RSquared = CalculateRSquared(_dissolution2Times, _dissolution2Values);
DissolutionRSquared = Dissolution2RSquared;
}
DissolutionCurveStatus = "";
return true;
}
private ushort ResolveDissolution1PercentAddress()
{
return _plcConfig.Dissolution1Percent != 0
? _plcConfig.Dissolution1Percent
: _plcConfig.DissolutionPercent;
}
private static bool IsValidDissolutionPercent(double value)
{
return double.IsFinite(value) && value >= 0 && value <= 150;
}
private static void AddDissolutionPoint(List<double> times, List<double> values, LineSeries series, double minutes, double value)
{
if (times.Count > 0 && minutes <= times[^1])
return;
times.Add(minutes);
values.Add(value);
series.Points.Add(new DataPoint(minutes, value));
}
private void UpdateDissolutionClock()
{
var now = DateTime.Now;
double elapsed1 = _isDissolution1Running && _dissolution1StartTime != DateTime.MinValue
? (now - _dissolution1StartTime).TotalMinutes
: 0;
double elapsed2 = _isDissolution2Running && _dissolution2StartTime != DateTime.MinValue
? (now - _dissolution2StartTime).TotalMinutes
: 0;
DissolutionElapsedTime = Math.Max(elapsed1, elapsed2);
var remaining = new List<double>();
if (_isDissolution1Running)
remaining.Add(Math.Max(0, Dissolution1TimeMin - elapsed1));
if (_isDissolution2Running)
remaining.Add(Math.Max(0, Dissolution2TimeMin - elapsed2));
DissolutionCountdown = remaining.Count == 0 ? 0 : remaining.Min();
}
partial void OnDissolution1TimeMinChanged(int value)
{
if (_isLoadingDissolution1Time || _plcConfig.Dissolution1Time == 0 || value <= 0)
return;
_ = WriteDissolutionTimeAsync(_plcConfig.Dissolution1Time, value);
}
partial void OnDissolution2TimeMinChanged(int value)
{
if (_isLoadingDissolution2Time || _plcConfig.Dissolution2Time == 0 || value <= 0)
return;
_ = WriteDissolutionTimeAsync(_plcConfig.Dissolution2Time, value);
}
private async Task LoadDissolutionTimesAsync()
{
if (_plcConfig.Dissolution1Time != 0)
{
try
{
_isLoadingDissolution1Time = true;
int value = await _plc.ReadIntAsync(_plcConfig.Dissolution1Time);
if (value > 0)
Dissolution1TimeMin = value;
}
catch { }
finally
{
_isLoadingDissolution1Time = false;
}
}
if (_plcConfig.Dissolution2Time != 0)
{
try
{
_isLoadingDissolution2Time = true;
int value = await _plc.ReadIntAsync(_plcConfig.Dissolution2Time);
if (value > 0)
Dissolution2TimeMin = value;
}
catch { }
finally
{
_isLoadingDissolution2Time = false;
}
}
}
private async Task WriteDissolutionTimeAsync(ushort registerAddress, int value)
{
if (registerAddress == 0 || value <= 0)
return;
try
{
await _plc.WriteRegisterAsync(registerAddress, (ushort)Math.Min(value, ushort.MaxValue));
}
catch { }
}
partial void OnDisintegrationTimeMinChanged(double value)
{
if (_isLoadingDisintegrationTime || _plcConfig.DisintegrationTime == 0 || value <= 0)
return;
_ = WriteDisintegrationTimeAsync(value);
}
partial void OnDisintegrationSpeedRpmChanged(double value)
{
if (_isLoadingDisintegrationSpeed || _plcConfig.DisintegrationSpeed == 0 || value <= 0)
return;
_ = WriteDisintegrationSpeedAsync(value);
}
private async Task LoadDisintegrationSpeedAsync()
{
if (_plcConfig.DisintegrationSpeed == 0)
return;
try
{
_isLoadingDisintegrationSpeed = true;
float value = await _plc.ReadFloatAsync(_plcConfig.DisintegrationSpeed);
if (value > 0)
DisintegrationSpeedRpm = value;
}
catch { }
finally
{
_isLoadingDisintegrationSpeed = false;
}
}
private async Task LoadDisintegrationTimeAsync()
{
if (_plcConfig.DisintegrationTime == 0)
return;
try
{
_isLoadingDisintegrationTime = true;
float value = await _plc.ReadFloatAsync(_plcConfig.DisintegrationTime);
if (value > 0)
DisintegrationTimeMin = value;
}
catch { }
finally
{
_isLoadingDisintegrationTime = false;
}
}
private async Task WriteDisintegrationTimeAsync(double value)
{
if (_plcConfig.DisintegrationTime == 0 || value <= 0)
return;
try
{
await _plc.WriteFloatAsync(_plcConfig.DisintegrationTime, (float)value);
}
catch { }
}
private async Task WriteDisintegrationSpeedAsync(double value)
{
if (_plcConfig.DisintegrationSpeed == 0 || value <= 0)
return;
try
{
await _plc.WriteFloatAsync(_plcConfig.DisintegrationSpeed, (float)value);
}
catch { }
}
private async Task PulseCoilAsync(ushort coilAddress)
{
if (coilAddress == 0)
throw new InvalidOperationException("PLC线圈地址未配置");
await _plc.WriteCoilAsync(coilAddress, true);
await Task.Delay(100);
await _plc.WriteCoilAsync(coilAddress, false);
}
private async Task RunHardnessAsync()
{
if (Phase != TestPhase.Idle) return;
@@ -274,9 +578,7 @@ namespace TabletTester2025.ViewModels
for (int i = 0; i < count; i++)
{ // 1. 给PLC发指令启动单次硬度测试
await _plc.WriteCoilAsync((ushort)(StationId == 1 ? _plcConfig.HardnessStartCoil :
StationId == 2 ? _plcConfig.HardnessStartCoil2
: StationId == 3 ? _plcConfig.HardnessStartCoil3 : 0), true);
await _plc.WriteCoilAsync(_plcConfig.HardnessStartCoil, true);
bool completed = false;
while (!completed && Phase == TestPhase.Running)
{
@@ -318,7 +620,7 @@ namespace TabletTester2025.ViewModels
}
}
/// 脆碎度测试主逻辑(适配3工位、实时状态显示)
/// 脆碎度测试主逻辑(实时状态显示)
private async Task RunFriabilityAsync()
{
@@ -333,17 +635,11 @@ namespace TabletTester2025.ViewModels
try
{
ushort startCoil = StationId switch
{
1 => _plcConfig.FriabilityStartCoil, // 工位1启动线圈
2 => _plcConfig.FriabilityStartCoil2, // 工位2启动线圈
3 => _plcConfig.FriabilityStartCoil3, // 工位3启动线圈
_ => 0
};
ushort startCoil = _plcConfig.FriabilityStartCoil;
if (startCoil == 0)
{
throw new InvalidOperationException("当前工位未配置脆碎度启动线圈地址");
throw new InvalidOperationException("未配置脆碎度启动线圈地址");
}
WeightBefore = await _balance.ReadWeightAsync();
await _plc.WriteCoilAsync(startCoil, true);
@@ -410,8 +706,12 @@ namespace TabletTester2025.ViewModels
try
{
await _plc.WriteCoilAsync(_plcConfig.DisintegrationStartCoil, true);
int maxSec = App.CurrentPharmaParams.DisintegrationMaxSeconds;
await WriteDisintegrationSpeedAsync(DisintegrationSpeedRpm);
await WriteDisintegrationTimeAsync(DisintegrationTimeMin);
await PulseCoilAsync(_plcConfig.DisintegrationStartCoil);
int maxSec = DisintegrationTimeMin > 0
? (int)Math.Ceiling(DisintegrationTimeMin * 60)
: App.CurrentPharmaParams.DisintegrationMaxSeconds;
while (RemainingTubes > 0 && DisintegrationSeconds < maxSec && Phase == TestPhase.Running)
{
await Task.Delay(500);
@@ -428,72 +728,182 @@ namespace TabletTester2025.ViewModels
finally
{
Phase = TestPhase.Idle;
DisintegrationPass = (RemainingTubes == 0 && DisintegrationSeconds <= App.CurrentPharmaParams.DisintegrationMaxSeconds);
int maxSec = DisintegrationTimeMin > 0
? (int)Math.Ceiling(DisintegrationTimeMin * 60)
: App.CurrentPharmaParams.DisintegrationMaxSeconds;
DisintegrationPass = (RemainingTubes == 0 && DisintegrationSeconds <= maxSec);
await SaveBatchResult();
}
}
private async Task RunDissolutionAsync()
private async Task StopDisintegrationAsync()
{
if (Phase != TestPhase.Idle) return;
CurrentTest = TestType.Dissolution;
Phase = TestPhase.Running;
DissolutionPass = false; // 添加这一行
_dissolutionStartTime = DateTime.Now;
_dissolutionSeries.Points.Clear();
_dissolutionTimes.Clear();
_dissolutionValues.Clear();
try
{
await _plc.WriteCoilAsync(_plcConfig.DissolutionStartCoil, true);
var sampleTimes = App.CurrentPharmaParams.DissolutionSampleTimes;
double prevMin = 0;
foreach (var t in sampleTimes)
{
int delayMs = (int)((t - prevMin) * 60 * 1000);
if (delayMs > 0) await Task.Delay(delayMs);
double value = await _plc.ReadFloatAsync(_plcConfig.DissolutionPercent);
DissolutionPercent = value;
_dissolutionTimes.Add(t);
_dissolutionValues.Add(value);
prevMin = t;
await App.Current.Dispatcher.InvokeAsync(() =>
{
MessageBox.Show($"工位{StationId} 请在{t}分钟取样。当前溶出度: {value:F1}%");
});
}
// 计算 R²
_dissolutionRSquared = CalculateRSquared(_dissolutionTimes, _dissolutionValues);
bool pass = DissolutionPercent >= App.CurrentPharmaParams.DissolutionMinPercentAt30min;
Phase = TestPhase.Completed;
}
catch (Exception ex)
{
await App.Current.Dispatcher.InvokeAsync(() => MessageBox.Show($"溶出测试出错: {ex.Message}"));
Phase = TestPhase.Error;
await PulseCoilAsync(_plcConfig.DisintegrationStopCoil);
}
finally
{
Phase = TestPhase.Idle;
DissolutionPass = (DissolutionPercent >= App.CurrentPharmaParams.DissolutionMinPercentAt30min);
await SaveBatchResult();
_disintegrationTimer?.Stop();
}
}
private async Task StartDissolution1Async()
{
CurrentTest = TestType.Dissolution;
Phase = TestPhase.Running;
DissolutionPass = false;
ResetDissolutionChannel(1);
_dissolution1StartTime = DateTime.Now;
_isDissolution1Running = true;
DissolutionPlotModel.Title = "溶出曲线";
await WriteDissolutionTimeAsync(_plcConfig.Dissolution1Time, Dissolution1TimeMin);
await PulseCoilAsync(_plcConfig.Dissolution1StartCoil);
}
private async Task StopDissolution1Async()
{
try
{
await PulseCoilAsync(_plcConfig.Dissolution1StopCoil);
await FinalizeDissolutionChannelAsync(1);
}
finally
{
_isDissolution1Running = false;
Phase = _isDissolution2Running ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}
}
private async Task StartDissolution2Async()
{
if (_plcConfig.Dissolution2Percent == 0)
{
LocalAlarm = "溶出2溶出度寄存器未配置";
DissolutionCurveStatus = LocalAlarm;
return;
}
CurrentTest = TestType.Dissolution;
Phase = TestPhase.Running;
DissolutionPass = false;
ResetDissolutionChannel(2);
_dissolution2StartTime = DateTime.Now;
_isDissolution2Running = true;
DissolutionPlotModel.Title = "溶出曲线";
await WriteDissolutionTimeAsync(_plcConfig.Dissolution2Time, Dissolution2TimeMin);
await PulseCoilAsync(_plcConfig.Dissolution2StartCoil);
}
private async Task StopDissolution2Async()
{
try
{
await PulseCoilAsync(_plcConfig.Dissolution2StopCoil);
await FinalizeDissolutionChannelAsync(2);
}
finally
{
_isDissolution2Running = false;
Phase = _isDissolution1Running ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}
}
private void ResetDissolutionChannel(int channel)
{
if (channel == 1)
{
_dissolution1Times.Clear();
_dissolution1Values.Clear();
_dissolution1Series.Points.Clear();
_dissolution1StartTime = DateTime.MinValue;
Dissolution1Percent = 0;
Dissolution1RSquared = 0;
}
else
{
_dissolution2Times.Clear();
_dissolution2Values.Clear();
_dissolution2Series.Points.Clear();
_dissolution2StartTime = DateTime.MinValue;
Dissolution2Percent = 0;
Dissolution2RSquared = 0;
}
DissolutionCurveStatus = "";
DissolutionPlotModel.InvalidatePlot(true);
}
private async Task FinalizeDissolutionChannelAsync(int channel)
{
var times = channel == 1 ? _dissolution1Times : _dissolution2Times;
var values = channel == 1 ? _dissolution1Values : _dissolution2Values;
if (values.Count == 0)
{
DissolutionCurveStatus = $"溶出{channel}无有效曲线数据,未保存结果";
LocalAlarm = DissolutionCurveStatus;
return;
}
_dissolutionResultChannel = $"溶出{channel}";
_dissolutionResultRate30Min = GetDissolutionRateAt30Min(times, values);
_dissolutionResultRSquared = CalculateRSquared(times, values);
DissolutionPercent = _dissolutionResultRate30Min;
DissolutionRSquared = _dissolutionResultRSquared;
if (channel == 1)
Dissolution1RSquared = _dissolutionResultRSquared;
else
Dissolution2RSquared = _dissolutionResultRSquared;
DissolutionPass = _dissolutionResultRate30Min >= App.CurrentPharmaParams.DissolutionMinPercentAt30min;
await SaveBatchResult();
}
private static double GetDissolutionRateAt30Min(List<double> times, List<double> values)
{
if (values.Count == 0)
return 0;
int index = 0;
double nearestDistance = double.MaxValue;
for (int i = 0; i < times.Count; i++)
{
double distance = Math.Abs(times[i] - 30);
if (distance < nearestDistance)
{
nearestDistance = distance;
index = i;
}
}
return values[index];
}
private async Task RunDissolutionAsync()
{
await StartDissolution1Async();
}
private async Task SaveBatchResult(bool? forcedQualified = null)
{
try
{
// 计算溶出曲线的 R²需在 RunDissolutionAsync 中计算后存入 _dissolutionRSquared
double rsquared = DissolutionRSquared; // 确保此属性在溶出结束后被赋值
double dissolutionRate30Min = CurrentTest == TestType.Dissolution
? _dissolutionResultRate30Min
: DissolutionPercent;
double rsquared = CurrentTest == TestType.Dissolution
? _dissolutionResultRSquared
: DissolutionRSquared;
var batch = new TestBatch
{
TestTime = DateTime.Now,
StationId = StationId,
SampleName = $"样品-{StationId}",
SampleName = "样品",
// 硬度
HardnessAvg = HardnessAvg,
@@ -514,11 +924,12 @@ namespace TabletTester2025.ViewModels
// 崩解
DisintegrationTimeSec = DisintegrationSeconds,
RemainingTubesAtEnd = RemainingTubes,
DisintegrationTargetFreq = DisintegrationTargetFreq,
DisintegrationTargetFreq = 0,
DisintegrationTemp = DisintegrationTemp,
// 溶出
DissolutionRate30Min = DissolutionPercent,
DissolutionChannel = CurrentTest == TestType.Dissolution ? _dissolutionResultChannel : "",
DissolutionRate30Min = dissolutionRate30Min,
DissolutionTargetRpm = DissolutionTargetRpm,
DissolutionRSquared = rsquared,
DissolutionSampleInterval = DissolutionSampleInterval,
@@ -557,7 +968,7 @@ namespace TabletTester2025.ViewModels
TestType.Hardness => "硬度",
TestType.Friability => "脆碎度",
TestType.Disintegration => "崩解",
TestType.Dissolution => "溶出",
TestType.Dissolution => string.IsNullOrWhiteSpace(_dissolutionResultChannel) ? "溶出" : _dissolutionResultChannel,
_ => ""
};
LocalAlarm = currentPass ? $"{projectName}测试合格" : $"{projectName}测试不合格";
@@ -572,7 +983,7 @@ namespace TabletTester2025.ViewModels
private double CalculateRSquared(List<double> timeMinutes, List<double> concentration)
{
if (timeMinutes.Count < 2) return 1;
if (timeMinutes.Count < 2 || timeMinutes.Count != concentration.Count) return 0;
int n = timeMinutes.Count;
double sumX = timeMinutes.Sum();
double sumY = concentration.Sum();
@@ -582,14 +993,18 @@ namespace TabletTester2025.ViewModels
double numerator = (n * sumXY - sumX * sumY);
double denominator = Math.Sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
if (denominator <= 0 || double.IsNaN(denominator))
return 0;
double r = numerator / denominator;
return r * r;
double result = r * r;
return double.IsFinite(result) ? result : 0;
}
private async Task ExportHistoryAsync()
{
var batches = await Task.Run(() => _db.GetBatches(StationId, 100));
string path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"工位{StationId}_检测记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
var batches = await Task.Run(() => _db.GetBatches(null, 100));
string path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"检测记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
_excel.ExportToExcel(batches, path);
await App.Current.Dispatcher.InvokeAsync(() => MessageBox.Show($"导出成功: {path}"));
}
@@ -603,4 +1018,4 @@ namespace TabletTester2025.ViewModels
}
}
}
}