初始化

This commit is contained in:
GukSang.Jin
2026-03-09 19:06:55 +08:00
parent 3261acb4ba
commit 1d4b4bc754
8 changed files with 372 additions and 319 deletions

View File

@@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Cardiopulmonarybypasssystems.Models"
mc:Ignorable="d"
Title="心肺转流检验"
Title="心肺转流检测"
Width="1024"
Height="768"
MinWidth="960"
@@ -27,13 +27,13 @@
<StackPanel>
<TextBlock Text="{Binding PageTitle}" FontSize="28" FontWeight="Bold" Foreground="White" TextWrapping="Wrap" />
<TextBlock Margin="0,8,0,0" Foreground="#EFFAFC" FontSize="15" TextWrapping="Wrap"
Text="&#x518D;&#x5FAA;&#x73AF;&#x7387;&#x7531;&#x5B9E;&#x65F6;&#x6D41;&#x91CF;&#x4F20;&#x611F;&#x5668;&#x8BA1;&#x7B97;&#xFF0C;&#x5176;&#x4ED6;&#x7ED3;&#x679C;&#x4EBA;&#x5DE5;&#x8BB0;&#x5F55;&#xFF0C;&#x5B9E;&#x9A8C;&#x7ED3;&#x675F;&#x540E;&#x5BFC;&#x51FA;&#x62A5;&#x8868;&#x3002;" />
Text="&#x518D;&#x5FAA;&#x73AF;&#x7387;&#x7531;&#x5B9E;&#x65F6;&#x6D41;&#x91CF;&#x4F20;&#x611F;&#x5668;&#x8BA1;&#x7B97;&#xFF0C;&#x5176;&#x4ED6;&#x7ED3;&#x679C;&#x7531;&#x4EBA;&#x5DE5;&#x586B;&#x5199;&#xFF0C;&#x68C0;&#x6D4B;&#x5B8C;&#x6210;&#x540E;&#x5BFC;&#x51FA;&#x62A5;&#x8868;&#x3002;" />
</StackPanel>
<StackPanel Grid.Row="1" Margin="0,12,0,0">
<WrapPanel>
<Button Command="{Binding ToggleAcquisitionCommand}" Content="&#x91C7;&#x96C6;" Background="#FFFFFFFF" Foreground="{StaticResource HeaderBrush}" />
<Button Command="{Binding AcknowledgeAlarmCommand}" Content="&#x6D88;&#x8B66;" Background="#33FFFFFF" />
<Button Command="{Binding CompleteExperimentCommand}" Content="&#x7ED3;&#x675F;" Background="#FFF0B145" />
<Button Command="{Binding CompleteDetectionCommand}" Content="&#x5B8C;&#x6210;&#x68C0;&#x6D4B;" Background="#FFF0B145" />
<Button Command="{Binding ExportReportCommand}" Content="&#x5BFC;&#x51FA; PDF" Background="#FFEA7E3C" />
</WrapPanel>
<TextBlock Margin="0,2,0,0" Foreground="#EFFAFC" FontSize="14" Text="{Binding ExportStateText}" TextWrapping="Wrap" />
@@ -53,7 +53,7 @@
<StackPanel>
<Border Style="{StaticResource CardBorderStyle}" Margin="0,0,0,12">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x5B9E;&#x9A8C;&#x603B;&#x89C8;" />
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x68C0;&#x6D4B;&#x603B;&#x89C8;" />
<UniformGrid Columns="1">
<Border Margin="0,0,0,8" Padding="14" Background="#FFE7F5F3" CornerRadius="14">
<StackPanel>
@@ -111,7 +111,7 @@
<Border Grid.Column="2" Style="{StaticResource CardBorderStyle}" Margin="0">
<TabControl>
<TabItem Header="&#x68C0;&#x9A8C;&#x5355;">
<TabItem Header="&#x9879;&#x76EE;&#x68C0;&#x6D4B;">
<ScrollViewer Margin="0,6,0,0" VerticalScrollBarVisibility="Auto">
<StackPanel>
<Border Style="{StaticResource CardBorderStyle}">
@@ -123,16 +123,27 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="0" MinHeight="40" Text="{Binding RequirementSearchText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1" Margin="8,0,0,0" MinWidth="90" Padding="14,8" Command="{Binding ClearRequirementSearchCommand}" Content="&#x6E05;&#x7A7A;" Background="#FF6B8791" />
<TextBox Grid.Column="0" Margin="0" MinHeight="40" Text="{Binding ItemSearchText, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1" Margin="8,0,0,0" MinWidth="90" Padding="14,8" Command="{Binding ClearItemSearchCommand}" Content="&#x6E05;&#x7A7A;" Background="#FF6B8791" />
</Grid>
<TextBlock Margin="0,8,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding FilteredRequirementSummary}" />
<TextBlock Margin="0,8,0,0" FontSize="14" Foreground="{StaticResource WarningBrush}" Text="&#x672A;&#x5339;&#x914D;&#x5230;&#x6D4B;&#x8BD5;&#x9879;&#x3002;">
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="220" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Style="{StaticResource CaptionStyle}" Text="&#x5FEB;&#x901F;&#x7B5B;&#x9009;" />
<ComboBox Grid.Column="1"
Margin="12,0,0,8"
ItemsSource="{Binding ItemFilterOptions}"
SelectedItem="{Binding ActiveFilter, Mode=TwoWay}" />
</Grid>
<TextBlock Margin="0,8,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding FilteredItemSummary}" />
<TextBlock Margin="0,8,0,0" FontSize="14" Foreground="{StaticResource WarningBrush}" Text="&#x672A;&#x5339;&#x914D;&#x5230;&#x9879;&#x76EE;&#x3002;">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasFilteredRequirements}" Value="False">
<DataTrigger Binding="{Binding HasFilteredItems}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
@@ -169,8 +180,8 @@
<Border Style="{StaticResource CardBorderStyle}">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x9879;&#x76EE;&#x6E05;&#x5355;" />
<DataGrid ItemsSource="{Binding FilteredRequirements}"
SelectedItem="{Binding SelectedRequirement, Mode=TwoWay}"
<DataGrid ItemsSource="{Binding FilteredItems}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
CanUserSortColumns="False"
IsReadOnly="True"
CanUserAddRows="False"
@@ -180,8 +191,26 @@
MinHeight="240">
<DataGrid.Columns>
<DataGridTextColumn Header="&#x9879;&#x76EE;" Binding="{Binding Item}" Width="180" />
<DataGridTextColumn Header="&#x91C7;&#x96C6;&#x65B9;&#x5F0F;" Binding="{Binding CaptureModeText}" Width="150" />
<DataGridTemplateColumn Header="&#x91C7;&#x96C6;&#x65B9;&#x5F0F;" Width="170">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Margin="0,4" Padding="10,4" Background="{Binding CaptureModeBadgeBackground}" CornerRadius="999">
<TextBlock HorizontalAlignment="Center" Foreground="{Binding CaptureModeBadgeForeground}" FontWeight="Bold" Text="{Binding CaptureModeText}" />
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="&#x5F53;&#x524D;&#x72B6;&#x6001;" Binding="{Binding StatusText}" Width="90" />
<DataGridTemplateColumn Header="&#x5B8C;&#x6210;&#x60C5;&#x51B5;" Width="130">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="0,2">
<TextBlock FontWeight="Bold" Text="{Binding CompletionText}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="{Binding CompletionHint}" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="&#x7ED3;&#x679C;/&#x8BF4;&#x660E;" Binding="{Binding Measured}" Width="*" />
</DataGrid.Columns>
</DataGrid>
@@ -191,33 +220,34 @@
<UniformGrid Columns="4" Margin="0,0,0,12">
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F53;&#x524D;&#x6D4B;&#x8BD5;&#x9879;" />
<TextBlock FontSize="18" FontWeight="Bold" Text="{Binding SelectedRequirementTitle}" TextWrapping="Wrap" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F53;&#x524D;&#x9879;&#x76EE;" />
<TextBlock FontSize="18" FontWeight="Bold" Text="{Binding SelectedItemTitle}" TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x91C7;&#x96C6;&#x65B9;&#x5F0F;" />
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedRequirementCaptureModeText}" TextWrapping="Wrap" />
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedItemCaptureModeText}" TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x72B6;&#x6001;" />
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedRequirementStatusText}" />
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedItemStatusText}" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedItem.CompletionHint}" TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8BB0;&#x5F55;&#x65F6;&#x95F4;" />
<TextBlock FontSize="16" FontWeight="Bold" Text="{Binding SelectedRequirement.RecordedAtText}" TextWrapping="Wrap" />
<TextBlock FontSize="16" FontWeight="Bold" Text="{Binding SelectedItem.RecordedAtText}" TextWrapping="Wrap" />
</StackPanel>
</Border>
</UniformGrid>
<Border Style="{StaticResource CardBorderStyle}">
<StackPanel>
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x6761;&#x6B3E;&#x8BE6;&#x60C5;&#x4E0E;&#x5F55;&#x5165;" />
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x9879;&#x76EE;&#x8BE6;&#x60C5;&#x4E0E;&#x7ED3;&#x679C;&#x586B;&#x5199;" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.12*" />
@@ -226,43 +256,43 @@
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x7C7B;&#x522B;" />
<TextBlock FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Text="{Binding SelectedRequirement.Category}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8981;&#x6C42;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirement.Requirement}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x65B9;&#x6CD5;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirement.Method}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x63A7;&#x5236;&#x70B9;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirement.ControlPoint}" />
<TextBlock FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Text="{Binding SelectedItem.Category}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5224;&#x5B9A;&#x8981;&#x6C42;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItem.AcceptanceCriteria}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x68C0;&#x6D4B;&#x65B9;&#x6CD5;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItem.TestMethod}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8BB0;&#x5F55;&#x8981;&#x70B9;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItem.RecordFocus}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x6570;&#x636E;&#x6765;&#x6E90;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirementMeasurementSource}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5B9E;&#x65F6;&#x9884;&#x89C8;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItemMeasurementSource}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5B9E;&#x65F6;&#x6570;&#x636E;&#x9884;&#x89C8;" />
<Border Padding="12" Background="#FFF4F8FA" CornerRadius="14" Margin="0,0,0,10">
<StackPanel>
<TextBlock FontSize="16" FontWeight="SemiBold" Text="{Binding SelectedRequirementLiveDisplay}" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedRequirementLiveHint}" TextWrapping="Wrap" />
<TextBlock FontSize="16" FontWeight="SemiBold" Text="{Binding SelectedItemLiveDisplay}" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedItemLiveHint}" TextWrapping="Wrap" />
</StackPanel>
</Border>
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F55;&#x5165;&#x8981;&#x6C42;" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x586B;&#x5199;&#x8BF4;&#x660E;" />
<TextBlock Margin="0,0,0,6" Foreground="{StaticResource MutedTextBrush}" FontSize="13" Text="{Binding RealtimeMeasurementHint}" TextWrapping="Wrap" />
<TextBox Text="{Binding ManualMeasuredValue, UpdateSourceTrigger=PropertyChanged}" MinHeight="104" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="{Binding SelectedRequirementUsesRealtimeValue}" />
<TextBox Text="{Binding ResultValue, UpdateSourceTrigger=PropertyChanged}" MinHeight="104" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="{Binding SelectedItemUsesRealtimeValue}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5224;&#x5B9A;" />
<ComboBox ItemsSource="{Binding ManualStatusOptions}" SelectedItem="{Binding SelectedManualStatusText, Mode=TwoWay}" />
<ComboBox ItemsSource="{Binding ResultStatusOptions}" SelectedItem="{Binding SelectedResultStatusText, Mode=TwoWay}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8BB0;&#x5F55;&#x4EBA;" />
<TextBox Text="{Binding ManualOperator, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding ResultOperator, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x590D;&#x6838;&#x4EBA;" />
<TextBox Text="{Binding ReviewerName, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x6279;&#x51C6;&#x4EBA;" />
<TextBox Text="{Binding ApproverName, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5907;&#x6CE8;" />
<TextBox Text="{Binding ManualNote, UpdateSourceTrigger=PropertyChanged}" MinHeight="104" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="{Binding SelectedRequirementUsesRealtimeValue}" />
<TextBox Text="{Binding ResultNote, UpdateSourceTrigger=PropertyChanged}" MinHeight="104" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="{Binding SelectedItemUsesRealtimeValue}" />
</StackPanel>
</Grid>
<WrapPanel Margin="0,8,0,0">
<Button Command="{Binding SelectPreviousRequirementCommand}" Content="&#x4E0A;&#x4E00;&#x9879;" Background="#FF6B8791" />
<Button Command="{Binding SelectNextRequirementCommand}" Content="&#x4E0B;&#x4E00;&#x9879;" Background="#FF6B8791" />
<Button Command="{Binding ApplyManualResultCommand}" Content="&#x4FDD;&#x5B58;" />
<Button Command="{Binding SelectPreviousItemCommand}" Content="&#x4E0A;&#x4E00;&#x9879;" Background="#FF6B8791" />
<Button Command="{Binding SelectNextItemCommand}" Content="&#x4E0B;&#x4E00;&#x9879;" Background="#FF6B8791" />
<Button Command="{Binding ApplyResultCommand}" Content="&#x4FDD;&#x5B58;" />
</WrapPanel>
</StackPanel>
</Border>
@@ -270,7 +300,7 @@
</ScrollViewer>
</TabItem>
<TabItem Header="&#x5B9E;&#x65F6;&#x5DE5;&#x51B5;">
<TabItem Header="&#x5B9E;&#x65F6;&#x6570;&#x636E;">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,6,0,0">
<UniformGrid Columns="4" Margin="0,0,0,12">
@@ -331,7 +361,7 @@
<Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5165;&#x53E3;/&#x51FA;&#x53E3;&#x538B;&#x529B;" />
<TextBlock FontSize="18" FontWeight="Bold" Text="{Binding SelectedRequirementLiveDisplay}" TextWrapping="Wrap" />
<TextBlock FontSize="18" FontWeight="Bold" Text="{Binding SelectedItemLiveDisplay}" TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
@@ -343,8 +373,8 @@
<Border Style="{StaticResource PanelSectionStyle}">
<StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F53;&#x524D;&#x9009;&#x4E2D;&#x6761;&#x6B3E;&#x7B56;&#x7565;" />
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedRequirementCaptureModeText}" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedRequirementMeasurementSource}" TextWrapping="Wrap" />
<TextBlock Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedItemCaptureModeText}" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedItemMeasurementSource}" TextWrapping="Wrap" />
</StackPanel>
</Border>
</UniformGrid>

