678 lines
23 KiB
C#
678 lines
23 KiB
C#
using Modbus.Device;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Configuration;
|
||
using System.IO.Ports;
|
||
using System.Linq;
|
||
using System.Net.Sockets;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Data;
|
||
using System.Windows.Documents;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using System.Windows.Navigation;
|
||
using System.Windows.Shapes;
|
||
using System.Windows.Threading;
|
||
using 自救器呼吸器综合检验仪.Data;
|
||
|
||
namespace 自救器呼吸器综合检验仪
|
||
{
|
||
/// <summary>
|
||
/// MainWindow.xaml 的交互逻辑
|
||
/// </summary>
|
||
public partial class MainWindow : Window
|
||
{
|
||
private MainWindow _mainWindow;
|
||
private MainWindow2 _mainWindow2;
|
||
private MainWindow3 _mainWindow3;
|
||
private MainWindow4 _mainWindow4;
|
||
private ParameterSettingsWindow _mainWindow5;
|
||
private ReportWindow _reportWindow;
|
||
|
||
private bool _wasRunning = false; // 上次检验的运行状态
|
||
private bool _recordAdded = false; // 本轮测试是否已插入记录
|
||
private DateTime _testStartTime; // 测试开始时间
|
||
private Button _selectedParameterButton;
|
||
|
||
private static readonly Brush ParameterButtonDefaultBrush = CreateParameterButtonBrush("#3498DB");
|
||
private static readonly Brush ParameterButtonSelectedBrush = CreateParameterButtonBrush("#F39C12");
|
||
|
||
int retryCount = 0; // 连接失败重试次数
|
||
private TcpClient _tcpClient => ModbusResourceManager.Instance.TcpClient;
|
||
private IModbusMaster _modbusMaster => ModbusResourceManager.Instance.ModbusMaster;
|
||
|
||
|
||
private const byte SlaveId = 1; // PLC从站地址
|
||
private const ushort Address262 = 300;
|
||
private bool _isManualInput = false; // 手动输入标记:true=输入中(暂停读取),false=正常读取
|
||
private readonly DispatcherTimer _readTimer;
|
||
|
||
Function ma;
|
||
DataChange c = new DataChange();
|
||
|
||
public MainWindow()
|
||
{
|
||
InitializeComponent();
|
||
_readTimer = InitDispatcherTimer();
|
||
//InitializeModbusTcp();
|
||
BindInputFocusEvents();
|
||
}
|
||
|
||
private void BindInputFocusEvents()
|
||
{
|
||
// 地址400输入框(count)
|
||
settingtime.GotFocus += (s, e) => _isManualInput = true; // 获得焦点→暂停读取
|
||
settingtime.LostFocus += (s, e) => _isManualInput = false; // 失去焦点→恢复读取
|
||
|
||
}
|
||
|
||
private DispatcherTimer InitDispatcherTimer()
|
||
{
|
||
var timer = new DispatcherTimer
|
||
{
|
||
Interval = TimeSpan.FromMilliseconds(500)
|
||
};
|
||
timer.Tick += async (s, e) =>
|
||
{
|
||
if (!_isManualInput && _modbusMaster != null)
|
||
{
|
||
|
||
try
|
||
{
|
||
await ReadAllPlcDataAsync();
|
||
}
|
||
catch { }
|
||
}
|
||
};
|
||
return timer;
|
||
}
|
||
|
||
private async System.Threading.Tasks.Task ReadAllPlcDataAsync()
|
||
{
|
||
try
|
||
{
|
||
// 检查连接状态
|
||
if (_tcpClient == null || !_tcpClient.Connected || _modbusMaster == null)
|
||
{
|
||
return;
|
||
}
|
||
// 仅在非手动输入时读取设置地址(避免覆盖输入)
|
||
if (!_isManualInput)
|
||
{
|
||
await ReadAddr262DataAsync();
|
||
await System.Threading.Tasks.Task.Delay(50);
|
||
|
||
await ReadAddr404DataAsync();
|
||
await System.Threading.Tasks.Task.Delay(50);
|
||
|
||
await ReadAddr406DataAsync();
|
||
await System.Threading.Tasks.Task.Delay(50);
|
||
await ReadAddr130DataAsync();
|
||
}
|
||
|
||
await ReadAddrStatusDataAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
retryCount++;
|
||
if (retryCount > 3)
|
||
{
|
||
ShowErrorMsg($"读取PLC数据失败:{ex.Message}");
|
||
retryCount = 0;
|
||
return;
|
||
}
|
||
await System.Threading.Tasks.Task.Delay(500);
|
||
await ReadAllPlcDataAsync();
|
||
}
|
||
}
|
||
|
||
private async System.Threading.Tasks.Task ReadAddr262DataAsync()
|
||
{
|
||
try
|
||
{
|
||
ushort[] registers = await System.Threading.Tasks.Task.Run(() =>
|
||
_modbusMaster.ReadHoldingRegisters(1, Address262, 2)
|
||
);
|
||
|
||
if (registers != null)
|
||
{
|
||
int addr406Value = registers[0];
|
||
Dispatcher.Invoke(() => settingtime.Text = addr406Value.ToString("F0"));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//ShowErrorMsg($"读取地址406失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private async System.Threading.Tasks.Task ReadAddr404DataAsync()
|
||
{
|
||
try
|
||
{
|
||
ushort[] registers = await System.Threading.Tasks.Task.Run(() =>
|
||
_modbusMaster.ReadHoldingRegisters(1, 1330, 2)
|
||
);
|
||
|
||
if (registers != null)
|
||
{
|
||
float addr406Value = c.UshortToFloat(registers[1], registers[0]);
|
||
|
||
Dispatcher.Invoke(() => min.Text = addr406Value.ToString("F2"));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//ShowErrorMsg($"读取地址406失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private async System.Threading.Tasks.Task ReadAddr406DataAsync()
|
||
{
|
||
try
|
||
{
|
||
ushort[] registers = await System.Threading.Tasks.Task.Run(() =>
|
||
_modbusMaster.ReadHoldingRegisters(1, 72, 1)
|
||
);
|
||
|
||
if (registers != null)
|
||
{
|
||
int addr406Value = registers[0];
|
||
Dispatcher.Invoke(() => second.Text = addr406Value.ToString("F0"));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//ShowErrorMsg($"读取地址406失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
|
||
private async System.Threading.Tasks.Task ReadAddr130DataAsync()
|
||
{
|
||
try
|
||
{
|
||
ushort[] registers = await System.Threading.Tasks.Task.Run(() =>
|
||
_modbusMaster?.ReadHoldingRegisters(1, 76, 2)
|
||
);
|
||
|
||
if (registers != null)
|
||
{
|
||
double addr406Value = c.UshortToFloat(registers[1], registers[0]);
|
||
addr406Value = Math.Truncate(addr406Value * 100) / 100.0;
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
|
||
speed.Text = addr406Value.ToString("F2");
|
||
|
||
});
|
||
|
||
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//ShowErrorMsg($"读取地址406失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
bool isReset = false;
|
||
// 地址400写入(16位整数)
|
||
private async void BtnWrite400_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
_modbusMaster.WriteSingleCoil(1, 75, true);
|
||
isReset = true;
|
||
UpdateParameterButtonSelection(sender as Button);
|
||
}
|
||
|
||
|
||
private void ShowErrorMsg(string msg)
|
||
{
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
MessageBox.Show(msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
});
|
||
}
|
||
|
||
private static Brush CreateParameterButtonBrush(string colorCode)
|
||
{
|
||
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(colorCode));
|
||
}
|
||
|
||
private void UpdateParameterButtonSelection(Button selectedButton)
|
||
{
|
||
if (selectedButton == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (_selectedParameterButton != null && !ReferenceEquals(_selectedParameterButton, selectedButton))
|
||
{
|
||
ApplyParameterButtonStyle(_selectedParameterButton, false);
|
||
}
|
||
|
||
_selectedParameterButton = selectedButton;
|
||
ApplyParameterButtonStyle(_selectedParameterButton, true);
|
||
}
|
||
|
||
private void ApplyParameterButtonStyle(Button button, bool isSelected)
|
||
{
|
||
if (button == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
button.Background = isSelected ? ParameterButtonSelectedBrush : ParameterButtonDefaultBrush;
|
||
button.BorderBrush = Brushes.Transparent;
|
||
button.BorderThickness = new Thickness(0);
|
||
}
|
||
|
||
private void Button_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
//ma.BtnClickFunctionForNew(Function.ButtonType.切换型, 170);
|
||
OpenQuantitativeSupplyWindow2();
|
||
// SwitchWindow(ref _reportWindow, () => new ReportWindow());
|
||
}
|
||
|
||
private void Button_Click_1(object sender, RoutedEventArgs e)
|
||
{
|
||
ma.BtnClickFunctionForNew(Function.ButtonType.切换型, 70);
|
||
}
|
||
|
||
private void Button_Click_2(object sender, RoutedEventArgs e)
|
||
{
|
||
//ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 73);
|
||
ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 73);
|
||
}
|
||
|
||
private void Button_Click_3(object sender, RoutedEventArgs e)
|
||
{
|
||
ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 74);
|
||
}
|
||
|
||
private void Button_Click_4(object sender, RoutedEventArgs e)
|
||
{
|
||
SwitchWindow(ref _mainWindow, () => new MainWindow(), "正在进入定量供应检验", "正在准备定量供应检验页面...");
|
||
}
|
||
|
||
private void Button_Click_5(object sender, RoutedEventArgs e)
|
||
{
|
||
SwitchWindow(ref _mainWindow2, () => new MainWindow2(), "正在进入负压气密性检验", "正在切换到负压气密性检验页面...");
|
||
}
|
||
|
||
private void Button_Click_6(object sender, RoutedEventArgs e)
|
||
{
|
||
SwitchWindow(ref _mainWindow3, () => new MainWindow3(), "正在进入正压气密性检验", "正在切换到正压气密性检验页面...");
|
||
}
|
||
|
||
private void Button_Click_7(object sender, RoutedEventArgs e)
|
||
{
|
||
SwitchWindow(ref _mainWindow4, () => new MainWindow4(), "正在进入排气阀压力测试", "正在切换到排气阀压力测试页面...");
|
||
}
|
||
|
||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||
{
|
||
string plcIp = "192.168.1.10";
|
||
bool initSuccess = Data.ModbusResourceManager.Instance.Init(plcIp, 502);
|
||
if (!initSuccess)
|
||
{
|
||
MessageBox.Show("连接Modbus服务器失败!", "错误");
|
||
this.Close();
|
||
return;
|
||
}
|
||
|
||
// 检查连接状态
|
||
if (_tcpClient == null || !_tcpClient.Connected)
|
||
{
|
||
MessageBox.Show("Modbus连接异常!", "错误");
|
||
this.Close();
|
||
return;
|
||
}
|
||
|
||
ma = new Function(_modbusMaster);
|
||
|
||
_modbusMaster.WriteSingleCoil(1, 61, true);
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
|
||
_modbusMaster.WriteSingleCoil(1, 62, false);
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
|
||
_modbusMaster.WriteSingleCoil(1, 63, false);
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
|
||
_modbusMaster.WriteSingleCoil(1, 64, false);
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
_modbusMaster.WriteSingleCoil(1, 75, true);
|
||
_readTimer.Start();
|
||
|
||
|
||
|
||
|
||
pressureDiff2.Text = ConfigurationManager.AppSettings["No"]?.ToString();
|
||
|
||
}
|
||
|
||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||
{
|
||
// 停止定时器
|
||
_readTimer?.Stop();
|
||
|
||
// 停止ViewModel的定时器
|
||
if (DataContext is MainViewModel viewModel)
|
||
{
|
||
viewModel.StopTimer();
|
||
}
|
||
|
||
// 释放Modbus资源
|
||
ModbusResourceManager.Instance?.Dispose();
|
||
|
||
// 延后到当前窗口完成关闭后再统一关停,避免关闭重入。
|
||
AppShutdownCoordinator.RequestShutdown();
|
||
}
|
||
private void Window_Closed(object sender, EventArgs e)
|
||
{
|
||
// 由应用级关停统一处理其他窗口,避免关闭链路重入。
|
||
}
|
||
private void BtnWrite401_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
if (!short.TryParse(settingtime.Text.Trim(), out short writeValue) ||
|
||
writeValue < short.MinValue || writeValue > short.MaxValue)
|
||
{
|
||
ShowErrorMsg($"需输入整数(范围:{short.MinValue} ~ {short.MaxValue})");
|
||
settingtime.Focus();
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
//await System.Threading.Tasks.Task.Run(() =>
|
||
// _modbusMaster.WriteSingleRegister(SlaveId, Address262, (ushort)writeValue)
|
||
//);
|
||
|
||
ma.WriteToPLCForNew(settingtime.Text.Trim(), 300, Function.DataType.整形);
|
||
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
_isManualInput = false; // 写入后恢复读取
|
||
UpdateParameterButtonSelection(sender as Button);
|
||
//await ReadAddr400DataAsync(); // 刷新显示(确认写入成功)
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowErrorMsg($"地址400写入失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void SwitchWindow<T>(ref T windowInstance, Func<T> createFunc, string loadingTitle = null, string loadingSubtitle = null) where T : Window, new()
|
||
{
|
||
// 1. 停止当前窗口的定时器(不释放资源)
|
||
_readTimer?.Stop();
|
||
|
||
// 2. 检查资源是否可用(添加重连机制)
|
||
if (_tcpClient == null || !_tcpClient.Connected || _modbusMaster == null)
|
||
{
|
||
// 尝试重新连接
|
||
bool reconnectSuccess = TryReconnect();
|
||
if (!reconnectSuccess)
|
||
{
|
||
MessageBox.Show("TCP连接已断开,请重新连接!", "提示");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 3. 复用窗口实例:不存在则创建,存在则激活
|
||
if (windowInstance == null)
|
||
{
|
||
windowInstance = createFunc();
|
||
// 添加窗口关闭事件处理
|
||
windowInstance.Closed += (s, args) =>
|
||
{
|
||
WindowNavigationHelper.RestoreWindow(this, () => _readTimer?.Start());
|
||
};
|
||
}
|
||
|
||
// 4. 目标窗口完成首帧渲染后再切换,避免白屏
|
||
WindowNavigationHelper.ShowWithoutWhiteFlash(this, windowInstance, loadingTitle, loadingSubtitle);
|
||
}
|
||
|
||
// 添加重连方法
|
||
private bool TryReconnect()
|
||
{
|
||
try
|
||
{
|
||
string plcIp = "192.168.1.10";
|
||
bool initSuccess = Data.ModbusResourceManager.Instance.Init(plcIp, 502);
|
||
if (initSuccess)
|
||
{
|
||
ma = new Function(_modbusMaster);
|
||
return true;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowErrorMsg($"重新连接失败:{ex.Message}");
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
private void OpenQuantitativeSupplyWindow()
|
||
{
|
||
try
|
||
{
|
||
SwitchWindow(ref _mainWindow5, () => new ParameterSettingsWindow());
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"打开定量供应检验窗口时出错:{ex.Message}", "错误",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
private void btn_setting_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
OpenQuantitativeSupplyWindow();
|
||
}
|
||
|
||
|
||
private async System.Threading.Tasks.Task ReadAddrStatusDataAsync()
|
||
{
|
||
try
|
||
{
|
||
bool[] result = _modbusMaster?.ReadCoils(0x01, 71, 1);
|
||
bool isTestRunning = result != null && result.Length > 0 && result[0];
|
||
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
if (isTestRunning)
|
||
{
|
||
status.Text = "正在运行";
|
||
statusPanel.Background = Brushes.Green;
|
||
if (!_wasRunning)
|
||
{
|
||
_testStartTime = DateTime.Now;
|
||
_recordAdded = false; // 测试刚开始,允许插入记录
|
||
}
|
||
}
|
||
else
|
||
{
|
||
status.Text = "空闲";
|
||
statusPanel.Background = Brushes.Blue;
|
||
// 状态从“运行”切换到“空闲”,且还没插入记录
|
||
if (_wasRunning && !_recordAdded)
|
||
{
|
||
AddTestRecord(); // 插入记录
|
||
_recordAdded = true; // 标记已插入
|
||
}
|
||
}
|
||
_wasRunning = isTestRunning; // 更新上一次状态
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowErrorMsg($"读取运行状态失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void OpenQuantitativeSupplyWindow2()
|
||
{
|
||
try
|
||
{
|
||
SwitchWindow(ref _reportWindow, () => new ReportWindow());
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"打开定量供应检验窗口时出错:{ex.Message}", "错误",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
|
||
private void AddTestRecord()
|
||
{
|
||
try
|
||
{
|
||
// 计算测试时长
|
||
TimeSpan duration = DateTime.Now - _testStartTime;
|
||
string durationStr = $"{duration.Minutes:00}:{duration.Seconds:00}";
|
||
|
||
// 获取当前流量值
|
||
double currentFlowRate = 0;
|
||
if (!string.IsNullOrEmpty(speed.Text) && double.TryParse(speed.Text, out double flow))
|
||
{
|
||
currentFlowRate = flow;
|
||
}
|
||
|
||
// 获取当前时间
|
||
string currentTime = DateTime.Now.ToString("HH:mm");
|
||
string currentDate = DateTime.Now.ToString("yy/MM/dd");
|
||
|
||
// 添加到报表窗口(如果存在或创建)
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
// 如果窗口不存在,先创建但不显示
|
||
if (_reportWindow == null)
|
||
{
|
||
_reportWindow = new ReportWindow();
|
||
}
|
||
|
||
double temprate = 0;
|
||
if (!isReset)
|
||
{
|
||
temprate = currentFlowRate;
|
||
currentFlowRate = 0;
|
||
}
|
||
else
|
||
{
|
||
|
||
}
|
||
|
||
string No = ConfigurationManager.AppSettings["No"]?.ToString();
|
||
// 添加记录到窗口(无论窗口是否显示)
|
||
_reportWindow.AddRecord(currentTime, currentDate, currentFlowRate, durationStr, temprate, No);
|
||
});
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowErrorMsg($"记录测试数据失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private async void BtnWrite400_Click1(object sender, RoutedEventArgs e)
|
||
{
|
||
_modbusMaster.WriteSingleCoil(1, 75, false);
|
||
isReset = false;
|
||
UpdateParameterButtonSelection(sender as Button);
|
||
}
|
||
|
||
//private void pressureDiff2_GotFocus(object sender, RoutedEventArgs e)
|
||
//{
|
||
|
||
//}
|
||
private void pressureDiff2_GotFocus(object sender, RoutedEventArgs e)
|
||
{
|
||
|
||
SaveAppSetting("No", pressureDiff2.Text);
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 保存 AppSettings 配置到配置文件
|
||
/// </summary>
|
||
/// <param name="key">配置键名</param>
|
||
/// <param name="value">配置值</param>
|
||
/// <returns>是否保存成功</returns>
|
||
public bool SaveAppSetting(string key, string value)
|
||
{
|
||
try
|
||
{
|
||
// 1. 获取当前程序的配置文件路径(编译后的 exe.config)
|
||
string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
|
||
|
||
// 2. 加载配置文件(ExeConfigurationFileMap 指定配置路径)
|
||
ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap { ExeConfigFilename = configPath };
|
||
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
|
||
|
||
// 3. 处理 appSettings 节点
|
||
if (config.AppSettings.Settings[key] == null)
|
||
{
|
||
// 键不存在 → 新增配置
|
||
config.AppSettings.Settings.Add(key, value);
|
||
}
|
||
else
|
||
{
|
||
// 键已存在 → 更新配置
|
||
config.AppSettings.Settings[key].Value = value;
|
||
}
|
||
|
||
// 4. 保存配置(重要!否则修改不生效)
|
||
config.Save(ConfigurationSaveMode.Modified);
|
||
|
||
// 5. 刷新 ConfigurationManager 缓存(可选,让后续读取能立即获取新值)
|
||
ConfigurationManager.RefreshSection("appSettings");
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获异常(如权限不足、文件被占用等)
|
||
MessageBox.Show($"保存配置失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void settingtime_TextChanged(object sender, TextChangedEventArgs e)
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
|
||
public class TestRecord
|
||
{
|
||
public int Id { get; set; }
|
||
public string Time { get; set; }//时间
|
||
public string Date { get; set; }//日期
|
||
public double FlowRate { get; set; } // 供氧流量L/min
|
||
public string Duration { get; set; } // 测试时长
|
||
public DateTime CreateTime { get; set; }
|
||
|
||
public string HeadName { get; set; } // 表头名
|
||
|
||
public double FlowRate2 { get; set; } // 供氧流量L/min
|
||
|
||
public double FlowRate3 { get; set; } // 供氧流量L/min
|
||
|
||
public string No { get; set; }
|
||
}
|
||
}
|