初始化

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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Cardiopulmonarybypasssystems.Models" xmlns:models="clr-namespace:Cardiopulmonarybypasssystems.Models"
mc:Ignorable="d" mc:Ignorable="d"
Title="心肺转流检验" Title="心肺转流检测"
Width="1024" Width="1024"
Height="768" Height="768"
MinWidth="960" MinWidth="960"
@@ -27,13 +27,13 @@
<StackPanel> <StackPanel>
<TextBlock Text="{Binding PageTitle}" FontSize="28" FontWeight="Bold" Foreground="White" TextWrapping="Wrap" /> <TextBlock Text="{Binding PageTitle}" FontSize="28" FontWeight="Bold" Foreground="White" TextWrapping="Wrap" />
<TextBlock Margin="0,8,0,0" Foreground="#EFFAFC" FontSize="15" 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>
<StackPanel Grid.Row="1" Margin="0,12,0,0"> <StackPanel Grid.Row="1" Margin="0,12,0,0">
<WrapPanel> <WrapPanel>
<Button Command="{Binding ToggleAcquisitionCommand}" Content="&#x91C7;&#x96C6;" Background="#FFFFFFFF" Foreground="{StaticResource HeaderBrush}" /> <Button Command="{Binding ToggleAcquisitionCommand}" Content="&#x91C7;&#x96C6;" Background="#FFFFFFFF" Foreground="{StaticResource HeaderBrush}" />
<Button Command="{Binding AcknowledgeAlarmCommand}" Content="&#x6D88;&#x8B66;" Background="#33FFFFFF" /> <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" /> <Button Command="{Binding ExportReportCommand}" Content="&#x5BFC;&#x51FA; PDF" Background="#FFEA7E3C" />
</WrapPanel> </WrapPanel>
<TextBlock Margin="0,2,0,0" Foreground="#EFFAFC" FontSize="14" Text="{Binding ExportStateText}" TextWrapping="Wrap" /> <TextBlock Margin="0,2,0,0" Foreground="#EFFAFC" FontSize="14" Text="{Binding ExportStateText}" TextWrapping="Wrap" />
@@ -53,7 +53,7 @@
<StackPanel> <StackPanel>
<Border Style="{StaticResource CardBorderStyle}" Margin="0,0,0,12"> <Border Style="{StaticResource CardBorderStyle}" Margin="0,0,0,12">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x5B9E;&#x9A8C;&#x603B;&#x89C8;" /> <TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x68C0;&#x6D4B;&#x603B;&#x89C8;" />
<UniformGrid Columns="1"> <UniformGrid Columns="1">
<Border Margin="0,0,0,8" Padding="14" Background="#FFE7F5F3" CornerRadius="14"> <Border Margin="0,0,0,8" Padding="14" Background="#FFE7F5F3" CornerRadius="14">
<StackPanel> <StackPanel>
@@ -111,7 +111,7 @@
<Border Grid.Column="2" Style="{StaticResource CardBorderStyle}" Margin="0"> <Border Grid.Column="2" Style="{StaticResource CardBorderStyle}" Margin="0">
<TabControl> <TabControl>
<TabItem Header="&#x68C0;&#x9A8C;&#x5355;"> <TabItem Header="&#x9879;&#x76EE;&#x68C0;&#x6D4B;">
<ScrollViewer Margin="0,6,0,0" VerticalScrollBarVisibility="Auto"> <ScrollViewer Margin="0,6,0,0" VerticalScrollBarVisibility="Auto">
<StackPanel> <StackPanel>
<Border Style="{StaticResource CardBorderStyle}"> <Border Style="{StaticResource CardBorderStyle}">
@@ -123,16 +123,27 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="0" MinHeight="40" Text="{Binding RequirementSearchText, UpdateSourceTrigger=PropertyChanged}" /> <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 ClearRequirementSearchCommand}" Content="&#x6E05;&#x7A7A;" Background="#FF6B8791" /> <Button Grid.Column="1" Margin="8,0,0,0" MinWidth="90" Padding="14,8" Command="{Binding ClearItemSearchCommand}" Content="&#x6E05;&#x7A7A;" Background="#FF6B8791" />
</Grid> </Grid>
<TextBlock Margin="0,8,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding FilteredRequirementSummary}" /> <Grid Margin="0,8,0,0">
<TextBlock Margin="0,8,0,0" FontSize="14" Foreground="{StaticResource WarningBrush}" Text="&#x672A;&#x5339;&#x914D;&#x5230;&#x6D4B;&#x8BD5;&#x9879;&#x3002;"> <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> <TextBlock.Style>
<Style TargetType="TextBlock"> <Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" /> <Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding HasFilteredRequirements}" Value="False"> <DataTrigger Binding="{Binding HasFilteredItems}" Value="False">
<Setter Property="Visibility" Value="Visible" /> <Setter Property="Visibility" Value="Visible" />
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>
@@ -169,8 +180,8 @@
<Border Style="{StaticResource CardBorderStyle}"> <Border Style="{StaticResource CardBorderStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x9879;&#x76EE;&#x6E05;&#x5355;" /> <TextBlock Style="{StaticResource SectionTitleStyle}" Text="&#x9879;&#x76EE;&#x6E05;&#x5355;" />
<DataGrid ItemsSource="{Binding FilteredRequirements}" <DataGrid ItemsSource="{Binding FilteredItems}"
SelectedItem="{Binding SelectedRequirement, Mode=TwoWay}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
CanUserSortColumns="False" CanUserSortColumns="False"
IsReadOnly="True" IsReadOnly="True"
CanUserAddRows="False" CanUserAddRows="False"
@@ -180,8 +191,26 @@
MinHeight="240"> MinHeight="240">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="&#x9879;&#x76EE;" Binding="{Binding Item}" Width="180" /> <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" /> <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="*" /> <DataGridTextColumn Header="&#x7ED3;&#x679C;/&#x8BF4;&#x660E;" Binding="{Binding Measured}" Width="*" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
@@ -191,33 +220,34 @@
<UniformGrid Columns="4" Margin="0,0,0,12"> <UniformGrid Columns="4" Margin="0,0,0,12">
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}"> <Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F53;&#x524D;&#x6D4B;&#x8BD5;&#x9879;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F53;&#x524D;&#x9879;&#x76EE;" />
<TextBlock FontSize="18" FontWeight="Bold" Text="{Binding SelectedRequirementTitle}" TextWrapping="Wrap" /> <TextBlock FontSize="18" FontWeight="Bold" Text="{Binding SelectedItemTitle}" TextWrapping="Wrap" />
</StackPanel> </StackPanel>
</Border> </Border>
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}"> <Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x91C7;&#x96C6;&#x65B9;&#x5F0F;" /> <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> </StackPanel>
</Border> </Border>
<Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}"> <Border Margin="0,0,8,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x72B6;&#x6001;" /> <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> </StackPanel>
</Border> </Border>
<Border Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}"> <Border Margin="0,0,0,8" Style="{StaticResource PanelSectionStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8BB0;&#x5F55;&#x65F6;&#x95F4;" /> <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> </StackPanel>
</Border> </Border>
</UniformGrid> </UniformGrid>
<Border Style="{StaticResource CardBorderStyle}"> <Border Style="{StaticResource CardBorderStyle}">
<StackPanel> <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>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="1.12*" /> <ColumnDefinition Width="1.12*" />
@@ -226,43 +256,43 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x7C7B;&#x522B;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x7C7B;&#x522B;" />
<TextBlock FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Text="{Binding SelectedRequirement.Category}" /> <TextBlock FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Text="{Binding SelectedItem.Category}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8981;&#x6C42;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5224;&#x5B9A;&#x8981;&#x6C42;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirement.Requirement}" /> <TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItem.AcceptanceCriteria}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x65B9;&#x6CD5;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x68C0;&#x6D4B;&#x65B9;&#x6CD5;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirement.Method}" /> <TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItem.TestMethod}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x63A7;&#x5236;&#x70B9;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x8BB0;&#x5F55;&#x8981;&#x70B9;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirement.ControlPoint}" /> <TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItem.RecordFocus}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x6570;&#x636E;&#x6765;&#x6E90;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x6570;&#x636E;&#x6765;&#x6E90;" />
<TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedRequirementMeasurementSource}" /> <TextBlock FontSize="15" Margin="0,0,0,10" TextWrapping="Wrap" Text="{Binding SelectedItemMeasurementSource}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5B9E;&#x65F6;&#x9884;&#x89C8;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5B9E;&#x65F6;&#x6570;&#x636E;&#x9884;&#x89C8;" />
<Border Padding="12" Background="#FFF4F8FA" CornerRadius="14" Margin="0,0,0,10"> <Border Padding="12" Background="#FFF4F8FA" CornerRadius="14" Margin="0,0,0,10">
<StackPanel> <StackPanel>
<TextBlock FontSize="16" FontWeight="SemiBold" Text="{Binding SelectedRequirementLiveDisplay}" TextWrapping="Wrap" /> <TextBlock FontSize="16" FontWeight="SemiBold" Text="{Binding SelectedItemLiveDisplay}" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedRequirementLiveHint}" TextWrapping="Wrap" /> <TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedItemLiveHint}" TextWrapping="Wrap" />
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2"> <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" /> <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;" /> <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;" /> <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;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x590D;&#x6838;&#x4EBA;" />
<TextBox Text="{Binding ReviewerName, UpdateSourceTrigger=PropertyChanged}" /> <TextBox Text="{Binding ReviewerName, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x6279;&#x51C6;&#x4EBA;" /> <TextBlock Style="{StaticResource CaptionStyle}" Text="&#x6279;&#x51C6;&#x4EBA;" />
<TextBox Text="{Binding ApproverName, UpdateSourceTrigger=PropertyChanged}" /> <TextBox Text="{Binding ApproverName, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5907;&#x6CE8;" /> <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> </StackPanel>
</Grid> </Grid>
<WrapPanel Margin="0,8,0,0"> <WrapPanel Margin="0,8,0,0">
<Button Command="{Binding SelectPreviousRequirementCommand}" Content="&#x4E0A;&#x4E00;&#x9879;" Background="#FF6B8791" /> <Button Command="{Binding SelectPreviousItemCommand}" Content="&#x4E0A;&#x4E00;&#x9879;" Background="#FF6B8791" />
<Button Command="{Binding SelectNextRequirementCommand}" Content="&#x4E0B;&#x4E00;&#x9879;" Background="#FF6B8791" /> <Button Command="{Binding SelectNextItemCommand}" Content="&#x4E0B;&#x4E00;&#x9879;" Background="#FF6B8791" />
<Button Command="{Binding ApplyManualResultCommand}" Content="&#x4FDD;&#x5B58;" /> <Button Command="{Binding ApplyResultCommand}" Content="&#x4FDD;&#x5B58;" />
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Border> </Border>
@@ -270,7 +300,7 @@
</ScrollViewer> </ScrollViewer>
</TabItem> </TabItem>
<TabItem Header="&#x5B9E;&#x65F6;&#x5DE5;&#x51B5;"> <TabItem Header="&#x5B9E;&#x65F6;&#x6570;&#x636E;">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,6,0,0"> <StackPanel Margin="0,6,0,0">
<UniformGrid Columns="4" Margin="0,0,0,12"> <UniformGrid Columns="4" Margin="0,0,0,12">
@@ -331,7 +361,7 @@
<Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}"> <Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5165;&#x53E3;/&#x51FA;&#x53E3;&#x538B;&#x529B;" /> <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> </StackPanel>
</Border> </Border>
<Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}"> <Border Margin="0,0,8,0" Style="{StaticResource PanelSectionStyle}">
@@ -343,8 +373,8 @@
<Border Style="{StaticResource PanelSectionStyle}"> <Border Style="{StaticResource PanelSectionStyle}">
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource CaptionStyle}" Text="&#x5F53;&#x524D;&#x9009;&#x4E2D;&#x6761;&#x6B3E;&#x7B56;&#x7565;" /> <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 Style="{StaticResource MetricValueStyle}" FontSize="20" Text="{Binding SelectedItemCaptureModeText}" TextWrapping="Wrap" />
<TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedRequirementMeasurementSource}" TextWrapping="Wrap" /> <TextBlock Margin="0,6,0,0" Style="{StaticResource CaptionStyle}" Text="{Binding SelectedItemMeasurementSource}" TextWrapping="Wrap" />
</StackPanel> </StackPanel>
</Border> </Border>
</UniformGrid> </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; namespace Cardiopulmonarybypasssystems.Models;
public enum RequirementCaptureMode public enum InspectionItemCaptureMode
{ {
ManualEntry, ManualEntry,
RealtimeAssist, RealtimeAssist,

View File

@@ -1,6 +1,6 @@
namespace Cardiopulmonarybypasssystems.Models; namespace Cardiopulmonarybypasssystems.Models;
public enum RequirementStatus public enum InspectionItemStatus
{ {
Pending, Pending,
Qualified, 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 public interface IStandardRepository
{ {
IReadOnlyList<TestRequirement> GetRequirements(); IReadOnlyList<InspectionItem> GetInspectionItems();
IReadOnlyList<TraceEvent> GetSeedTraceEvents(); IReadOnlyList<TraceEvent> GetSeedTraceEvents();
} }

View File

@@ -4,31 +4,31 @@ namespace Cardiopulmonarybypasssystems.Services;
public sealed class StandardRepository : IStandardRepository 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.1", Category = "生物性能", Item = "无菌", AcceptanceCriteria = "应无菌", TestMethod = "进行无菌培养观察;判定无菌生长", RecordFocus = "记录无菌培养结果" },
new() { Clause = "4.1.2", Category = "生物性能", Item = "细菌内毒素", Requirement = "内毒素不超过 20 EU/件", Method = "提取样液后进行细菌内毒素检测,确认每件结果不超过限值", ControlPoint = "内毒素检测记录" }, new() { Clause = "4.1.2", Category = "生物性能", Item = "细菌内毒素", AcceptanceCriteria = "每件内毒素不超过 20 EU", TestMethod = "提取液进行内毒素检测;判定结果不超过 20 EU/件。", RecordFocus = "记录内毒素检测" },
new() { Clause = "4.1.3", Category = "生物性能", Item = "生物相容性", Requirement = "符合预期用途生物学评价", Method = "结合预期用途完成生物学评价并审查评价结论", ControlPoint = "生物学评价记录" }, new() { Clause = "4.1.3", Category = "生物性能", Item = "生物相容性", AcceptanceCriteria = "生物学评价应符合预期用途。", TestMethod = "预期用途完成生物学评价;判定评价结论符合使用要求。", RecordFocus = "记录生物学评价结论" },
new() { Clause = "4.2.1", Category = "物理性能", Item = "血液通道密合性", Requirement = "血液通道应泄漏", Method = "封闭血液通道并加压至最大工作压力 1.5 倍,保压观察是否泄漏", ControlPoint = "人工保压观察" }, new() { Clause = "4.2.1", Category = "物理性能", Item = "血液通道密合性", AcceptanceCriteria = "血液通道应泄漏", TestMethod = "封闭通道并加压保压观察;判定血液通道无泄漏", RecordFocus = "观察并记录保压结果" },
new() { Clause = "4.2.2", Category = "物理性能", Item = "接头", Requirement = "连接牢固,辅助接头同样满足要求", Method = "对接头和辅助接头施加规定拉力,检查是否松动或脱开", ControlPoint = "人工拉脱力记录" }, new() { Clause = "4.2.2", Category = "物理性能", Item = "接头", AcceptanceCriteria = "各接头连接牢固。", TestMethod = "对接头施加规定拉力;判定连接不松动、不脱开", RecordFocus = "记录接头拉脱结果" },
new() { Clause = "4.2.3", Category = "物理性能", Item = "抗扭结抗性", Requirement = "流量降低不超过 50%", Method = "将插管弯折至规定状态,比较弯折前后流量变化", ControlPoint = "人工填写弯折流量" }, new() { Clause = "4.2.3", Category = "物理性能", Item = "抗扭结抗性", AcceptanceCriteria = "弯折后流量降低不超过 50%", TestMethod = "将插管弯折后测量流量变化;判定流量降低不超过 50%。", RecordFocus = "填写弯折前后流量" },
new() { Clause = "4.2.4", Category = "物理性能", Item = "抗拉强度", Requirement = "承受持续最小拉力且不分离", Method = "对连接部位持续施加预期使用力 1.5 倍拉力,观察是否分离", ControlPoint = "拉力试验记录" }, new() { Clause = "4.2.4", Category = "物理性能", Item = "抗拉强度", AcceptanceCriteria = "受力后部件不应分离", TestMethod = "对连接部位持续施加拉力;判定部件不分离", RecordFocus = "记录拉力试验结果" },
new() { Clause = "4.2.5", Category = "物理性能", Item = "外观", Requirement = "无毛刺、裂纹、异物、色斑,远端无锐角", Method = "在正常照明下目视检查管体表面、接口和远端外观", ControlPoint = "人工外观复核" }, new() { Clause = "4.2.5", Category = "物理性能", Item = "外观", AcceptanceCriteria = "外观应完整、洁净、无明显缺陷。", TestMethod = "在正常照明下目视检查外观;判定无毛刺、裂纹、异物和锐角。", RecordFocus = "核对并记录外观结果" },
new() { Clause = "4.2.6", Category = "物理性能", Item = "完整性", Requirement = "模拟使用后无腐蚀、磨损、塌陷", Method = "完成模拟使用后,对管体和组件进行放大检查", ControlPoint = "使用后检查" }, new() { Clause = "4.2.6", Category = "物理性能", Item = "完整性", AcceptanceCriteria = "模拟使用后应保持完整。", TestMethod = "模拟使用后检查管体和组件;判定无腐蚀、磨损和塌陷。", RecordFocus = "记录使用后检查结果" },
new() { Clause = "4.2.7", Category = "物理性能", Item = "射线可探测性", Requirement = "需具备可探测性时应满足要求", Method = "在影像条件下观察插管显影情况和可识别", ControlPoint = "影像记录" }, new() { Clause = "4.2.7", Category = "物理性能", Item = "射线可探测性", AcceptanceCriteria = "需要时应具备射线可探测性。", TestMethod = "在影像条件下观察显影情况;判定插管可被清晰识别", RecordFocus = "记录影像观察结果" },
new() { Clause = "4.2.8", Category = "物理性能", Item = "距离标记", Requirement = "标记应指示至远端距离,间隔不超过 10 mm 或风评值", Method = "目视检查距离标记位置间距和清晰度", ControlPoint = "人工核对标记" }, new() { Clause = "4.2.8", Category = "物理性能", Item = "距离标记", AcceptanceCriteria = "距离标记应清晰且间隔符合要求。", TestMethod = "检查距离标记位置间距;判定标记清晰且间隔符合要求。", RecordFocus = "核对距离标记" },
new() { Clause = "4.2.9", Category = "物理性能", Item = "管腔标记", Requirement = "端口和血流方向可肉眼识别", Method = "目视检查端口标识和血流方向标识是否清晰可辨", ControlPoint = "人工核对流向" }, new() { Clause = "4.2.9", Category = "物理性能", Item = "管腔标记", AcceptanceCriteria = "端口和流向标识应清晰可辨。", TestMethod = "检查端口和流向标识;判定标识肉眼清晰可辨", RecordFocus = "核对端口和流向标识" },
new() new()
{ {
Clause = "4.3.1", Clause = "4.3.1",
Category = "性能特征", Category = "性能特征",
Item = "压力降", Item = "压力降",
Requirement = "应在制造商声明范围内", AcceptanceCriteria = "压力降应在声明范围内",
Method = "在额定流量点同步测量入口出口压力,计算压差", TestMethod = "在额定流量点测量入口出口压力;判定压差在声明范围内。",
ControlPoint = "入口/出口压差", RecordFocus = "实时监控压差",
CaptureMode = RequirementCaptureMode.RealtimeMonitor, CaptureMode = InspectionItemCaptureMode.RealtimeMonitor,
MeasurementSource = "入口/出口压力传感器", MeasurementSource = "入口/出口压力传感器",
ManualEntryHint = "该条款由实时压力信号自动采集,无需人工重复录入。", ManualEntryHint = "该项目由实时压力信号自动采集,无需人工重复填写。",
LiveDisplayHint = "实时显示入口压力、出口压力和压差,用于直接判定压力降。" LiveDisplayHint = "实时显示入口压力、出口压力和压差,用于直接判定压力降。"
}, },
new() new()
@@ -36,12 +36,12 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.2", Clause = "4.3.2",
Category = "性能特征", Category = "性能特征",
Item = "抗塌陷", Item = "抗塌陷",
Requirement = "引流管负压后压降增量不超过 40%", AcceptanceCriteria = "负压后压降增量不超过 40%",
Method = "施加负压后比较压差增量和流量变化,判断是否塌陷", TestMethod = "施加负压后比较压差增量和流量变化;判定压降增量不超过限值。",
ControlPoint = "负压后人工判定", RecordFocus = "记录负压与压差变化",
CaptureMode = RequirementCaptureMode.RealtimeAssist, CaptureMode = InspectionItemCaptureMode.RealtimeAssist,
MeasurementSource = "负压辅助引流、入口/出口压差趋势", MeasurementSource = "负压辅助引流、入口/出口压差趋势",
ManualEntryHint = "系统实时显示负压与压差趋势,最终结论仍由检验员按试验工况手动录入。", ManualEntryHint = "系统实时显示负压与压差趋势,最终结论仍由检测员根据检测记录手动填写。",
LiveDisplayHint = "建议结合负压、压差增量和流量变化综合判定是否塌陷。" LiveDisplayHint = "建议结合负压、压差增量和流量变化综合判定是否塌陷。"
}, },
new() new()
@@ -49,12 +49,12 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.3", Clause = "4.3.3",
Category = "性能特征", Category = "性能特征",
Item = "再循环", Item = "再循环",
Requirement = "再循环百分比在声明范围内", AcceptanceCriteria = "再循环比例应在声明范围内",
Method = "在双腔回路模型中测量引流回输流量,计算再循环比例", TestMethod = "在双腔回路中测量引流回输流量;判定再循环比例在声明范围内。",
ControlPoint = "三路流量实时计算", RecordFocus = "实时监控再循环率",
CaptureMode = RequirementCaptureMode.RealtimeMonitor, CaptureMode = InspectionItemCaptureMode.RealtimeMonitor,
MeasurementSource = "主泵 / 静脉引流 / 动脉回输流量传感器", MeasurementSource = "主泵 / 静脉引流 / 动脉回输流量传感器",
ManualEntryHint = "该条款由三路流量实时计算,无需人工重复录入。", ManualEntryHint = "该项目由三路流量实时计算,无需人工重复填写。",
LiveDisplayHint = "实时显示主泵、引流、回输流量及再循环率。" LiveDisplayHint = "实时显示主泵、引流、回输流量及再循环率。"
}, },
new() new()
@@ -62,12 +62,12 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.4", Clause = "4.3.4",
Category = "性能特征", Category = "性能特征",
Item = "血细胞破坏", Item = "血细胞破坏",
Requirement = "游离血红蛋白增量在声明范围内", AcceptanceCriteria = "游离血红蛋白增量在声明范围内",
Method = "进行体外循环试验检测循环前后游离血红蛋白增量", TestMethod = "进行体外循环试验检测游离血红蛋白;判定增量在声明范围内。",
ControlPoint = "人工填写 fHb", RecordFocus = "填写游离血红蛋白结果",
CaptureMode = RequirementCaptureMode.RealtimeAssist, CaptureMode = InspectionItemCaptureMode.RealtimeAssist,
MeasurementSource = "游离血红蛋白在线趋势 + 实验室结果", MeasurementSource = "游离血红蛋白在线趋势 + 实验室结果",
ManualEntryHint = "系统仅实时显示趋势,最终结果应以验室检测值手动录入。", ManualEntryHint = "系统仅实时显示趋势,最终结果应以验室检测值手动填写。",
LiveDisplayHint = "关注游离血红蛋白上升趋势,避免仅用在线值替代正式记录。" LiveDisplayHint = "关注游离血红蛋白上升趋势,避免仅用在线值替代正式记录。"
}, },
new() new()
@@ -75,29 +75,29 @@ public sealed class StandardRepository : IStandardRepository
Clause = "4.3.4", Clause = "4.3.4",
Category = "性能特征", Category = "性能特征",
Item = "血小板/白细胞减少率", Item = "血小板/白细胞减少率",
Requirement = "应在声明范围内", AcceptanceCriteria = "血细胞减少率应在声明范围内",
Method = "进行体外循环试验比较循环前后血小板和白细胞计数变化", TestMethod = "进行体外循环试验比较血细胞计数;判定减少率在声明范围内。",
ControlPoint = "人工填写血常规", RecordFocus = "填写血细胞计数结果",
CaptureMode = RequirementCaptureMode.RealtimeAssist, CaptureMode = InspectionItemCaptureMode.RealtimeAssist,
MeasurementSource = "白细胞减少率趋势 + 血常规结果", MeasurementSource = "白细胞减少率趋势 + 血常规结果",
ManualEntryHint = "系统用于实时显示趋势,最终血常规结果仍需人工录入。", ManualEntryHint = "系统用于实时显示趋势,最终血常规结果仍需人工填写。",
LiveDisplayHint = "实时观察白细胞减少率趋势,最终以检报告为准。" LiveDisplayHint = "实时观察白细胞减少率趋势,最终以检报告为准。"
}, },
new() { Clause = "4.3.5", Category = "性能特征", Item = "有效期", Requirement = "有效期内应符合标准", Method = "对实时或加速老化样品复测关键性能,评价有效期内是否仍满足要求", ControlPoint = "老化记录" }, new() { Clause = "4.3.5", Category = "性能特征", Item = "有效期", AcceptanceCriteria = "有效期内应持续符合要求。", TestMethod = "对老化样品复测关键性能;判定有效期内仍符合要求", RecordFocus = "记录老化复测结果" },
new() { Clause = "4.4.1", Category = "化学性能", Item = "还原物质", Requirement = "高锰酸钾消耗差值 < 2.0 mL", Method = "制备检验液后比较高锰酸钾消耗量与空白液差值", ControlPoint = "人工填写化学结果" }, new() { Clause = "4.4.1", Category = "化学性能", Item = "还原物质", AcceptanceCriteria = "高锰酸钾消耗差值小于 2.0 mL", TestMethod = "测定检验液与空白液的高锰酸钾消耗差值;判定差值小于 2.0 mL。", RecordFocus = "填写还原物质结果" },
new() { Clause = "4.4.2", Category = "化学性能", Item = "金属离子", Requirement = "Ba、Cr、Cu、Pb 总量符合限值", Method = "制备浸提液后测定钡、铬、铜、铅总量", ControlPoint = "人工填写金属离子" }, new() { Clause = "4.4.2", Category = "化学性能", Item = "金属离子", AcceptanceCriteria = "金属离子总量符合限值", TestMethod = "测定浸提液中金属离子总量;判定结果符合限值要求。", RecordFocus = "填写金属离子结果" },
new() { Clause = "4.4.3", Category = "化学性能", Item = "酸碱度", Requirement = "检验液与空白差值不超过 4.0", Method = "测量检验液与空白液的 pH 差值", ControlPoint = "人工填写 pH" }, new() { Clause = "4.4.3", Category = "化学性能", Item = "酸碱度", AcceptanceCriteria = "检验液与空白液 pH 差值不超过 4.0", TestMethod = "测量检验液与空白液的 pH 差值;判定差值不超过 4.0。", RecordFocus = "填写 pH 差值" },
new() { Clause = "4.4.4", Category = "化学性能", Item = "蒸发残渣", Requirement = "不超过规定值", Method = "蒸干检验液并称量残渣,确认结果不超过限值", ControlPoint = "人工填写残渣" }, new() { Clause = "4.4.4", Category = "化学性能", Item = "蒸发残渣", AcceptanceCriteria = "蒸发残渣量不超过规定值", TestMethod = "蒸干检验液并称量残渣;判定残渣量不超过规定值。", RecordFocus = "填写残渣称量结果" },
new() { Clause = "4.4.5", Category = "化学性能", Item = "紫外吸光度", Requirement = "应小于 0.1", Method = "测量检验液在规定波长下的紫外吸光度", ControlPoint = "人工填写 UV" }, new() { Clause = "4.4.5", Category = "化学性能", Item = "紫外吸光度", AcceptanceCriteria = "紫外吸光度应小于 0.1", TestMethod = "测量检验液的紫外吸光度;判定吸光度小于 0.1。", RecordFocus = "填写紫外吸光度" },
new() { Clause = "4.4.5", Category = "化学性能", Item = "色泽", Requirement = "应无色透明", Method = "目视观察检验液色和澄清度", ControlPoint = "人工外观确认" }, new() { Clause = "4.4.5", Category = "化学性能", Item = "色泽", AcceptanceCriteria = "检验液应无色透明", TestMethod = "目视观察检验液色和澄清度;判定应无色透明。", RecordFocus = "记录色泽观察结果" },
new() { Clause = "4.5", Category = "残留", Item = "环氧乙烷残留量", Requirement = "EO 残留量不大于 10 mg/kg", Method = "对样品提取后测定环氧乙烷残留量,确认不超过限值", ControlPoint = "人工填写 EO" }, new() { Clause = "4.5", Category = "残留", Item = "环氧乙烷残留量", AcceptanceCriteria = "环氧乙烷残留量不大于 10 mg/kg", TestMethod = "测定样品的环氧乙烷残留量;判定结果不大于 10 mg/kg。", RecordFocus = "填写 EO 残留量" },
new() { Clause = "4.6", Category = "耐蚀性", Item = "金属部件耐蚀性", Requirement = "直接接触血液部位不超过 1 级", Method = "对金属部件进行耐蚀性试验并腐蚀等级判定", ControlPoint = "耐蚀性记录" }, new() { Clause = "4.6", Category = "耐蚀性", Item = "金属部件耐蚀性", AcceptanceCriteria = "金属部件腐蚀等级不超过 1 级", TestMethod = "进行耐蚀性试验并评定腐蚀等级判定结果不超过 1 级。", RecordFocus = "记录腐蚀等级" },
new() { Clause = "4.7", Category = "清洁度", Item = "污染指数", Requirement = "污染指数不超过 90", Method = "收集样品污染物并计算污染指数", ControlPoint = "人工填写污染指数" } new() { Clause = "4.7", Category = "清洁度", Item = "污染指数", AcceptanceCriteria = "污染指数不超过 90", TestMethod = "收集污染物并计算污染指数;判定结果不超过 90。", RecordFocus = "填写污染指数" }
]; ];
public IReadOnlyList<TraceEvent> GetSeedTraceEvents() => 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(8.6), Stage = "样品登记", Detail = "录入规格 24Fr/32Fr、双腔、EO 灭菌", Operator = "张工" },
new() { Timestamp = DateTime.Today.AddHours(9.1), Stage = "回路预充", Detail = "实时通道仅做辅助显示,最终结果由试验员手动录入", 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; private readonly DispatcherTimer _timer;
[ObservableProperty] [ObservableProperty]
private string pageTitle = "心肺转流系统一次性使用动静脉插管"; private string pageTitle = "心肺转流系统一次性使用动静脉插管检测";
[ObservableProperty] [ObservableProperty]
private string currentStage = "试验执行中"; private string currentStage = "检测进行中";
[ObservableProperty] [ObservableProperty]
private string operatorName = "张工"; private string operatorName = "张工";
@@ -40,7 +40,7 @@ public partial class MainViewModel : ObservableObject
private bool acquisitionRunning = true; private bool acquisitionRunning = true;
[ObservableProperty] [ObservableProperty]
private bool experimentCompleted; private bool detectionCompleted;
[ObservableProperty] [ObservableProperty]
private double complianceRate; private double complianceRate;
@@ -58,42 +58,42 @@ public partial class MainViewModel : ObservableObject
private double deltaPressure; private double deltaPressure;
[ObservableProperty] [ObservableProperty]
private string latestAction = "系统已载入标准条款,等待试验员录入结果。"; private string latestAction = "系统已载入标准项目,等待填写检测结果。";
[ObservableProperty] [ObservableProperty]
private TestRequirement? selectedRequirement; private InspectionItem? selectedItem;
[ObservableProperty] [ObservableProperty]
private string manualMeasuredValue = ""; private string resultValue = "";
[ObservableProperty] [ObservableProperty]
private string manualNote = ""; private string resultNote = "";
[ObservableProperty] [ObservableProperty]
private string manualOperator = "张工"; private string resultOperator = "张工";
[ObservableProperty] [ObservableProperty]
private string selectedManualStatusText = "合格"; private string selectedResultStatusText = "合格";
[ObservableProperty] [ObservableProperty]
private string experimentSummary = ""; private string detectionSummary = "";
[ObservableProperty] [ObservableProperty]
private string requirementSearchText = ""; private string itemSearchText = "";
public MainViewModel(IStandardRepository repository, IModbusTelemetryService telemetryService) public MainViewModel(IStandardRepository repository, IModbusTelemetryService telemetryService)
{ {
_telemetryService = telemetryService; _telemetryService = telemetryService;
Requirements = new ObservableCollection<TestRequirement>(repository.GetRequirements()); InspectionItems = new ObservableCollection<InspectionItem>(repository.GetInspectionItems());
Channels = new ObservableCollection<DeviceChannel>(telemetryService.GetChannels()); Channels = new ObservableCollection<DeviceChannel>(telemetryService.GetChannels());
TraceEvents = new ObservableCollection<TraceEvent>(repository.GetSeedTraceEvents()); TraceEvents = new ObservableCollection<TraceEvent>(repository.GetSeedTraceEvents());
AlarmMessages = new ObservableCollection<AlarmMessage>(); AlarmMessages = new ObservableCollection<AlarmMessage>();
ManualStatusOptions = new ObservableCollection<string>(["待检", "合格", "预警", "不合格"]); ResultStatusOptions = new ObservableCollection<string>(["待检", "合格", "预警", "不合格"]);
SelectedRequirement = Requirements.FirstOrDefault(); SelectedItem = InspectionItems.FirstOrDefault();
if (SelectedRequirement is not null) if (SelectedItem is not null)
{ {
LoadSelectedRequirementDraft(SelectedRequirement); LoadSelectedItemDraft(SelectedItem);
} }
RefreshTelemetryPanel(); RefreshTelemetryPanel();
@@ -104,21 +104,21 @@ public partial class MainViewModel : ObservableObject
_timer.Start(); _timer.Start();
} }
public ObservableCollection<TestRequirement> Requirements { get; } public ObservableCollection<InspectionItem> InspectionItems { get; }
public ObservableCollection<DeviceChannel> Channels { get; } public ObservableCollection<DeviceChannel> Channels { get; }
public ObservableCollection<TraceEvent> TraceEvents { get; } public ObservableCollection<TraceEvent> TraceEvents { get; }
public ObservableCollection<AlarmMessage> AlarmMessages { get; } public ObservableCollection<AlarmMessage> AlarmMessages { get; }
public ObservableCollection<string> ManualStatusOptions { get; } public ObservableCollection<string> ResultStatusOptions { get; }
public IEnumerable<TestRequirement> FilteredRequirements => Requirements.Where(MatchesRequirementSearch); public IEnumerable<InspectionItem> FilteredItems => InspectionItems.Where(MatchesItemSearch);
public bool HasFilteredRequirements => FilteredRequirements.Any(); public bool HasFilteredItems => FilteredItems.Any();
public IEnumerable<DeviceChannel> FlowSensorChannels => Channels.Where(IsFlowSensorChannel); public IEnumerable<DeviceChannel> FlowSensorChannels => Channels.Where(IsFlowSensorChannel);
public IEnumerable<DeviceChannel> OtherChannels => Channels.Where(channel => !IsFlowSensorChannel(channel)); public IEnumerable<DeviceChannel> OtherChannels => Channels.Where(channel => !IsFlowSensorChannel(channel));
public string ComplianceDisplay => $"{ComplianceRate:F0}%"; public string ComplianceDisplay => $"{ComplianceRate:F0}%";
public string DeltaPressureDisplay => $"{DeltaPressure:F1} mmHg"; public string DeltaPressureDisplay => $"{DeltaPressure:F1} mmHg";
public string ExportStateText => ExperimentCompleted ? "实验已结束,可导出 PDF 正式报表" : "实验进行中,完成后导出 PDF 正式报表"; public string ExportStateText => DetectionCompleted ? "检测已完成,可导出 PDF 正式报表" : "检测进行中,完成后导出 PDF 正式报表";
public string SelectedRequirementTitle => SelectedRequirement?.Item ?? "未选择测试项"; public string SelectedItemTitle => SelectedItem?.Item ?? "未选择项";
public string SelectedRequirementStatusText => SelectedRequirement?.StatusText ?? "待检"; public string SelectedItemStatusText => SelectedItem?.StatusText ?? "待检";
public string RealtimeRecirculationDisplay => $"{RecirculationRate:F1}%"; public string RealtimeRecirculationDisplay => $"{RecirculationRate:F1}%";
public string PumpFlowDisplay => $"{PumpFlow:F2} L/min"; public string PumpFlowDisplay => $"{PumpFlow:F2} L/min";
public string DrainageFlowDisplay => $"{DrainageFlow: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 PumpFlowNormalizedValue => ChannelNormalizedValue("主泵流量");
public double DrainageFlowNormalizedValue => ChannelNormalizedValue("静脉引流流量"); public double DrainageFlowNormalizedValue => ChannelNormalizedValue("静脉引流流量");
public double ReturnFlowNormalizedValue => ChannelNormalizedValue("动脉回输流量"); public double ReturnFlowNormalizedValue => ChannelNormalizedValue("动脉回输流量");
public string FilteredRequirementSummary => $"{FilteredRequirements.Count()} / {Requirements.Count} "; public string FilteredItemSummary => $"{FilteredItems.Count()} / {InspectionItems.Count} ";
public bool HasRequirementSearchText => !string.IsNullOrWhiteSpace(RequirementSearchText); public bool HasItemSearchText => !string.IsNullOrWhiteSpace(ItemSearchText);
public int RealtimeMonitorCount => Requirements.Count(requirement => requirement.CaptureMode == RequirementCaptureMode.RealtimeMonitor); public int RealtimeMonitorCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.RealtimeMonitor);
public int RealtimeAssistCount => Requirements.Count(requirement => requirement.CaptureMode == RequirementCaptureMode.RealtimeAssist); public int RealtimeAssistCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.RealtimeAssist);
public int ManualEntryCount => Requirements.Count(requirement => requirement.CaptureMode == RequirementCaptureMode.ManualEntry); public int ManualEntryCount => InspectionItems.Count(item => item.CaptureMode == InspectionItemCaptureMode.ManualEntry);
public string SelectedRequirementCaptureModeText => SelectedRequirement?.CaptureModeText ?? "未选择"; public string SelectedItemCaptureModeText => SelectedItem?.CaptureModeText ?? "未选择";
public string SelectedRequirementMeasurementSource => SelectedRequirement?.MeasurementSource ?? "-"; public string SelectedItemMeasurementSource => SelectedItem?.MeasurementSource ?? "-";
public bool SelectedRequirementUsesRealtimeValue => SelectedRequirement?.CaptureMode == RequirementCaptureMode.RealtimeMonitor; public bool SelectedItemUsesRealtimeValue => SelectedItem?.CaptureMode == InspectionItemCaptureMode.RealtimeMonitor;
public string RealtimeMeasurementHint => SelectedRequirementUsesRealtimeValue public string RealtimeMeasurementHint => SelectedItemUsesRealtimeValue
? "当前条款取实时工况计算值,不再手动录入。" ? "当前项目使用实时数据自动判定,无需手动填写。"
: SelectedRequirement?.ManualEntryHint ?? "当前条款保持人工录入。"; : SelectedItem?.ManualEntryHint ?? "当前项目需要人工填写结果。";
public string SelectedRequirementLiveDisplay => BuildSelectedRequirementLiveDisplay(); public string SelectedItemLiveDisplay => BuildSelectedItemLiveDisplay();
public string SelectedRequirementLiveHint => SelectedRequirement?.LiveDisplayHint ?? "当前条款无实时映射。"; public string SelectedItemLiveHint => SelectedItem?.LiveDisplayHint ?? "当前项目无实时映射。";
public string NegativeAssistPressureDisplay => $"{ChannelValue(""):F1} kPa"; public string NegativeAssistPressureDisplay => $"{ChannelValue(""):F1} kPa";
public string TemperatureDisplay => $"{ChannelValue(""):F1} °C"; public string TemperatureDisplay => $"{ChannelValue(""):F1} °C";
public string FreeHemoglobinDisplay => $"{ChannelValue(""):F3} g/L"; 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 OnComplianceRateChanged(double value) => OnPropertyChanged(nameof(ComplianceDisplay));
partial void OnDeltaPressureChanged(double value) => OnPropertyChanged(nameof(DeltaPressureDisplay)); partial void OnDeltaPressureChanged(double value) => OnPropertyChanged(nameof(DeltaPressureDisplay));
partial void OnExperimentCompletedChanged(bool value) => OnPropertyChanged(nameof(ExportStateText)); partial void OnDetectionCompletedChanged(bool value) => OnPropertyChanged(nameof(ExportStateText));
partial void OnRequirementSearchTextChanged(string value) partial void OnItemSearchTextChanged(string value)
{ {
OnPropertyChanged(nameof(FilteredRequirements)); OnPropertyChanged(nameof(FilteredItems));
OnPropertyChanged(nameof(FilteredRequirementSummary)); OnPropertyChanged(nameof(FilteredItemSummary));
OnPropertyChanged(nameof(HasFilteredRequirements)); OnPropertyChanged(nameof(HasFilteredItems));
OnPropertyChanged(nameof(HasRequirementSearchText)); OnPropertyChanged(nameof(HasItemSearchText));
var filtered = FilteredRequirements.ToList(); var filtered = FilteredItems.ToList();
if (filtered.Count == 0) if (filtered.Count == 0)
{ {
return; 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(SelectedItemTitle));
OnPropertyChanged(nameof(SelectedRequirementStatusText)); OnPropertyChanged(nameof(SelectedItemStatusText));
OnPropertyChanged(nameof(SelectedRequirementCaptureModeText)); OnPropertyChanged(nameof(SelectedItemCaptureModeText));
OnPropertyChanged(nameof(SelectedRequirementMeasurementSource)); OnPropertyChanged(nameof(SelectedItemMeasurementSource));
OnPropertyChanged(nameof(SelectedRequirementUsesRealtimeValue)); OnPropertyChanged(nameof(SelectedItemUsesRealtimeValue));
OnPropertyChanged(nameof(RealtimeMeasurementHint)); OnPropertyChanged(nameof(RealtimeMeasurementHint));
OnPropertyChanged(nameof(SelectedRequirementLiveDisplay)); OnPropertyChanged(nameof(SelectedItemLiveDisplay));
OnPropertyChanged(nameof(SelectedRequirementLiveHint)); OnPropertyChanged(nameof(SelectedItemLiveHint));
if (value is not null) if (value is not null)
{ {
LoadSelectedRequirementDraft(value); LoadSelectedItemDraft(value);
} }
} }
[RelayCommand] [RelayCommand]
private void SelectRequirement(TestRequirement? requirement) private void SelectItem(InspectionItem? item)
{ {
if (requirement is null) if (item is null)
{ {
return; return;
} }
SelectedRequirement = requirement; SelectedItem = item;
} }
[RelayCommand] [RelayCommand]
private void ClearRequirementSearch() private void ClearItemSearch()
{ {
RequirementSearchText = string.Empty; ItemSearchText = string.Empty;
} }
[RelayCommand] [RelayCommand]
@@ -213,7 +213,7 @@ public partial class MainViewModel : ObservableObject
{ {
AcquisitionRunning = !AcquisitionRunning; AcquisitionRunning = !AcquisitionRunning;
DeviceStatus = AcquisitionRunning ? "Modbus 在线" : "采集暂停"; DeviceStatus = AcquisitionRunning ? "Modbus 在线" : "采集暂停";
LatestAction = AcquisitionRunning ? "继续采集实时工况,供试验员参考。" : "已暂停实时采集。"; LatestAction = AcquisitionRunning ? "继续采集实时数据,供检测参考。" : "已暂停实时采集。";
if (AcquisitionRunning) if (AcquisitionRunning)
{ {
@@ -228,95 +228,95 @@ public partial class MainViewModel : ObservableObject
} }
[RelayCommand] [RelayCommand]
private void SelectPreviousRequirement() private void SelectPreviousItem()
{ {
var scope = ActiveRequirementScope(); var scope = ActiveItemScope();
if (SelectedRequirement is null) if (SelectedItem is null)
{ {
SelectedRequirement = scope.FirstOrDefault(); SelectedItem = scope.FirstOrDefault();
return; return;
} }
var index = scope.IndexOf(SelectedRequirement); var index = scope.IndexOf(SelectedItem);
if (index > 0) if (index > 0)
{ {
SelectedRequirement = scope[index - 1]; SelectedItem = scope[index - 1];
} }
} }
[RelayCommand] [RelayCommand]
private void SelectNextRequirement() private void SelectNextItem()
{ {
var scope = ActiveRequirementScope(); var scope = ActiveItemScope();
if (SelectedRequirement is null) if (SelectedItem is null)
{ {
SelectedRequirement = scope.FirstOrDefault(); SelectedItem = scope.FirstOrDefault();
return; return;
} }
var index = scope.IndexOf(SelectedRequirement); var index = scope.IndexOf(SelectedItem);
if (index >= 0 && index < scope.Count - 1) if (index >= 0 && index < scope.Count - 1)
{ {
SelectedRequirement = scope[index + 1]; SelectedItem = scope[index + 1];
} }
} }
[RelayCommand] [RelayCommand]
private void ApplyManualResult() private void ApplyResult()
{ {
if (SelectedRequirement is null) if (SelectedItem is null)
{ {
LatestAction = "请先选择条款项。"; LatestAction = "请先选择项。";
return; return;
} }
if (SelectedRequirementUsesRealtimeValue) if (SelectedItemUsesRealtimeValue)
{ {
LatestAction = "当前条款已改为实时工况计算值,无需手动录入。"; LatestAction = "当前项目使用实时数据自动判定,无需手动填写。";
return; return;
} }
if (string.IsNullOrWhiteSpace(ManualMeasuredValue)) if (string.IsNullOrWhiteSpace(ResultValue))
{ {
LatestAction = "请先输入测量结果或人工结论。"; LatestAction = "请先填写检测结果或判定结论。";
return; return;
} }
SelectedRequirement.Measured = ManualMeasuredValue.Trim(); SelectedItem.Measured = ResultValue.Trim();
SelectedRequirement.Notes = ManualNote.Trim(); SelectedItem.Notes = ResultNote.Trim();
SelectedRequirement.RecordedBy = string.IsNullOrWhiteSpace(ManualOperator) ? OperatorName : ManualOperator.Trim(); SelectedItem.RecordedBy = string.IsNullOrWhiteSpace(ResultOperator) ? OperatorName : ResultOperator.Trim();
SelectedRequirement.RecordedAt = DateTime.Now; SelectedItem.RecordedAt = DateTime.Now;
SelectedRequirement.Status = SelectedManualStatusText switch SelectedItem.Status = SelectedResultStatusText switch
{ {
"待检" => RequirementStatus.Pending, "待检" => InspectionItemStatus.Pending,
"合格" => RequirementStatus.Qualified, "合格" => InspectionItemStatus.Qualified,
"预警" => RequirementStatus.Warning, "预警" => InspectionItemStatus.Warning,
"不合格" => RequirementStatus.Critical, "不合格" => InspectionItemStatus.Critical,
_ => RequirementStatus.Pending _ => InspectionItemStatus.Pending
}; };
LatestAction = $"已记录 {SelectedRequirement.Item} 的人工测试结果。"; LatestAction = $"已填写 {SelectedItem.Item} 的检测结果。";
TraceEvents.Insert(0, new TraceEvent TraceEvents.Insert(0, new TraceEvent
{ {
Timestamp = DateTime.Now, Timestamp = DateTime.Now,
Stage = "人工录入", Stage = "结果填写",
Detail = $"{SelectedRequirement.Item}: {SelectedRequirement.Measured}", Detail = $"{SelectedItem.Item}: {SelectedItem.Measured}",
Operator = SelectedRequirement.RecordedBy Operator = SelectedItem.RecordedBy
}); });
RefreshComputedState(); RefreshComputedState();
} }
[RelayCommand] [RelayCommand]
private void CompleteExperiment() private void CompleteDetection()
{ {
ExperimentCompleted = true; DetectionCompleted = true;
CurrentStage = "实验结束"; CurrentStage = "检测完成";
AcquisitionRunning = false; AcquisitionRunning = false;
_timer.Stop(); _timer.Stop();
DeviceStatus = "采集停止"; DeviceStatus = "采集停止";
LatestAction = PendingCount == 0 ? "实验结束,全部条款已录入,可导出 PDF 报表。" : $"实验结束,但仍有 {PendingCount} 项待,请确认后导出 PDF 报表。"; LatestAction = PendingCount == 0 ? "检测完成,全部项目已填写,可导出 PDF 报表。" : $"检测完成,但仍有 {PendingCount} 项待处理,请确认后导出 PDF 报表。";
TraceEvents.Insert(0, NewTrace("实验结束", "实验员结束本次检测任务")); TraceEvents.Insert(0, NewTrace("检测完成", "检测员结束本次检测任务"));
} }
[RelayCommand] [RelayCommand]
@@ -339,8 +339,8 @@ public partial class MainViewModel : ObservableObject
{ {
var outputDirectory = AppContext.BaseDirectory; var outputDirectory = AppContext.BaseDirectory;
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss"); var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
var htmlPath = Path.Combine(outputDirectory, $"实验报表-{BatchNumber}-{timestamp}.html"); var htmlPath = Path.Combine(outputDirectory, $"检测报表-{BatchNumber}-{timestamp}.html");
var pdfPath = Path.Combine(outputDirectory, $"实验报表-{BatchNumber}-{timestamp}.pdf"); var pdfPath = Path.Combine(outputDirectory, $"检测报表-{BatchNumber}-{timestamp}.pdf");
File.WriteAllText(htmlPath, BuildHtmlReport(), Encoding.UTF8); File.WriteAllText(htmlPath, BuildHtmlReport(), Encoding.UTF8);
@@ -416,43 +416,45 @@ public partial class MainViewModel : ObservableObject
OnPropertyChanged(nameof(ReturnFlowNormalizedValue)); OnPropertyChanged(nameof(ReturnFlowNormalizedValue));
OnPropertyChanged(nameof(FlowSensorChannels)); OnPropertyChanged(nameof(FlowSensorChannels));
OnPropertyChanged(nameof(OtherChannels)); OnPropertyChanged(nameof(OtherChannels));
OnPropertyChanged(nameof(SelectedRequirementLiveDisplay)); OnPropertyChanged(nameof(SelectedItemLiveDisplay));
OnPropertyChanged(nameof(SelectedRequirementLiveHint)); OnPropertyChanged(nameof(SelectedItemLiveHint));
SyncRealtimeRequirements(); SyncRealtimeItems();
} }
private void RefreshComputedState() private void RefreshComputedState()
{ {
QualifiedCount = Requirements.Count(r => r.Status == RequirementStatus.Qualified); QualifiedCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Qualified);
WarningCount = Requirements.Count(r => r.Status == RequirementStatus.Warning || r.Status == RequirementStatus.Critical); WarningCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Warning || r.Status == InspectionItemStatus.Critical);
PendingCount = Requirements.Count(r => r.Status == RequirementStatus.Pending); PendingCount = InspectionItems.Count(r => r.Status == InspectionItemStatus.Pending);
ComplianceRate = Requirements.Count == 0 ? 0 : QualifiedCount * 100d / Requirements.Count; ComplianceRate = InspectionItems.Count == 0 ? 0 : QualifiedCount * 100d / InspectionItems.Count;
} }
private void LoadSelectedRequirementDraft(TestRequirement requirement) private void LoadSelectedItemDraft(InspectionItem item)
{ {
ManualMeasuredValue = SelectedRequirementUsesRealtimeValue ResultValue = SelectedItemUsesRealtimeValue
? requirement.Measured ? item.Measured
: requirement.Measured == "待检测" ? string.Empty : requirement.Measured; : item.Measured == "待检测" ? string.Empty : item.Measured;
ManualNote = requirement.Notes; ResultNote = item.Notes;
ManualOperator = string.IsNullOrWhiteSpace(requirement.RecordedBy) ? OperatorName : requirement.RecordedBy; ResultOperator = string.IsNullOrWhiteSpace(item.RecordedBy) ? OperatorName : item.RecordedBy;
SelectedManualStatusText = requirement.Status switch SelectedResultStatusText = item.Status switch
{ {
RequirementStatus.Warning => "预警", InspectionItemStatus.Warning => "预警",
RequirementStatus.Critical => "不合格", InspectionItemStatus.Critical => "不合格",
RequirementStatus.Pending => "待检", InspectionItemStatus.Pending => "待检",
_ => "合格" _ => "合格"
}; };
} }
private string BuildHtmlReport() private string BuildHtmlReport()
{ {
var rows = string.Join(Environment.NewLine, Requirements.Select(r => $@" var rows = string.Join(Environment.NewLine, InspectionItems.Select(r => $@"
<tr> <tr>
<td>{Html(r.Clause)}</td>
<td>{Html(r.Category)}</td> <td>{Html(r.Category)}</td>
<td>{Html(r.Item)}</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.Measured)}</td>
<td>{Html(r.StatusText)}</td> <td>{Html(r.StatusText)}</td>
<td>{Html(r.RecordedBy)}</td> <td>{Html(r.RecordedBy)}</td>
@@ -493,10 +495,10 @@ ul {{ padding-left: 20px; }}
<div><strong></strong>{Html(ComplianceDisplay)}</div> <div><strong></strong>{Html(ComplianceDisplay)}</div>
<div><strong></strong>{Html(DeltaPressureDisplay)}</div> <div><strong></strong>{Html(DeltaPressureDisplay)}</div>
</div> </div>
<div class='card'><strong></strong>{Html(ExperimentSummary)}</div> <div class='card'><strong></strong>{Html(DetectionSummary)}</div>
<h2></h2> <h2></h2>
<table> <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> <tbody>{rows}</tbody>
</table> </table>
<h2></h2> <h2></h2>
@@ -537,72 +539,72 @@ ul {{ padding-left: 20px; }}
Timestamp = DateTime.Now, Timestamp = DateTime.Now,
Stage = stage, Stage = stage,
Detail = detail, 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"); var pressureItem = InspectionItems.FirstOrDefault(item => item.Clause == "4.3.1");
if (pressureRequirement is not null) if (pressureItem is not null)
{ {
var pressureStatus = DeltaPressure switch var pressureStatus = DeltaPressure switch
{ {
<= 20 => RequirementStatus.Qualified, <= 20 => InspectionItemStatus.Qualified,
<= 24 => RequirementStatus.Warning, <= 24 => InspectionItemStatus.Warning,
_ => RequirementStatus.Critical _ => InspectionItemStatus.Critical
}; };
pressureRequirement.Measured = $"实时压差 {DeltaPressure:F1} mmHg"; pressureItem.Measured = $"实时压差 {DeltaPressure:F1} mmHg";
pressureRequirement.Notes = pressureItem.Notes =
$"入口 {ChannelValue(""):F1} mmHg出口 {ChannelValue(""):F1} mmHg按额定流量点连续监控。"; $"入口 {ChannelValue(""):F1} mmHg出口 {ChannelValue(""):F1} mmHg按额定流量点连续监控。";
pressureRequirement.RecordedBy = "实时工况"; pressureItem.RecordedBy = "实时数据";
pressureRequirement.RecordedAt = DateTime.Now; pressureItem.RecordedAt = DateTime.Now;
pressureRequirement.Status = pressureStatus; pressureItem.Status = pressureStatus;
} }
var recirculationRequirement = Requirements.FirstOrDefault(requirement => requirement.Clause == "4.3.3"); var recirculationItem = InspectionItems.FirstOrDefault(item => item.Clause == "4.3.3");
if (recirculationRequirement is null) if (recirculationItem is null)
{ {
return; return;
} }
var status = RecirculationRate switch var status = RecirculationRate switch
{ {
<= 8 => RequirementStatus.Qualified, <= 8 => InspectionItemStatus.Qualified,
<= 12 => RequirementStatus.Warning, <= 12 => InspectionItemStatus.Warning,
_ => RequirementStatus.Critical _ => InspectionItemStatus.Critical
}; };
recirculationRequirement.Measured = recirculationItem.Measured =
$"实时计算值 {RecirculationRate:F1}% (回输 {ReturnFlow:F2} / 引流 {DrainageFlow:F2} L/min)"; $"实时计算值 {RecirculationRate:F1}% (回输 {ReturnFlow:F2} / 引流 {DrainageFlow:F2} L/min)";
recirculationRequirement.Notes = recirculationItem.Notes =
$"由 3 路流量传感器实时计算;主泵 {PumpFlow:F2} L/min流量差 {Math.Abs(PumpFlow - ReturnFlow):F2} L/min。"; $"由 3 路流量传感器实时计算;主泵 {PumpFlow:F2} L/min流量差 {Math.Abs(PumpFlow - ReturnFlow):F2} L/min。";
recirculationRequirement.RecordedBy = "实时工况"; recirculationItem.RecordedBy = "实时数据";
recirculationRequirement.RecordedAt = DateTime.Now; recirculationItem.RecordedAt = DateTime.Now;
recirculationRequirement.Status = status; recirculationItem.Status = status;
if (SelectedRequirement == recirculationRequirement) if (SelectedItem == recirculationItem)
{ {
ManualMeasuredValue = recirculationRequirement.Measured; ResultValue = recirculationItem.Measured;
ManualNote = recirculationRequirement.Notes; ResultNote = recirculationItem.Notes;
SelectedManualStatusText = recirculationRequirement.Status switch SelectedResultStatusText = recirculationItem.Status switch
{ {
RequirementStatus.Pending => "待检", InspectionItemStatus.Pending => "待检",
RequirementStatus.Warning => "预警", InspectionItemStatus.Warning => "预警",
RequirementStatus.Critical => "不合格", InspectionItemStatus.Critical => "不合格",
_ => "合格" _ => "合格"
}; };
} }
if (pressureRequirement is not null && SelectedRequirement == pressureRequirement) if (pressureItem is not null && SelectedItem == pressureItem)
{ {
ManualMeasuredValue = pressureRequirement.Measured; ResultValue = pressureItem.Measured;
ManualNote = pressureRequirement.Notes; ResultNote = pressureItem.Notes;
SelectedManualStatusText = pressureRequirement.Status switch SelectedResultStatusText = pressureItem.Status switch
{ {
RequirementStatus.Pending => "待检", InspectionItemStatus.Pending => "待检",
RequirementStatus.Warning => "预警", InspectionItemStatus.Warning => "预警",
RequirementStatus.Critical => "不合格", InspectionItemStatus.Critical => "不合格",
_ => "合格" _ => "合格"
}; };
} }
@@ -610,21 +612,21 @@ ul {{ padding-left: 20px; }}
private double ChannelValue(string name) => Channels.First(channel => channel.Name == name).Value; 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 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; return true;
} }
var keyword = RequirementSearchText.Trim(); var keyword = ItemSearchText.Trim();
return MatchesKeyword(requirement.Clause, keyword) return MatchesKeyword(item.Clause, keyword)
|| MatchesKeyword(requirement.Item, keyword) || MatchesKeyword(item.Item, keyword)
|| MatchesKeyword(requirement.Category, keyword) || MatchesKeyword(item.Category, keyword)
|| MatchesKeyword(requirement.Requirement, keyword) || MatchesKeyword(item.AcceptanceCriteria, keyword)
|| MatchesKeyword(requirement.Method, keyword); || MatchesKeyword(item.TestMethod, keyword);
} }
private static bool MatchesKeyword(string source, string keyword) private static bool MatchesKeyword(string source, string keyword)
@@ -653,21 +655,21 @@ ul {{ padding-left: 20px; }}
private static bool IsFlowSensorChannel(DeviceChannel channel) => private static bool IsFlowSensorChannel(DeviceChannel channel) =>
channel.Name is "主泵流量" or "静脉引流流量" or "动脉回输流量"; 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.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.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.3", _) => $"再循环率 {RecirculationRate:F1}% / 引流 {DrainageFlow:F2} / 回输 {ReturnFlow:F2} L/min",
("4.3.4", "血细胞破坏") => $"游离血红蛋白 {ChannelValue(""):F3} g/L / 温度 {ChannelValue(""):F1} °C", ("4.3.4", "血细胞破坏") => $"游离血红蛋白 {ChannelValue(""):F3} g/L / 温度 {ChannelValue(""):F1} °C",
("4.3.4", "血小板/白细胞减少率") => $"白细胞减少率 {ChannelValue(""):F1}% / 温度 {ChannelValue(""):F1} °C", ("4.3.4", "血小板/白细胞减少率") => $"白细胞减少率 {ChannelValue(""):F1}% / 温度 {ChannelValue(""):F1} °C",
_ => "当前条款无实时信号,按试验原始记录手动录入。" _ => "当前项目无实时信号,按检测原始记录手动填写。"
}; };
} }
} }