View File

@@ -0,0 +1,73 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Cardiopulmonarybypasssystems.Models;
public partial class InspectionItem : ObservableObject
{
public required string Clause { get; init; }
public required string Category { get; init; }
public required string Item { get; init; }
public required string AcceptanceCriteria { get; init; }
public required string TestMethod { get; init; }
public required string RecordFocus { get; init; }
public InspectionItemCaptureMode CaptureMode { get; init; } = InspectionItemCaptureMode.ManualEntry;
public string MeasurementSource { get; init; } = "人工观察 / 检测记录";
public string ManualEntryHint { get; init; } = "按标准方法完成检测后,由检测员手动填写结果。";
public string LiveDisplayHint { get; init; } = "当前项目无实时信号接入。";
[ObservableProperty]
private string measured = "待检测";
[ObservableProperty]
private InspectionItemStatus status = InspectionItemStatus.Pending;
[ObservableProperty]
private string notes = "";
[ObservableProperty]
private string recordedBy = "";
[ObservableProperty]
private DateTime? recordedAt;
public string StatusText => Status switch
{
InspectionItemStatus.Qualified => "合格",
InspectionItemStatus.Warning => "预警",
InspectionItemStatus.Critical => "不合格",
_ => "待检"
};
public bool RequiresManualEntry => CaptureMode != InspectionItemCaptureMode.RealtimeMonitor;
public string CaptureModeText => CaptureMode switch
{
InspectionItemCaptureMode.RealtimeMonitor => "实时监控",
InspectionItemCaptureMode.RealtimeAssist => "实时显示+手动录入",
_ => "手动录入"
};
public string CaptureModeBadgeBackground => CaptureMode switch
{
InspectionItemCaptureMode.RealtimeMonitor => "#FFE3F6EF",
InspectionItemCaptureMode.RealtimeAssist => "#FFEAF0FB",
_ => "#FFFDF0E5"
};
public string CaptureModeBadgeForeground => CaptureMode switch
{
InspectionItemCaptureMode.RealtimeMonitor => "#FF0E6C58",
InspectionItemCaptureMode.RealtimeAssist => "#FF2A5DA8",
_ => "#FFA45A12"
};
public string CompletionText => Status == InspectionItemStatus.Pending ? "待填写" : "已完成";
public string CompletionHint => Status == InspectionItemStatus.Pending
? CaptureMode == InspectionItemCaptureMode.RealtimeMonitor ? "等待实时数据" : "等待填写结果"
: $"状态:{StatusText}";
public string RecordedAtText => RecordedAt?.ToString("yyyy-MM-dd HH:mm") ?? "未记录";
partial void OnStatusChanged(InspectionItemStatus value)
{
OnPropertyChanged(nameof(StatusText));
OnPropertyChanged(nameof(CompletionText));
OnPropertyChanged(nameof(CompletionHint));
}
partial void OnRecordedAtChanged(DateTime? value) => OnPropertyChanged(nameof(RecordedAtText));
}

View File

@@ -1,6 +1,6 @@
namespace Cardiopulmonarybypasssystems.Models;
public enum RequirementCaptureMode
public enum InspectionItemCaptureMode
{
ManualEntry,
RealtimeAssist,

View File

@@ -1,6 +1,6 @@
namespace Cardiopulmonarybypasssystems.Models;
public enum RequirementStatus
public enum InspectionItemStatus
{
Pending,
Qualified,

View File

@@ -1,52 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Cardiopulmonarybypasssystems.Models;
public partial class TestRequirement : ObservableObject
{
public required string Clause { get; init; }
public required string Category { get; init; }
public required string Item { get; init; }
public required string Requirement { get; init; }
public required string Method { get; init; }
public required string ControlPoint { get; init; }
public RequirementCaptureMode CaptureMode { get; init; } = RequirementCaptureMode.ManualEntry;
public string MeasurementSource { get; init; } = "人工观察 / 试验记录";
public string ManualEntryHint { get; init; } = "按标准方法完成试验后,由检验员手动录入结果。";
public string LiveDisplayHint { get; init; } = "当前条款无实时信号接入。";
[ObservableProperty]
private string measured = "待检测";
[ObservableProperty]
private RequirementStatus status = RequirementStatus.Pending;
[ObservableProperty]
private string notes = "";
[ObservableProperty]
private string recordedBy = "";
[ObservableProperty]
private DateTime? recordedAt;
public string StatusText => Status switch
{
RequirementStatus.Qualified => "合格",
RequirementStatus.Warning => "预警",
RequirementStatus.Critical => "不合格",
_ => "待检"
};
public bool RequiresManualEntry => CaptureMode != RequirementCaptureMode.RealtimeMonitor;
public string CaptureModeText => CaptureMode switch
{
RequirementCaptureMode.RealtimeMonitor => "实时监控",
RequirementCaptureMode.RealtimeAssist => "实时显示+手动录入",
_ => "手动录入"
};
public string RecordedAtText => RecordedAt?.ToString("yyyy-MM-dd HH:mm") ?? "未记录";
partial void OnStatusChanged(RequirementStatus value) => OnPropertyChanged(nameof(StatusText));
partial void OnRecordedAtChanged(DateTime? value) => OnPropertyChanged(nameof(RecordedAtText));
}

View File

@@ -4,6 +4,6 @@ namespace Cardiopulmonarybypasssystems.Services;
public interface IStandardRepository
{
IReadOnlyList<TestRequirement> GetRequirements();
IReadOnlyList<InspectionItem> GetInspectionItems();
IReadOnlyList<TraceEvent> GetSeedTraceEvents();
}

View File

@@ -4,31 +4,31 @@ namespace Cardiopulmonarybypasssystems.Services;
public sealed class StandardRepository : IStandardRepository
{
public IReadOnlyList<TestRequirement> GetRequirements() =>
public IReadOnlyList<InspectionItem> GetInspectionItems() =>
[
new() { Clause = "4.1.1", Category = "生物性能", Item = "无菌", Requirement = "插管应无菌", Method = "对灭菌后样品进行无菌培养观察,确认无菌生长", ControlPoint = "灭菌批次放行" },
new() { Clause = "4.1.2", Category = "生物性能", Item = "细菌内毒素", Requirement = "内毒素不超过 20 EU/件", Method = "提取样液后进行细菌内毒素检测,确认每件结果不超过限值", ControlPoint = "内毒素检测记录" },
new() { Clause = "4.1.3", Category = "生物性能", Item = "生物相容性", Requirement = "符合预期用途生物学评价", Method = "结合预期用途完成生物学评价并审查评价结论", ControlPoint = "生物学评价记录" },
new() { Clause = "4.2.1", Category = "物理性能", Item = "血液通道密合性", Requirement = "血液通道应泄漏", Method = "封闭血液通道并加压至最大工作压力 1.5 倍,保压观察是否泄漏", ControlPoint = "人工保压观察" },
new() { Clause = "4.2.2", Category = "物理性能", Item = "接头", Requirement = "连接牢固,辅助接头同样满足要求", Method = "对接头和辅助接头施加规定拉力,检查是否松动或脱开", ControlPoint = "人工拉脱力记录" },
new() { Clause = "4.2.3", Category = "物理性能", Item = "抗扭结抗性", Requirement = "流量降低不超过 50%", Method = "将插管弯折至规定状态,比较弯折前后流量变化", ControlPoint = "人工填写弯折流量" },
new() { Clause = "4.2.4", Category = "物理性能", Item = "抗拉强度", Requirement = "承受持续最小拉力且不分离", Method = "对连接部位持续施加预期使用力 1.5 倍拉力,观察是否分离", ControlPoint = "拉力试验记录" },
new() { Clause = "4.2.5", Category = "物理性能", Item = "外观", Requirement = "无毛刺、裂纹、异物、色斑,远端无锐角", Method = "在正常照明下目视检查管体表面、接口和远端外观", ControlPoint = "人工外观复核" },
new() { Clause = "4.2.6", Category = "物理性能", Item = "完整性", Requirement = "模拟使用后无腐蚀、磨损、塌陷", Method = "完成模拟使用后,对管体和组件进行放大检查", ControlPoint = "使用后检查" },
new() { Clause = "4.2.7", Category = "物理性能", Item = "射线可探测性", Requirement = "需具备可探测性时应满足要求", Method = "在影像条件下观察插管显影情况和可识别", ControlPoint = "影像记录" },
new() { Clause = "4.2.8", Category = "物理性能", Item = "距离标记", Requirement = "标记应指示至远端距离,间隔不超过 10 mm 或风评值", Method = "目视检查距离标记位置间距和清晰度", ControlPoint = "人工核对标记" },
new() { Clause = "4.2.9", Category = "物理性能", Item = "管腔标记", Requirement = "端口和血流方向可肉眼识别", Method = "目视检查端口标识和血流方向标识是否清晰可辨", ControlPoint = "人工核对流向" },
new() { Clause = "4.1.1", Category = "生物性能", Item = "无菌", AcceptanceCriteria = "应无菌", TestMethod = "进行无菌培养观察;判定无菌生长", RecordFocus = "记录无菌培养结果" },
new() { Clause = "4.1.2", Category = "生物性能", Item = "细菌内毒素", AcceptanceCriteria = "每件内毒素不超过 20 EU", TestMethod = "提取液进行内毒素检测;判定结果不超过 20 EU/件。", RecordFocus = "记录内毒素检测" },
new() { Clause = "4.1.3", Category = "生物性能", Item = "生物相容性", AcceptanceCriteria = "生物学评价应符合预期用途。", TestMethod = "预期用途完成生物学评价;判定评价结论符合使用要求。", RecordFocus = "记录生物学评价结论" },
new() { Clause = "4.2.1", Category = "物理性能", Item = "血液通道密合性", AcceptanceCriteria = "血液通道应泄漏", TestMethod = "封闭通道并加压保压观察;判定血液通道无泄漏", RecordFocus = "观察并记录保压结果" },
new() { Clause = "4.2.2", Category = "物理性能", Item = "接头", AcceptanceCriteria = "各接头连接牢固。", TestMethod = "对接头施加规定拉力;判定连接不松动、不脱开", RecordFocus = "记录接头拉脱结果" },
new() { Clause = "4.2.3", Category = "物理性能", Item = "抗扭结抗性", AcceptanceCriteria = "弯折后流量降低不超过 50%", TestMethod = "将插管弯折后测量流量变化;判定流量降低不超过 50%。", RecordFocus = "填写弯折前后流量" },
new() { Clause = "4.2.4", Category = "物理性能", Item = "抗拉强度", AcceptanceCriteria = "受力后部件不应分离", TestMethod = "对连接部位持续施加拉力;判定部件不分离", RecordFocus = "记录拉力试验结果" },
new() { Clause = "4.2.5", Category = "物理性能", Item = "外观", AcceptanceCriteria = "外观应完整、洁净、无明显缺陷。", TestMethod = "在正常照明下目视检查外观;判定无毛刺、裂纹、异物和锐角。", RecordFocus = "核对并记录外观结果" },
new() { Clause = "4.2.6", Category = "物理性能", Item = "完整性", AcceptanceCriteria = "模拟使用后应保持完整。", TestMethod = "模拟使用后检查管体和组件;判定无腐蚀、磨损和塌陷。", RecordFocus = "记录使用后检查结果" },
new() { Clause = "4.2.7", Category = "物理性能", Item = "射线可探测性", AcceptanceCriteria = "需要时应具备射线可探测性。", TestMethod = "在影像条件下观察显影情况;判定插管可被清晰识别", RecordFocus = "记录影像观察结果" },
new() { Clause = "4.2.8", Category = "物理性能", Item = "距离标记", AcceptanceCriteria = "距离标记应清晰且间隔符合要求。", TestMethod = "检查距离标记位置间距;判定标记清晰且间隔符合要求。", RecordFocus = "核对距离标记" },
new() { Clause = "4.2.9", Category = "物理性能", Item = "管腔标记", AcceptanceCriteria = "端口和流向标识应清晰可辨。", TestMethod = "检查端口和流向标识;判定标识肉眼清晰可辨", RecordFocus = "核对端口和流向标识" },
new()
{
Clause = "4.3.1",
Category = "性能特征",
Item = "压力降",
Requirement = "应在制造商声明范围内",
Method = "在额定流量点同步测量入口出口压力,计算压差",
ControlPoint = "入口/出口压差",
CaptureMode = RequirementCaptureMode.RealtimeMonitor,
AcceptanceCriteria = "压力降应在声明范围内",
TestMethod = "在额定流量点测量入口出口压力;判定压差在声明范围内。",
RecordFocus = "实时监控压差",
CaptureMode = InspectionItemCaptureMode.RealtimeMonitor,
MeasurementSource = "入口/出口压力传感器",
ManualEntryHint = "该条款由实时压力信号自动采集,无需人工重复录入。",
ManualEntryHint = "该项目由实时压力信号自动采集,无需人工重复填写。",
LiveDisplayHint = "实时显示入口压力、出口压力和压差,用于直接判定压力降。"
},
new()
@@ -36,12 +36,12 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.2",
Category = "性能特征",
Item = "抗塌陷",
Requirement = "引流管负压后压降增量不超过 40%",
Method = "施加负压后比较压差增量和流量变化,判断是否塌陷",
ControlPoint = "负压后人工判定",
CaptureMode = RequirementCaptureMode.RealtimeAssist,
AcceptanceCriteria = "负压后压降增量不超过 40%",
TestMethod = "施加负压后比较压差增量和流量变化;判定压降增量不超过限值。",
RecordFocus = "记录负压与压差变化",
CaptureMode = InspectionItemCaptureMode.RealtimeAssist,
MeasurementSource = "负压辅助引流、入口/出口压差趋势",
ManualEntryHint = "系统实时显示负压与压差趋势,最终结论仍由检验员按试验工况手动录入。",
ManualEntryHint = "系统实时显示负压与压差趋势,最终结论仍由检测员根据检测记录手动填写。",
LiveDisplayHint = "建议结合负压、压差增量和流量变化综合判定是否塌陷。"
},
new()
@@ -49,12 +49,12 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.3",
Category = "性能特征",
Item = "再循环",
Requirement = "再循环百分比在声明范围内",
Method = "在双腔回路模型中测量引流回输流量,计算再循环比例",
ControlPoint = "三路流量实时计算",
CaptureMode = RequirementCaptureMode.RealtimeMonitor,
AcceptanceCriteria = "再循环比例应在声明范围内",
TestMethod = "在双腔回路中测量引流回输流量;判定再循环比例在声明范围内。",
RecordFocus = "实时监控再循环率",
CaptureMode = InspectionItemCaptureMode.RealtimeMonitor,
MeasurementSource = "主泵 / 静脉引流 / 动脉回输流量传感器",
ManualEntryHint = "该条款由三路流量实时计算,无需人工重复录入。",
ManualEntryHint = "该项目由三路流量实时计算,无需人工重复填写。",
LiveDisplayHint = "实时显示主泵、引流、回输流量及再循环率。"
},
new()
@@ -62,12 +62,12 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.4",
Category = "性能特征",
Item = "血细胞破坏",
Requirement = "游离血红蛋白增量在声明范围内",
Method = "进行体外循环试验检测循环前后游离血红蛋白增量",
ControlPoint = "人工填写 fHb",
CaptureMode = RequirementCaptureMode.RealtimeAssist,
AcceptanceCriteria = "游离血红蛋白增量在声明范围内",
TestMethod = "进行体外循环试验检测游离血红蛋白;判定增量在声明范围内。",
RecordFocus = "填写游离血红蛋白结果",
CaptureMode = InspectionItemCaptureMode.RealtimeAssist,
MeasurementSource = "游离血红蛋白在线趋势 + 实验室结果",
ManualEntryHint = "系统仅实时显示趋势,最终结果应以验室检测值手动录入。",
ManualEntryHint = "系统仅实时显示趋势,最终结果应以验室检测值手动填写。",
LiveDisplayHint = "关注游离血红蛋白上升趋势,避免仅用在线值替代正式记录。"
},
new()
@@ -75,29 +75,29 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.4",
Category = "性能特征",
Item = "血小板/白细胞减少率",
Requirement = "应在声明范围内",
Method = "进行体外循环试验比较循环前后血小板和白细胞计数变化",
ControlPoint = "人工填写血常规",
CaptureMode = RequirementCaptureMode.RealtimeAssist,
AcceptanceCriteria = "血细胞减少率应在声明范围内",
TestMethod = "进行体外循环试验比较血细胞计数;判定减少率在声明范围内。",
RecordFocus = "填写血细胞计数结果",
CaptureMode = InspectionItemCaptureMode.RealtimeAssist,
MeasurementSource = "白细胞减少率趋势 + 血常规结果",
ManualEntryHint = "系统用于实时显示趋势,最终血常规结果仍需人工录入。",
LiveDisplayHint = "实时观察白细胞减少率趋势,最终以检报告为准。"
ManualEntryHint = "系统用于实时显示趋势,最终血常规结果仍需人工填写。",
LiveDisplayHint = "实时观察白细胞减少率趋势,最终以检报告为准。"
},
new() { Clause = "4.3.5", Category = "性能特征", Item = "有效期", Requirement = "有效期内应符合标准", Method = "对实时或加速老化样品复测关键性能,评价有效期内是否仍满足要求", ControlPoint = "老化记录" },
new() { Clause = "4.4.1", Category = "化学性能", Item = "还原物质", Requirement = "高锰酸钾消耗差值 < 2.0 mL", Method = "制备检验液后比较高锰酸钾消耗量与空白液差值", ControlPoint = "人工填写化学结果" },
new() { Clause = "4.4.2", Category = "化学性能", Item = "金属离子", Requirement = "Ba、Cr、Cu、Pb 总量符合限值", Method = "制备浸提液后测定钡、铬、铜、铅总量", ControlPoint = "人工填写金属离子" },
new() { Clause = "4.4.3", Category = "化学性能", Item = "酸碱度", Requirement = "检验液与空白差值不超过 4.0", Method = "测量检验液与空白液的 pH 差值", ControlPoint = "人工填写 pH" },
new() { Clause = "4.4.4", Category = "化学性能", Item = "蒸发残渣", Requirement = "不超过规定值", Method = "蒸干检验液并称量残渣,确认结果不超过限值", ControlPoint = "人工填写残渣" },
new() { Clause = "4.4.5", Category = "化学性能", Item = "紫外吸光度", Requirement = "应小于 0.1", Method = "测量检验液在规定波长下的紫外吸光度", ControlPoint = "人工填写 UV" },
new() { Clause = "4.4.5", Category = "化学性能", Item = "色泽", Requirement = "应无色透明", Method = "目视观察检验液色和澄清度", ControlPoint = "人工外观确认" },
new() { Clause = "4.5", Category = "残留", Item = "环氧乙烷残留量", Requirement = "EO 残留量不大于 10 mg/kg", Method = "对样品提取后测定环氧乙烷残留量,确认不超过限值", ControlPoint = "人工填写 EO" },
new() { Clause = "4.6", Category = "耐蚀性", Item = "金属部件耐蚀性", Requirement = "直接接触血液部位不超过 1 级", Method = "对金属部件进行耐蚀性试验并腐蚀等级判定", ControlPoint = "耐蚀性记录" },
new() { Clause = "4.7", Category = "清洁度", Item = "污染指数", Requirement = "污染指数不超过 90", Method = "收集样品污染物并计算污染指数", ControlPoint = "人工填写污染指数" }
new() { Clause = "4.3.5", Category = "性能特征", Item = "有效期", AcceptanceCriteria = "有效期内应持续符合要求。", TestMethod = "对老化样品复测关键性能;判定有效期内仍符合要求", RecordFocus = "记录老化复测结果" },
new() { Clause = "4.4.1", Category = "化学性能", Item = "还原物质", AcceptanceCriteria = "高锰酸钾消耗差值小于 2.0 mL", TestMethod = "测定检验液与空白液的高锰酸钾消耗差值;判定差值小于 2.0 mL。", RecordFocus = "填写还原物质结果" },
new() { Clause = "4.4.2", Category = "化学性能", Item = "金属离子", AcceptanceCriteria = "金属离子总量符合限值", TestMethod = "测定浸提液中金属离子总量;判定结果符合限值要求。", RecordFocus = "填写金属离子结果" },
new() { Clause = "4.4.3", Category = "化学性能", Item = "酸碱度", AcceptanceCriteria = "检验液与空白液 pH 差值不超过 4.0", TestMethod = "测量检验液与空白液的 pH 差值;判定差值不超过 4.0。", RecordFocus = "填写 pH 差值" },
new() { Clause = "4.4.4", Category = "化学性能", Item = "蒸发残渣", AcceptanceCriteria = "蒸发残渣量不超过规定值", TestMethod = "蒸干检验液并称量残渣;判定残渣量不超过规定值。", RecordFocus = "填写残渣称量结果" },
new() { Clause = "4.4.5", Category = "化学性能", Item = "紫外吸光度", AcceptanceCriteria = "紫外吸光度应小于 0.1", TestMethod = "测量检验液的紫外吸光度;判定吸光度小于 0.1。", RecordFocus = "填写紫外吸光度" },
new() { Clause = "4.4.5", Category = "化学性能", Item = "色泽", AcceptanceCriteria = "检验液应无色透明", TestMethod = "目视观察检验液色和澄清度;判定应无色透明。", RecordFocus = "记录色泽观察结果" },
new() { Clause = "4.5", Category = "残留", Item = "环氧乙烷残留量", AcceptanceCriteria = "环氧乙烷残留量不大于 10 mg/kg", TestMethod = "测定样品的环氧乙烷残留量;判定结果不大于 10 mg/kg。", RecordFocus = "填写 EO 残留量" },
new() { Clause = "4.6", Category = "耐蚀性", Item = "金属部件耐蚀性", AcceptanceCriteria = "金属部件腐蚀等级不超过 1 级", TestMethod = "进行耐蚀性试验并评定腐蚀等级判定结果不超过 1 级。", RecordFocus = "记录腐蚀等级" },
new() { Clause = "4.7", Category = "清洁度", Item = "污染指数", AcceptanceCriteria = "污染指数不超过 90", TestMethod = "收集污染物并计算污染指数;判定结果不超过 90。", RecordFocus = "填写污染指数" }
];
public IReadOnlyList<TraceEvent> GetSeedTraceEvents() =>
[
new() { Timestamp = DateTime.Today.AddHours(8.2), Stage = "批次建档", Detail = "创建一次性使用动静脉插管检任务", Operator = "张工" },
new() { Timestamp = DateTime.Today.AddHours(8.2), Stage = "批次建档", Detail = "创建一次性使用动静脉插管检任务", Operator = "张工" },
new() { Timestamp = DateTime.Today.AddHours(8.6), Stage = "样品登记", Detail = "录入规格 24Fr/32Fr、双腔、EO 灭菌", Operator = "张工" },
new() { Timestamp = DateTime.Today.AddHours(9.1), Stage = "回路预充", Detail = "实时通道仅做辅助显示,最终结果由试验员手动录入", Operator = "王工" }
];

View File

@@ -16,10 +16,10 @@ public partial class MainViewModel : ObservableObject
private readonly DispatcherTimer _timer;
[ObservableProperty]
private string pageTitle = "心肺转流系统一次性使用动静脉插管";
private string pageTitle = "心肺转流系统一次性使用动静脉插管检测";
[ObservableProperty]
private string currentStage = "试验执行中";
private string currentStage = "检测进行中";
[ObservableProperty]
private string operatorName = "张工";
@@ -40,7 +40,7 @@ public partial class MainViewModel : ObservableObject
private bool acquisitionRunning = true;
[ObservableProperty]
private bool experimentCompleted;
private bool detectionCompleted;
[ObservableProperty]
private double complianceRate;
@@ -58,42 +58,42 @@ public partial class MainViewModel : ObservableObject
private double deltaPressure;
[ObservableProperty]
private string latestAction = "系统已载入标准条款,等待试验员录入结果。";
private string latestAction = "系统已载入标准项目,等待填写检测结果。";
[ObservableProperty]
private TestRequirement? selectedRequirement;
private InspectionItem? selectedItem;
[ObservableProperty]
private string manualMeasuredValue = "";
private string resultValue = "";
[ObservableProperty]
private string manualNote = "";
private string resultNote = "";
[ObservableProperty]
private string manualOperator = "张工";
private string resultOperator = "张工";
[ObservableProperty]
private string selectedManualStatusText = "合格";
private string selectedResultStatusText = "合格";
[ObservableProperty]
private string experimentSummary = "";
private string detectionSummary = "";
[ObservableProperty]
private string requirementSearchText = "";
private string itemSearchText = "";
public MainViewModel(IStandardRepository repository, IModbusTelemetryService telemetryService)
{
_telemetryService = telemetryService;
Requirements = new ObservableCollection<TestRequirement>(repository.GetRequirements());
InspectionItems = new ObservableCollection<InspectionItem>(repository.GetInspectionItems());
Channels = new ObservableCollection<DeviceChannel>(telemetryService.GetChannels());
TraceEvents = new ObservableCollection<TraceEvent>(repository.GetSeedTraceEvents());
AlarmMessages = new ObservableCollection<AlarmMessage>();
ManualStatusOptions = new ObservableCollection<string>(["待检", "合格", "预警", "不合格"]);
ResultStatusOptions = new ObservableCollection<string>(["待检", "合格", "预警", "不合格"]);
SelectedRequirement = Requirements.FirstOrDefault();
if (SelectedRequirement is not null)
SelectedItem = InspectionItems.FirstOrDefault();
if (SelectedItem is not null)
{
LoadSelectedRequirementDraft(SelectedRequirement);
LoadSelectedItemDraft(SelectedItem);
}
RefreshTelemetryPanel();
@@ -104,21 +104,21 @@ public partial class MainViewModel : ObservableObject
_timer.Start();
}
public ObservableCollection<TestRequirement> Requirements { get; }
public ObservableCollection<InspectionItem> InspectionItems { get; }
public ObservableCollection<DeviceChannel> Channels { get; }
public ObservableCollection<TraceEvent> TraceEvents { get; }
public ObservableCollection<AlarmMessage> AlarmMessages { get; }
public ObservableCollection<string> ManualStatusOptions { get; }
public IEnumerable<TestRequirement> FilteredRequirements => Requirements.Where(MatchesRequirementSearch);
public bool HasFilteredRequirements => FilteredRequirements.Any();
public ObservableCollection<string> ResultStatusOptions { get; }
public IEnumerable<InspectionItem> FilteredItems => InspectionItems.Where(MatchesItemSearch);
public bool HasFilteredItems => FilteredItems.Any();
public IEnumerable<DeviceChannel> FlowSensorChannels => Channels.Where(IsFlowSensorChannel);
public IEnumerable<DeviceChannel> OtherChannels => Channels.Where(channel => !IsFlowSensorChannel(channel));
public string ComplianceDisplay => $"{ComplianceRate:F0}%";
public string DeltaPressureDisplay => $"{DeltaPressure:F1} mmHg";
public string ExportStateText => ExperimentCompleted ? "实验已结束,可导出 PDF 正式报表" : "实验进行中,完成后导出 PDF 正式报表";
public string SelectedRequirementTitle => SelectedRequirement?.Item ?? "未选择测试项";
public string SelectedRequirementStatusText => SelectedRequirement?.StatusText ?? "待检";
public string ExportStateText => DetectionCompleted ? "检测已完成,可导出 PDF 正式报表" : "检测进行中,完成后导出 PDF 正式报表";
public string SelectedItemTitle => SelectedItem?.Item ?? "未选择项";
public string SelectedItemStatusText => SelectedItem?.StatusText ?? "待检";
public string RealtimeRecirculationDisplay => $"{RecirculationRate:F1}%";
public string PumpFlowDisplay => $"{PumpFlow:F2} L/min";
public string DrainageFlowDisplay => $"{DrainageFlow:F2} L/min";
@@ -130,19 +130,19 @@ public partial class MainViewModel : ObservableObject
public double PumpFlowNormalizedValue => ChannelNormalizedValue("主泵流量");
public double DrainageFlowNormalizedValue => ChannelNormalizedValue("静脉引流流量");
public double ReturnFlowNormalizedValue => ChannelNormalizedValue("动脉回输流量");
public string FilteredRequirementSummary => $"{FilteredRequirements.Count()} / {Requirements.Count} ";
public bool HasRequirementSearchText => !string.IsNullOrWhiteSpace(RequirementSearchText);
public int RealtimeMonitorCount => Requirements.Count(requirement => requirement.CaptureMode == RequirementCaptureMode.RealtimeMonitor);
public int RealtimeAssistCount => Requirements.Count(requirement => requirement.CaptureMode == RequirementCaptureMode.RealtimeAssist);
public int ManualEntryCount => Requirements.Count(requirement => requirement.CaptureMode == RequirementCaptureMode.ManualEntry);
public string SelectedRequirementCaptureModeText => SelectedRequirement?.CaptureModeText ?? "未选择";
public string SelectedRequirementMeasurementSource => SelectedRequirement?.MeasurementSource ?? "-";
public bool SelectedRequirementUsesRealtimeValue => SelectedRequirement?.CaptureMode == RequirementCaptureMode.RealtimeMonitor;
public string RealtimeMeasurementHint => SelectedRequirementUsesRealtimeValue
? "当前条款取实时工况计算值,不再手动录入。"
: SelectedRequirement?.ManualEntryHint ?? "当前条款保持人工录入。";
public string SelectedRequirementLiveDisplay => BuildSelectedRequirementLiveDisplay();
public string SelectedRequirementLiveHint => SelectedRequirement?.LiveDisplayHint ?? "当前条款无实时映射。";
public string FilteredItemSummary => $"{FilteredItems.Count()} / {InspectionItems.Count} ";
public bool HasItemSearchText => !string.IsNullOrWhiteSpace(ItemSearchText);
public int RealtimeMonitorCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.RealtimeMonitor);
public int RealtimeAssistCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.RealtimeAssist);
public int ManualEntryCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.ManualEntry);
public string SelectedItemCaptureModeText => SelectedItem?.CaptureModeText ?? "未选择";
public string SelectedItemMeasurementSource => SelectedItem?.MeasurementSource ?? "-";
public bool SelectedItemUsesRealtimeValue => SelectedItem?.CaptureMode == InspectionItemCaptureMode.RealtimeMonitor;
public string RealtimeMeasurementHint => SelectedItemUsesRealtimeValue
? "当前项目使用实时数据自动判定,无需手动填写。"
: SelectedItem?.ManualEntryHint ?? "当前项目需要人工填写结果。";
public string SelectedItemLiveDisplay => BuildSelectedItemLiveDisplay();
public string SelectedItemLiveHint => SelectedItem?.LiveDisplayHint ?? "当前项目无实时映射。";
public string NegativeAssistPressureDisplay => $"{ChannelValue(""):F1} kPa";
public string TemperatureDisplay => $"{ChannelValue(""):F1} °C";
public string FreeHemoglobinDisplay => $"{ChannelValue(""):F3} g/L";
@@ -155,57 +155,57 @@ public partial class MainViewModel : ObservableObject
partial void OnComplianceRateChanged(double value) => OnPropertyChanged(nameof(ComplianceDisplay));
partial void OnDeltaPressureChanged(double value) => OnPropertyChanged(nameof(DeltaPressureDisplay));
partial void OnExperimentCompletedChanged(bool value) => OnPropertyChanged(nameof(ExportStateText));
partial void OnRequirementSearchTextChanged(string value)
partial void OnDetectionCompletedChanged(bool value) => OnPropertyChanged(nameof(ExportStateText));
partial void OnItemSearchTextChanged(string value)
{
OnPropertyChanged(nameof(FilteredRequirements));
OnPropertyChanged(nameof(FilteredRequirementSummary));
OnPropertyChanged(nameof(HasFilteredRequirements));
OnPropertyChanged(nameof(HasRequirementSearchText));
OnPropertyChanged(nameof(FilteredItems));
OnPropertyChanged(nameof(FilteredItemSummary));
OnPropertyChanged(nameof(HasFilteredItems));
OnPropertyChanged(nameof(HasItemSearchText));
var filtered = FilteredRequirements.ToList();
var filtered = FilteredItems.ToList();
if (filtered.Count == 0)
{
return;
}
if (SelectedRequirement is null || !filtered.Contains(SelectedRequirement))
if (SelectedItem is null || !filtered.Contains(SelectedItem))
{
SelectedRequirement = filtered[0];
SelectedItem = filtered[0];
}
}
partial void OnSelectedRequirementChanged(TestRequirement? value)
partial void OnSelectedItemChanged(InspectionItem? value)
{
OnPropertyChanged(nameof(SelectedRequirementTitle));
OnPropertyChanged(nameof(SelectedRequirementStatusText));
OnPropertyChanged(nameof(SelectedRequirementCaptureModeText));
OnPropertyChanged(nameof(SelectedRequirementMeasurementSource));
OnPropertyChanged(nameof(SelectedRequirementUsesRealtimeValue));
OnPropertyChanged(nameof(SelectedItemTitle));
OnPropertyChanged(nameof(SelectedItemStatusText));
OnPropertyChanged(nameof(SelectedItemCaptureModeText));
OnPropertyChanged(nameof(SelectedItemMeasurementSource));
OnPropertyChanged(nameof(SelectedItemUsesRealtimeValue));
OnPropertyChanged(nameof(RealtimeMeasurementHint));
OnPropertyChanged(nameof(SelectedRequirementLiveDisplay));
OnPropertyChanged(nameof(SelectedRequirementLiveHint));
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
OnPropertyChanged(nameof(SelectedItemLiveHint));
if (value is not null)
{
LoadSelectedRequirementDraft(value);
LoadSelectedItemDraft(value);
}
}
[RelayCommand]
private void SelectRequirement(TestRequirement? requirement)
private void SelectItem(InspectionItem? item)
{
if (requirement is null)
if (item is null)
{
return;
}
SelectedRequirement = requirement;
SelectedItem = item;
}
[RelayCommand]
private void ClearRequirementSearch()
private void ClearItemSearch()
{
RequirementSearchText = string.Empty;
ItemSearchText = string.Empty;
}
[RelayCommand]
@@ -213,7 +213,7 @@ public partial class MainViewModel : ObservableObject
{
AcquisitionRunning = !AcquisitionRunning;
DeviceStatus = AcquisitionRunning ? "Modbus 在线" : "采集暂停";
LatestAction = AcquisitionRunning ? "继续采集实时工况,供试验员参考。" : "已暂停实时采集。";
LatestAction = AcquisitionRunning ? "继续采集实时数据,供检测参考。" : "已暂停实时采集。";
if (AcquisitionRunning)
{
@@ -228,95 +228,95 @@ public partial class MainViewModel : ObservableObject
}
[RelayCommand]
private void SelectPreviousRequirement()
private void SelectPreviousItem()
{
var scope = ActiveRequirementScope();
if (SelectedRequirement is null)
var scope = ActiveItemScope();
if (SelectedItem is null)
{
SelectedRequirement = scope.FirstOrDefault();
SelectedItem = scope.FirstOrDefault();
return;
}
var index = scope.IndexOf(SelectedRequirement);
var index = scope.IndexOf(SelectedItem);
if (index > 0)
{
SelectedRequirement = scope[index - 1];
SelectedItem = scope[index - 1];
}
}
[RelayCommand]
private void SelectNextRequirement()
private void SelectNextItem()
{
var scope = ActiveRequirementScope();
if (SelectedRequirement is null)
var scope = ActiveItemScope();
if (SelectedItem is null)
{
SelectedRequirement = scope.FirstOrDefault();
SelectedItem = scope.FirstOrDefault();
return;
}
var index = scope.IndexOf(SelectedRequirement);
var index = scope.IndexOf(SelectedItem);
if (index >= 0 && index < scope.Count - 1)
{
SelectedRequirement = scope[index + 1];
SelectedItem = scope[index + 1];
}
}
[RelayCommand]
private void ApplyManualResult()
private void ApplyResult()
{
if (SelectedRequirement is null)
if (SelectedItem is null)
{
LatestAction = "请先选择条款项。";
LatestAction = "请先选择项。";
return;
}
if (SelectedRequirementUsesRealtimeValue)
if (SelectedItemUsesRealtimeValue)
{
LatestAction = "当前条款已改为实时工况计算值,无需手动录入。";
LatestAction = "当前项目使用实时数据自动判定,无需手动填写。";
return;
}
if (string.IsNullOrWhiteSpace(ManualMeasuredValue))
if (string.IsNullOrWhiteSpace(ResultValue))
{
LatestAction = "请先输入测量结果或人工结论。";
LatestAction = "请先填写检测结果或判定结论。";
return;
}
SelectedRequirement.Measured = ManualMeasuredValue.Trim();
SelectedRequirement.Notes = ManualNote.Trim();
SelectedRequirement.RecordedBy = string.IsNullOrWhiteSpace(ManualOperator) ? OperatorName : ManualOperator.Trim();
SelectedRequirement.RecordedAt = DateTime.Now;
SelectedRequirement.Status = SelectedManualStatusText switch
SelectedItem.Measured = ResultValue.Trim();
SelectedItem.Notes = ResultNote.Trim();
SelectedItem.RecordedBy = string.IsNullOrWhiteSpace(ResultOperator) ? OperatorName : ResultOperator.Trim();
SelectedItem.RecordedAt = DateTime.Now;
SelectedItem.Status = SelectedResultStatusText switch
{
"待检" => RequirementStatus.Pending,
"合格" => RequirementStatus.Qualified,
"预警" => RequirementStatus.Warning,
"不合格" => RequirementStatus.Critical,
_ => RequirementStatus.Pending
"待检" => InspectionItemStatus.Pending,
"合格" => InspectionItemStatus.Qualified,
"预警" => InspectionItemStatus.Warning,
"不合格" => InspectionItemStatus.Critical,
_ => InspectionItemStatus.Pending
};
LatestAction = $"已记录 {SelectedRequirement.Item} 的人工测试结果。";
LatestAction = $"已填写 {SelectedItem.Item} 的检测结果。";
TraceEvents.Insert(0, new TraceEvent
{
Timestamp = DateTime.Now,
Stage = "人工录入",
Detail = $"{SelectedRequirement.Item}: {SelectedRequirement.Measured}",
Operator = SelectedRequirement.RecordedBy
Stage = "结果填写",
Detail = $"{SelectedItem.Item}: {SelectedItem.Measured}",
Operator = SelectedItem.RecordedBy
});
RefreshComputedState();
}
[RelayCommand]
private void CompleteExperiment()
private void CompleteDetection()
{
ExperimentCompleted = true;
CurrentStage = "实验结束";
DetectionCompleted = true;
CurrentStage = "检测完成";
AcquisitionRunning = false;
_timer.Stop();
DeviceStatus = "采集停止";
LatestAction = PendingCount == 0 ? "实验结束,全部条款已录入,可导出 PDF 报表。" : $"实验结束,但仍有 {PendingCount} 项待,请确认后导出 PDF 报表。";
TraceEvents.Insert(0, NewTrace("实验结束", "实验员结束本次检测任务"));
LatestAction = PendingCount == 0 ? "检测完成,全部项目已填写,可导出 PDF 报表。" : $"检测完成,但仍有 {PendingCount} 项待处理,请确认后导出 PDF 报表。";
TraceEvents.Insert(0, NewTrace("检测完成", "检测员结束本次检测任务"));
}
[RelayCommand]
@@ -339,8 +339,8 @@ public partial class MainViewModel : ObservableObject
{
var outputDirectory = AppContext.BaseDirectory;
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
var htmlPath = Path.Combine(outputDirectory, $"实验报表-{BatchNumber}-{timestamp}.html");
var pdfPath = Path.Combine(outputDirectory, $"实验报表-{BatchNumber}-{timestamp}.pdf");
var htmlPath = Path.Combine(outputDirectory, $"检测报表-{BatchNumber}-{timestamp}.html");
var pdfPath = Path.Combine(outputDirectory, $"检测报表-{BatchNumber}-{timestamp}.pdf");
File.WriteAllText(htmlPath, BuildHtmlReport(), Encoding.UTF8);
@@ -416,43 +416,45 @@ public partial class MainViewModel : ObservableObject
OnPropertyChanged(nameof(ReturnFlowNormalizedValue));
OnPropertyChanged(nameof(FlowSensorChannels));
OnPropertyChanged(nameof(OtherChannels));
OnPropertyChanged(nameof(SelectedRequirementLiveDisplay));
OnPropertyChanged(nameof(SelectedRequirementLiveHint));
OnPropertyChanged(nameof(SelectedItemLiveDisplay));
OnPropertyChanged(nameof(SelectedItemLiveHint));
SyncRealtimeRequirements();
SyncRealtimeItems();
}
private void RefreshComputedState()
{
QualifiedCount = Requirements.Count(r => r.Status == RequirementStatus.Qualified);
WarningCount = Requirements.Count(r => r.Status == RequirementStatus.Warning || r.Status == RequirementStatus.Critical);
PendingCount = Requirements.Count(r => r.Status == RequirementStatus.Pending);
ComplianceRate = Requirements.Count == 0 ? 0 : QualifiedCount * 100d / Requirements.Count;
QualifiedCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Qualified);
WarningCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Warning || r.Status == InspectionItemStatus.Critical);
PendingCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Pending);
ComplianceRate = InspectionItems.Count == 0 ? 0 : QualifiedCount * 100d / InspectionItems.Count;
}
private void LoadSelectedRequirementDraft(TestRequirement requirement)
private void LoadSelectedItemDraft(InspectionItem item)
{
ManualMeasuredValue = SelectedRequirementUsesRealtimeValue
? requirement.Measured
: requirement.Measured == "待检测" ? string.Empty : requirement.Measured;
ManualNote = requirement.Notes;
ManualOperator = string.IsNullOrWhiteSpace(requirement.RecordedBy) ? OperatorName : requirement.RecordedBy;
SelectedManualStatusText = requirement.Status switch
ResultValue = SelectedItemUsesRealtimeValue
? item.Measured
: item.Measured == "待检测" ? string.Empty : item.Measured;
ResultNote = item.Notes;
ResultOperator = string.IsNullOrWhiteSpace(item.RecordedBy) ? OperatorName : item.RecordedBy;
SelectedResultStatusText = item.Status switch
{
RequirementStatus.Warning => "预警",
RequirementStatus.Critical => "不合格",
RequirementStatus.Pending => "待检",
InspectionItemStatus.Warning => "预警",
InspectionItemStatus.Critical => "不合格",
InspectionItemStatus.Pending => "待检",
_ => "合格"
};
}
private string BuildHtmlReport()
{
var rows = string.Join(Environment.NewLine, Requirements.Select(r => $@"
var rows = string.Join(Environment.NewLine, InspectionItems.Select(r => $@"
<tr>
<td>{Html(r.Clause)}</td>
<td>{Html(r.Category)}</td>
<td>{Html(r.Item)}</td>
<td>{Html(r.AcceptanceCriteria)}</td>
<td>{Html(r.TestMethod)}</td>
<td>{Html(r.RecordFocus)}</td>
<td>{Html(r.Measured)}</td>
<td>{Html(r.StatusText)}</td>
<td>{Html(r.RecordedBy)}</td>
@@ -493,10 +495,10 @@ ul {{ padding-left: 20px; }}
<div><strong></strong>{Html(ComplianceDisplay)}</div>
<div><strong></strong>{Html(DeltaPressureDisplay)}</div>
</div>
<div class='card'><strong></strong>{Html(ExperimentSummary)}</div>
<h2></h2>
<div class='card'><strong></strong>{Html(DetectionSummary)}</div>
<h2></h2>
<table>
<thead><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead>
<thead><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead>
<tbody>{rows}</tbody>
</table>
<h2></h2>
@@ -537,72 +539,72 @@ ul {{ padding-left: 20px; }}
Timestamp = DateTime.Now,
Stage = stage,
Detail = detail,
Operator = string.IsNullOrWhiteSpace(ManualOperator) ? OperatorName : ManualOperator
Operator = string.IsNullOrWhiteSpace(ResultOperator) ? OperatorName : ResultOperator
};
private void SyncRealtimeRequirements()
private void SyncRealtimeItems()
{
var pressureRequirement = Requirements.FirstOrDefault(requirement => requirement.Clause == "4.3.1");
if (pressureRequirement is not null)
var pressureItem = InspectionItems.FirstOrDefault(item => item.Clause == "4.3.1");
if (pressureItem is not null)
{
var pressureStatus = DeltaPressure switch
{
<= 20 => RequirementStatus.Qualified,
<= 24 => RequirementStatus.Warning,
_ => RequirementStatus.Critical
<= 20 => InspectionItemStatus.Qualified,
<= 24 => InspectionItemStatus.Warning,
_ => InspectionItemStatus.Critical
};
pressureRequirement.Measured = $"实时压差 {DeltaPressure:F1} mmHg";
pressureRequirement.Notes =
pressureItem.Measured = $"实时压差 {DeltaPressure:F1} mmHg";
pressureItem.Notes =
$"入口 {ChannelValue(""):F1} mmHg出口 {ChannelValue(""):F1} mmHg按额定流量点连续监控。";
pressureRequirement.RecordedBy = "实时工况";
pressureRequirement.RecordedAt = DateTime.Now;
pressureRequirement.Status = pressureStatus;
pressureItem.RecordedBy = "实时数据";
pressureItem.RecordedAt = DateTime.Now;
pressureItem.Status = pressureStatus;
}
var recirculationRequirement = Requirements.FirstOrDefault(requirement => requirement.Clause == "4.3.3");
if (recirculationRequirement is null)
var recirculationItem = InspectionItems.FirstOrDefault(item => item.Clause == "4.3.3");
if (recirculationItem is null)
{
return;
}
var status = RecirculationRate switch
{
<= 8 => RequirementStatus.Qualified,
<= 12 => RequirementStatus.Warning,
_ => RequirementStatus.Critical
<= 8 => InspectionItemStatus.Qualified,
<= 12 => InspectionItemStatus.Warning,
_ => InspectionItemStatus.Critical
};
recirculationRequirement.Measured =
recirculationItem.Measured =
$"实时计算值 {RecirculationRate:F1}% (回输 {ReturnFlow:F2} / 引流 {DrainageFlow:F2} L/min)";
recirculationRequirement.Notes =
recirculationItem.Notes =
$"由 3 路流量传感器实时计算;主泵 {PumpFlow:F2} L/min流量差 {Math.Abs(PumpFlow - ReturnFlow):F2} L/min。";
recirculationRequirement.RecordedBy = "实时工况";
recirculationRequirement.RecordedAt = DateTime.Now;
recirculationRequirement.Status = status;
recirculationItem.RecordedBy = "实时数据";
recirculationItem.RecordedAt = DateTime.Now;
recirculationItem.Status = status;
if (SelectedRequirement == recirculationRequirement)
if (SelectedItem == recirculationItem)
{
ManualMeasuredValue = recirculationRequirement.Measured;
ManualNote = recirculationRequirement.Notes;
SelectedManualStatusText = recirculationRequirement.Status switch
ResultValue = recirculationItem.Measured;
ResultNote = recirculationItem.Notes;
SelectedResultStatusText = recirculationItem.Status switch
{
RequirementStatus.Pending => "待检",
RequirementStatus.Warning => "预警",
RequirementStatus.Critical => "不合格",
InspectionItemStatus.Pending => "待检",
InspectionItemStatus.Warning => "预警",
InspectionItemStatus.Critical => "不合格",
_ => "合格"
};
}
if (pressureRequirement is not null && SelectedRequirement == pressureRequirement)
if (pressureItem is not null && SelectedItem == pressureItem)
{
ManualMeasuredValue = pressureRequirement.Measured;
ManualNote = pressureRequirement.Notes;
SelectedManualStatusText = pressureRequirement.Status switch
ResultValue = pressureItem.Measured;
ResultNote = pressureItem.Notes;
SelectedResultStatusText = pressureItem.Status switch
{
RequirementStatus.Pending => "待检",
RequirementStatus.Warning => "预警",
RequirementStatus.Critical => "不合格",
InspectionItemStatus.Pending => "待检",
InspectionItemStatus.Warning => "预警",
InspectionItemStatus.Critical => "不合格",
_ => "合格"
};
}
@@ -610,21 +612,21 @@ ul {{ padding-left: 20px; }}
private double ChannelValue(string name) => Channels.First(channel => channel.Name == name).Value;
private double ChannelNormalizedValue(string name) => Channels.First(channel => channel.Name == name).NormalizedValue;
private List<TestRequirement> ActiveRequirementScope() => FilteredRequirements.ToList();
private List<InspectionItem> ActiveItemScope() => FilteredItems.ToList();
private bool MatchesRequirementSearch(TestRequirement requirement)
private bool MatchesItemSearch(InspectionItem item)
{
if (string.IsNullOrWhiteSpace(RequirementSearchText))
if (string.IsNullOrWhiteSpace(ItemSearchText))
{
return true;
}
var keyword = RequirementSearchText.Trim();
return MatchesKeyword(requirement.Clause, keyword)
|| MatchesKeyword(requirement.Item, keyword)
|| MatchesKeyword(requirement.Category, keyword)
|| MatchesKeyword(requirement.Requirement, keyword)
|| MatchesKeyword(requirement.Method, keyword);
var keyword = ItemSearchText.Trim();
return MatchesKeyword(item.Clause, keyword)
|| MatchesKeyword(item.Item, keyword)
|| MatchesKeyword(item.Category, keyword)
|| MatchesKeyword(item.AcceptanceCriteria, keyword)
|| MatchesKeyword(item.TestMethod, keyword);
}
private static bool MatchesKeyword(string source, string keyword)
@@ -653,21 +655,21 @@ ul {{ padding-left: 20px; }}
private static bool IsFlowSensorChannel(DeviceChannel channel) =>
channel.Name is "主泵流量" or "静脉引流流量" or "动脉回输流量";
private string BuildSelectedRequirementLiveDisplay()
private string BuildSelectedItemLiveDisplay()
{
if (SelectedRequirement is null)
if (SelectedItem is null)
{
return "未选择条款";
return "未选择项目";
}
return (SelectedRequirement.Clause, SelectedRequirement.Item) switch
return (SelectedItem.Clause, SelectedItem.Item) switch
{
("4.3.1", _) => $"入口 {ChannelValue(""):F1} mmHg / 出口 {ChannelValue(""):F1} mmHg / 压差 {DeltaPressure:F1} mmHg",
("4.3.2", _) => $"负压 {ChannelValue(""):F1} kPa / 压差 {DeltaPressure:F1} mmHg / 流量差 {Math.Abs(PumpFlow - ReturnFlow):F2} L/min",
("4.3.3", _) => $"再循环率 {RecirculationRate:F1}% / 引流 {DrainageFlow:F2} / 回输 {ReturnFlow:F2} L/min",
("4.3.4", "血细胞破坏") => $"游离血红蛋白 {ChannelValue(""):F3} g/L / 温度 {ChannelValue(""):F1} °C",
("4.3.4", "血小板/白细胞减少率") => $"白细胞减少率 {ChannelValue(""):F1}% / 温度 {ChannelValue(""):F1} °C",
_ => "当前条款无实时信号,按试验原始记录手动录入。"
_ => "当前项目无实时信号,按检测原始记录手动填写。"
};
}
}