using DevExpress.ClipboardSource.SpreadsheetML; using DevExpress.Internal; using DevExpress.XtraPrinting; using Modbus.Device; using Newtonsoft.Json; using OfficeOpenXml; using OfficeOpenXml.Drawing.Slicer.Style; using SQLite; using Sunny.UI; using System; using System.Collections.Generic; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using 口罩泄露定制款.Data; using 口罩泄露定制款.Excle; using 口罩泄露定制款.Form; using 口罩泄露定制款.Log; using 口罩泄露定制款.Modbus; using 口罩泄露测试仪控制系统_定制款.窗体; namespace 口罩泄露定制款 { public partial class frm_Main : UIForm { // 暂停功能相关变量 private bool isPaused = false; private int pausedRemainingSeconds = 0; private string currentVideoPath = string.Empty; UserExcleHelper excleHelper = new UserExcleHelper(); LoginData loginData = new LoginData(); Modbus_Connect modbus_Connect; public ModbusMaster master; Connect_TSI Tsi; ExperData experData = new ExperData(); Function fc; BoolSignal bool_Stop = new BoolSignal(); BoolSignal bool_Start = new BoolSignal(); DataTable dt_Show = new DataTable(); ExperData_ExcleHelper experExcleHelper = new ExperData_ExcleHelper(); private Logger _logger = new Logger(); frm_vidio form2 = new frm_vidio(); bool isNG = false; // 1. 成员变量 private List stepList = new List(); private int currentStepIndex = 0; private System.Windows.Forms.Timer autoStepTimer; private System.Windows.Forms.Timer autoStepTimer2; private int countdownSeconds = 120;//add by xyy,原版120s private bool isNeedRun = false; private string CreenValue = string.Empty; private const string UserDataFilePath = "UserData.json"; // JSON 文件路径 // 2. 配置读取 private void LoadIsNeedRunConfig() { string configPath = Path.Combine(Application.StartupPath, "UserData.json"); if (File.Exists(configPath)) { var json = File.ReadAllText(configPath); dynamic config = Newtonsoft.Json.JsonConvert.DeserializeObject(json); isNeedRun = config?.IsNeedRun == true;//是否运行跑步机 if (config?.ContentString == "自定义")//大声朗读内容 { CreenValue = config?.CustomScreenValue; } else { CreenValue = config?.ScreenValue; } } } // 步骤初始化 - 根据新标准调整 private void InitStepList() { stepList.Clear(); stepList.Add("请到指定测试位置"); stepList.Add("请将采样管插头插入测量接口"); if (isNeedRun) stepList.Add("请按下跑步机启动按钮"); stepList.Add("头部静止、不说话,2 min"); stepList.Add("左右转动头部看检测仓左右墙壁(大约15次),2 min"); stepList.Add("抬头和低头看检测仓顶和地面(大约15次),2 min"); stepList.Add("大声阅读屏幕显示文字,2 min"); stepList.Add("头部静止、不说话,2 min"); stepList.Add("测试完成,拔下采样管插头,走出测量仓"); comboBox1.Items.Clear(); foreach (var step in stepList) comboBox1.Items.Add(step); } // 4. 自动流程Timer private void InitAutoStepTimer() { if (autoStepTimer == null) { autoStepTimer = new System.Windows.Forms.Timer(); autoStepTimer.Interval = 1000; autoStepTimer.Tick += (s, e) => { countdownSeconds--; lb_status.Text = $"{stepList[currentStepIndex]} 倒计时: {countdownSeconds}秒"; // 更新form2的标签文本,保留原有内容只更新倒计时部分 int index = form2.uiLabel1.Text.LastIndexOf("\r\n倒计时:"); if (index > 0) { form2.uiLabel1.Text = form2.uiLabel1.Text.Substring(0, index) + $"\r\n倒计时: {countdownSeconds}秒"; } //form2.axWindowsMediaPlayer1.settings.autoStart = true; // 设置自动播放 //form2.axWindowsMediaPlayer1.Ctlcontrols.play(); if (countdownSeconds <= 0) { autoStepTimer?.Stop(); GoToNextStep(); } }; } //countdownTimer = new System.Windows.Forms.Timer(); //countdownTimer.Interval = 1000; // 1 second interval // countdownTimer.Tick += new EventHandler(CountdownTimer_Tick); } private void InitAutoStepTimer2() { if (autoStepTimer2 == null) { autoStepTimer2 = new System.Windows.Forms.Timer(); autoStepTimer2.Interval = 1000; autoStepTimer2.Tick += (s, e) => { if (float.Parse(lb_tsi_Indoor.Text) < 7) { master?.WriteSingleCoil(1, 16, true); } else if (float.Parse(lb_tsi_Indoor.Text) > 8) { master?.WriteSingleCoil(1, 16, false); } else { } //if (currentStepIndex == 1 && float.Parse(lb_tsi_Indoor.Text) >= 4) //{ // currentStepIndex = 2; // GoToStep(2); //} //else //{ //} }; } } // 5. 流程控制 private void StartProcess() { currentStepIndex = 0; GoToStep(currentStepIndex); } private void GoToNextStep() { currentStepIndex++; GoToStep(currentStepIndex); } private void GoToStep(int index) { isPaused = false; Pause1.Text = "暂停测试"; Pause1.Style = UIStyle.Red; // 检查是否超出步骤列表范围 if (index >= stepList.Count) { lb_status.Text = "所有流程已完成!"; form2.uiLabel1.Text = "所有流程已完成!"; //btn_NextStep.Enabled = false; //master?.WriteSingleCoil(1, 15, false); master?.WriteSingleCoil(1, 16, false); autoStepTimer?.Stop(); autoStepTimer2?.Stop(); StopVideo(); // 确保视频停止 return; } // 更新选中步骤和状态文本 comboBox1.SelectedIndex = index; string baseStepText = stepList[index]; lb_status.Text = baseStepText; // 更新form2的标签文本 form2.uiLabel1.Text = stepList[index].Contains("大声阅读屏幕显示文字") ? (baseStepText + "\r\n" + CreenValue) : baseStepText; // 处理视频路径(保持原有逻辑) string basePath = AppDomain.CurrentDomain.BaseDirectory; string videoPath = ""; if (isNeedRun) { switch (index) { case 3: videoPath = Path.Combine(basePath, "头部静止.MP4"); break; case 4: videoPath = Path.Combine(basePath, "左右转头.MP4"); break; case 5: videoPath = Path.Combine(basePath, "上下抬头.MP4"); break; case 6: videoPath = Path.Combine(basePath, "大声说话.MP4"); break; case 7: videoPath = Path.Combine(basePath, "头部静止.MP4"); break; default: videoPath = string.Empty; break; } // 标记当前步骤是否为需要延迟执行AddDgv的步骤 bool isDelayAddDgvStep = index > 2 && index < 8; if (isDelayAddDgvStep) { //master?.WriteSingleCoil(1, 16, true); 1 Thread.Sleep(100); // 修复:使用完整的事件处理方法语法 EventHandler handler = null; handler = new EventHandler((s, e) => { if (countdownSeconds <= 1) { AddDgv(); //// 移除事件(避免重复执行) autoStepTimer.Tick -= handler; } }); autoStepTimer.Tick += handler; } else { //master?.WriteSingleCoil(1, 16, false); 2 Thread.Sleep(100); } } else { switch (index) { case 2: videoPath = Path.Combine(basePath, "头部静止.MP4"); break; case 3: videoPath = Path.Combine(basePath, "左右转头.MP4"); break; case 4: videoPath = Path.Combine(basePath, "上下抬头.MP4"); break; case 5: videoPath = Path.Combine(basePath, "大声说话.MP4"); break; case 6: videoPath = Path.Combine(basePath, "头部静止.MP4"); break; default: videoPath = string.Empty; break; } // 标记当前步骤是否为需要延迟执行AddDgv的步骤 bool isDelayAddDgvStep = index > 1 && index < 7; if (isDelayAddDgvStep) { //master?.WriteSingleCoil(1, 16, true); 1 Thread.Sleep(100); // 修复:使用完整的事件处理方法语法 EventHandler handler = null; handler = new EventHandler((s, e) => { if (countdownSeconds <= 1) { AddDgv(); // 移除事件(避免重复执行) autoStepTimer.Tick -= handler; } }); autoStepTimer.Tick += handler; } else { //master?.WriteSingleCoil(1, 16, false); 2 Thread.Sleep(100); } } if (index == 0) { master?.WriteSingleCoil(1, 15, false); } if (index == 1) { autoStepTimer2.Start(); } if (index == 2) { master?.WriteSingleCoil(1, 15, true); } if (index == 7) { master?.WriteSingleCoil(1, 15, false); } // 控制视频播放 if (!string.IsNullOrEmpty(videoPath) && File.Exists(videoPath)) { form2.axWindowsMediaPlayer1.URL = videoPath; form2.axWindowsMediaPlayer1.Ctlcontrols.play(); } else { StopVideo(); } // 新的倒计时逻辑:前三步(0-2)30秒,后续4-9步(3-8)120秒 bool isCountdownStep = index >= 0 && index <= 8; if (isCountdownStep) { // 根据步骤索引设置倒计时秒数,原版前三步30秒,后续120秒 countdownSeconds = index < 2 ? 30 : index == 7 ? 10 : 120; // 更新状态文本,包含倒计时信息 lb_status.Text = $"{baseStepText} 倒计时: {countdownSeconds}秒"; form2.uiLabel1.Text = $"{form2.uiLabel1.Text}\r\n倒计时: {countdownSeconds}秒"; // 启动计时器 autoStepTimer?.Start(); } else { // 不在倒计时范围内,停止计时器 autoStepTimer?.Stop(); } } public void RefreshMainData() { LoadIsNeedRunConfig(); // 先读取配置 InitStepList(); // 再初始化流程步骤和下拉框 } // 新增:停止视频播放并清除路径的辅助方法 private void StopVideo() { form2.axWindowsMediaPlayer1.Ctlcontrols.stop(); form2.axWindowsMediaPlayer1.URL = string.Empty; } public frm_Main() { InitializeComponent(); comboBox1.SelectedIndex = 0; } #region 界面初始化 private void frm_Main_Load(object sender, EventArgs e) { try { Pause1.Text = "暂停测试"; Pause1.Style = UIStyle.Red; ///add by xyy LoadIsNeedRunConfig(); // 先读取配置 InitStepList(); // 再初始化流程步骤和下拉框 //add by xyy 192.168.1.10先改100 modbus_Connect = new Modbus_Connect("192.168.1.10", 502); master = modbus_Connect.GetMaster(); master?.WriteSingleCoil(1, 132, false); Task.Run(() => Read_PLC_Data()); fc = new Function(master); comboBox1.Enabled = false; //form2.axWindowsMediaPlayer1.PlayStateChange += // new AxWMPLib._WMPOCXEvents_PlayStateChangeEventHandler( // axWindowsMediaPlayer1_PlayStateChange); } catch (Exception EX) { MessageBox.Show("控制器连接失败!" + "\n" + "错误原因:" + EX.Message, "错误"); } try { Tsi = new Connect_TSI("COM18", "COM8"); Tsi.Connect(); Tsi.Start_Indoor(); Tsi.Start_Outdoor(); Tsi.Start_bendidoor(); } catch (Exception ex) { MessageBox.Show("光度计连接失败!" + "\n" + "错误原因:" + ex.Message, "错误"); } bool_Stop.OnRisingEdge += bool_Stop_OnRisingEdge; // bool_Start.OnRisingEdge += bool_Start_OnRisingEdge; Task.Run(() => { while (true) { bool_Stop.Value = data_M130_M135[2]; bool_Start.Value = data_M130_M135[0]; //bool_Stop.Value = false;//add by xyy //bool_Stop.Value = true;//add by xyy bool_Stop.CheckRisingEdge(); bool_Start.CheckRisingEdge(); Thread.Sleep(10); } }); tslb_登录用户.Text = loginData.UserName; UserAuthority(); InitializeDgv(); experData.TestStatus = comboBox1.Text; _logger.Log(experData.ExperName, "登陆系统", loginData.UserPower.ToString()); form2.Show(); } void InitializeDgv() { dt_Show.Columns.Add("姓名"); dt_Show.Columns.Add("性别"); dt_Show.Columns.Add("年龄"); dt_Show.Columns.Add("身份证号码"); dt_Show.Columns.Add("工作单位"); dt_Show.Columns.Add("工龄"); dt_Show.Columns.Add("日期"); dt_Show.Columns.Add("时间"); dt_Show.Columns.Add("实验员"); dt_Show.Columns.Add("样品编号"); dt_Show.Columns.Add("样品类别"); dt_Show.Columns.Add("测试状态"); dt_Show.Columns.Add("环境温度(℃)"); dt_Show.Columns.Add("环境湿度(%RH)"); dt_Show.Columns.Add("本底浓度(mg/m³)"); dt_Show.Columns.Add("室内浓度平均值(mg/m³)"); dt_Show.Columns.Add("呼吸器内气溶胶平均值(mg/m³)"); dt_Show.Columns.Add("泄露率(%)"); dgv_expirData.DataSource = dt_Show; dgv_expirData.Font = new System.Drawing.Font("黑体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); dgv_expirData.Columns[12].Width = 250; dgv_expirData.Columns[13].Width = 250; dgv_expirData.Columns[14].Width = 200; dgv_expirData.Columns[15].Width = 250; dgv_expirData.Columns[16].Width = 250; } //void AddDgv() //{ // float _TsiIndoorAgv = 0.0f; // float _TsiOutdoorAgv = 0.0f; // float _Xieloulv = 0.0f; // if (dt_Show.Columns.Count != 0) // { // if (list_Tsi_Indoor_Data.Count != 0 && list_Tsi_Outdoor_Data.Count != 0) // { // _TsiIndoorAgv = list_Tsi_Indoor_Data.Average(); // _TsiOutdoorAgv = list_Tsi_Outdoor_Data.Average(); // _Xieloulv = lsit_XieLoulv.Average(); // } // else // { // _TsiIndoorAgv = 0.0f; // _TsiOutdoorAgv = 0.0f; // _Xieloulv = 0.0f; // } // 口罩泄露测试仪控制系统_定制款.窗体.UserData userData = new 口罩泄露测试仪控制系统_定制款.窗体.UserData(); // if (File.Exists(UserDataFilePath)) // { // string json = File.ReadAllText(UserDataFilePath); // userData = JsonConvert.DeserializeObject<口罩泄露测试仪控制系统_定制款.窗体.UserData>(json); // } // dt_Show.Rows.Add( // userData?.Name, // userData?.Sex, // userData?.Age, // userData?.IdNumber, // userData?.Company, // userData?.CompanyAge, // DateTime.Now.ToString("yyyy-MM-dd"), // DateTime.Now.ToString("HH-mm-ss"), // experData.ExperName, // experData.ExperNum, // experData.ExperType, // comboBox1.Text, // experData.HuanJingWenDu, // experData.HuanJingShiDu, // experData.BenDiNongDu, // _TsiIndoorAgv, // _TsiOutdoorAgv, // _Xieloulv); // this.Invoke(new Action(() => // { // dgv_expirData.Refresh(); // InsertDataToDatabase(); // })); // } // // dt_Show.Rows.Add("1","2","3","4","5","6","7","8","9","10"); //} void AddDgv() { float _TsiIndoorAgv = 0.0f; float _TsiOutdoorAgv = 0.0f; float _Xieloulv = 0.0f; if (dt_Show.Columns.Count != 0) { // 使用最后100秒数据计算(标准要求) int dataCount = Math.Min(list_Tsi_Indoor_Data.Count, list_Tsi_Outdoor_Data.Count); int startIndex = Math.Max(0, dataCount - 100); List last100Indoor = new List(); List last100Outdoor = new List(); for (int i = startIndex; i < dataCount; i++) { last100Indoor.Add(list_Tsi_Indoor_Data[i]); last100Outdoor.Add(list_Tsi_Outdoor_Data[i]); } if (last100Indoor.Count > 0 && last100Outdoor.Count > 0) { _TsiIndoorAgv = last100Indoor.Average(); _TsiOutdoorAgv = last100Outdoor.Average(); // 使用连续采样法计算公式 _Xieloulv = experData.ContinuousSamplingLeakageRate( last100Indoor, last100Outdoor, experData.BenDiNongDu, experData.SamplingFlowRate, experData.DryingFlowRate); } else { _TsiIndoorAgv = 0.0f; _TsiOutdoorAgv = 0.0f; _Xieloulv = 0.0f; } 口罩泄露测试仪控制系统_定制款.窗体.UserData userData = new 口罩泄露测试仪控制系统_定制款.窗体.UserData(); if (File.Exists(UserDataFilePath)) { string json = File.ReadAllText(UserDataFilePath); userData = JsonConvert.DeserializeObject<口罩泄露测试仪控制系统_定制款.窗体.UserData>(json); } dt_Show.Rows.Add( userData?.Name, userData?.Sex, userData?.Age, userData?.IdNumber, userData?.Company, userData?.CompanyAge, DateTime.Now.ToString("yyyy-MM-dd"), DateTime.Now.ToString("HH-mm-ss"), experData.ExperName, experData.ExperNum, experData.ExperType, comboBox1.Text, experData.HuanJingWenDu.ToString("F1"), experData.HuanJingShiDu.ToString("F1"), experData.BenDiNongDu.ToString("F3"), _TsiIndoorAgv.ToString("F3"), _TsiOutdoorAgv.ToString("F3"), _Xieloulv.ToString("F2")); this.Invoke(new Action(() => { dgv_expirData.Refresh(); InsertDataToDatabase(); })); } } private void InsertDataToDatabase() { float _Xieloulv = 0.0f; _Xieloulv = experData.ContinuousSamplingLeakageRate( list_Tsi_Indoor_Data, list_Tsi_Outdoor_Data, experData.BenDiNongDu, experData.SamplingFlowRate, experData.DryingFlowRate); // 读取用户数据 口罩泄露测试仪控制系统_定制款.窗体.UserData userData = new 口罩泄露测试仪控制系统_定制款.窗体.UserData(); if (File.Exists(UserDataFilePath)) { string json = File.ReadAllText(UserDataFilePath); userData = JsonConvert.DeserializeObject<口罩泄露测试仪控制系统_定制款.窗体.UserData>(json); } // 创建 SQLite 连接并初始化 string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data.db"); SQLiteConnection _db = new SQLiteConnection(dbPath); _db.CreateTable(); // 构建 Experiment 对象 Experiment experiment = new Experiment { Name = userData?.Name, Sex = userData?.Sex, Age = userData?.Age != null ? int.Parse(userData.Age) : (int?)null, IdNumber = userData?.IdNumber, Company = userData?.Company, CompanyAge = userData?.CompanyAge != null ? int.Parse(userData.CompanyAge) : (int?)null, Date = DateTime.Now.ToString("yyyy-MM-dd"), Time = DateTime.Now.ToString("HH-mm-ss"), ExperName = experData.ExperName, //ExperNum = experData.ExperNum, ExperType = experData.ExperType, MaskType = userData.FullMaskType, TestStatus = comboBox1.Text, HuanJingWenDu = experData.HuanJingWenDu, HuanJingShiDu = experData.HuanJingShiDu, BenDiNongDu = experData.BenDiNongDu, TsiIndoorAgv = list_Tsi_Indoor_Data.Any() ? list_Tsi_Indoor_Data.Average() : 0, TsiOutdoorAgv = list_Tsi_Outdoor_Data.Any() ? list_Tsi_Outdoor_Data.Average() : 0, Xieloulv = _Xieloulv //lsit_XieLoulv.Any() ? lsit_XieLoulv.Average() : 0 }; // 插入数据 try { _db.Insert(experiment); } catch (Exception ex) { // 处理异常,这里可以根据实际需求进行日志记录或者弹窗提示等 Console.WriteLine($"插入数据失败: {ex.Message}"); } finally { _db.Close(); } } //private void InsertDataToDatabase() //{ // // 读取用户数据 // 口罩泄露测试仪控制系统_定制款.窗体.UserData userData = new 口罩泄露测试仪控制系统_定制款.窗体.UserData(); // if (File.Exists(UserDataFilePath)) // { // string json = File.ReadAllText(UserDataFilePath); // userData = JsonConvert.DeserializeObject<口罩泄露测试仪控制系统_定制款.窗体.UserData>(json); // } // // 创建 SQLite 连接并初始化 // string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data.db"); // SQLiteConnection _db = new SQLiteConnection(dbPath); // _db.CreateTable(); // // 构建 Experiment 对象 // Experiment experiment = new Experiment // { // Name = userData?.Name, // Sex = userData?.Sex, // Age = userData?.Age != null ? int.Parse(userData.Age) : (int?)null, // IdNumber = userData?.IdNumber, // Company = userData?.Company, // CompanyAge = userData?.CompanyAge != null ? int.Parse(userData.CompanyAge) : (int?)null, // Date = DateTime.Now.ToString("yyyy-MM-dd"), // Time = DateTime.Now.ToString("HH-mm-ss"), // ExperName = experData.ExperName, // //ExperNum = experData.ExperNum, // ExperType = experData.ExperType, // MaskType = userData.FullMaskType, // TestStatus = comboBox1.Text, // HuanJingWenDu = experData.HuanJingWenDu, // HuanJingShiDu = experData.HuanJingShiDu, // BenDiNongDu = experData.BenDiNongDu, // TsiIndoorAgv = list_Tsi_Indoor_Data.Any() ? list_Tsi_Indoor_Data.Average() : 0, // TsiOutdoorAgv = list_Tsi_Outdoor_Data.Any() ? list_Tsi_Outdoor_Data.Average() : 0, // Xieloulv = lsit_XieLoulv.Any() ? lsit_XieLoulv.Average() : 0 // }; // // 插入数据 // try // { // _db.Insert(experiment); // } // catch (Exception ex) // { // // 处理异常,这里可以根据实际需求进行日志记录或者弹窗提示等 // Console.WriteLine($"插入数据失败: {ex.Message}"); // } // finally // { // _db.Close(); // } //} Random rd = new Random(); private void timer_date_Tick(object sender, EventArgs e) { //刷新时间 tslb_time.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //刷新界面数据 Resfh_UI_Data(); } private void Resfh_UI_Data() { tslb_实验类型.Text = experData.ExperType; tslb_实验编号.Text = experData.ExperNum; lb_YangNongdu.Text = experData.O2NongDu.ToString("F2"); lb_shiDu.Text = experData.HuanJingShiDu.ToString("F2"); lb_wenDu.Text = experData.HuanJingWenDu.ToString("F2"); lb_CO2_Indoor.Text = experData.CO2NongDu_Indoor.ToString("F2"); lb_co2_OutDoor.Text = experData.Mask_CO2NongDu.ToString("F2"); lb_tsi_Indoor.Text = experData.InDoor_TSINongDu.ToString("F3"); lb_tsi_OutDoor.Text = experData.Mask_TSINongDu.ToString("F3"); lb_泄露率.Text = experData.XieLouLv.ToString("F2"); label16.Text = TestTime.ToString(); if (data_M6_M7[1]) { btn_Light.Text = "照明关"; btn_Light.ForeColor = System.Drawing.Color.Red; } else { btn_Light.Text = "照明开"; btn_Light.ForeColor = System.Drawing.Color.Black; } if (data_M6_M7[0]) { btn_风机.Text = "风机关"; btn_风机.ForeColor = System.Drawing.Color.Red; } else { btn_风机.Text = "风机开"; btn_风机.ForeColor = System.Drawing.Color.Black; } } //用户权限判断 void UserAuthority() { switch (loginData.UserPower) { case 0: ts_新建实验.Enabled = true; ts_参数设置.Enabled = true; ts_实验数据.Enabled = true; ts_审计追踪.Enabled = true; ts_用户管理.Enabled = true; tsbtn_新增用户.Enabled = true; toolStripButton1.Enabled = true; break; case 1: ts_新建实验.Enabled = true; ts_参数设置.Enabled = true; ts_实验数据.Enabled = true; ts_审计追踪.Enabled = true; ts_用户管理.Enabled = false; tsbtn_新增用户.Enabled = true; toolStripButton1.Enabled = false; break; case 2: ts_新建实验.Enabled = true; ts_参数设置.Enabled = false; ts_实验数据.Enabled = true; ts_审计追踪.Enabled = false; ts_用户管理.Enabled = false; tsbtn_新增用户.Enabled = false; toolStripButton1.Enabled = false; break; case 3: ts_新建实验.Enabled = true; ts_参数设置.Enabled = false; ts_实验数据.Enabled = false; ts_审计追踪.Enabled = false; ts_用户管理.Enabled = false; tsbtn_新增用户.Enabled = false; toolStripButton1.Enabled = false; break; case 4: ts_新建实验.Enabled = false; ts_参数设置.Enabled = false; ts_实验数据.Enabled = false; ts_审计追踪.Enabled = false; ts_用户管理.Enabled = false; tsbtn_新增用户.Enabled = false; toolStripButton1.Enabled = false; break; } } #endregion #region 画面切换 private void tsbtn_新增用户_Click(object sender, EventArgs e) { frm_AddUser addUser = new frm_AddUser(); addUser.ShowDialog(); } private void tsbtn_切换用户_Click(object sender, EventArgs e) { this.Close(); } private void ts_用户管理_Click(object sender, EventArgs e) { frm_UserSetting userSetting = new frm_UserSetting(); userSetting.ShowDialog(); } private void ts_审计追踪_Click(object sender, EventArgs e) { frm_Audit_Trail audit_Trail = new frm_Audit_Trail(); audit_Trail.ShowDialog(); } private void ts_新建实验_Click(object sender, EventArgs e) { frm_AddNewExperiment addUser = new frm_AddNewExperiment(this); addUser.ShowDialog(); } private void ts_参数设置_Click(object sender, EventArgs e) { frm_ExperSetting experSetting = new frm_ExperSetting(master); experSetting.ShowDialog(); } private void ts_实验数据_Click(object sender, EventArgs e) { frm_ExperData experData = new frm_ExperData(); experData.ShowDialog(); } #endregion #region private void frm_Main_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e) { Tsi.Stop_Indoor(); Tsi.Stop_Outdoor(); Tsi.Stop_bendidoor(); Tsi.Disconnect(); modbus_Connect.DisConnect(); this.Dispose(); } #endregion #region PLC数据读取 ushort[] data_D300_D320 = new ushort[21]; ushort[] data_D250_D299 = new ushort[50]; ushort[] data_D400_D499 = new ushort[100]; ushort[] data_D500_D507 = new ushort[8]; bool[] data_M6_M7 = new bool[2]; bool[] data_M60_M65 = new bool[6]; bool[] data_M90_M95 = new bool[6]; bool[] data_M130_M135 = new bool[6]; DataChange dc = new DataChange(); int TestTime = 0; void Read_PLC_Data() { while (true) { if (this.IsDisposed || master == null) { break; } try { //读取PLC数据 data_D300_D320 = master?.ReadHoldingRegisters(1, 300, 20); data_D250_D299 = master?.ReadHoldingRegisters(1, 250, 50); data_D400_D499 = master?.ReadHoldingRegisters(1, 400, 100); data_D500_D507 = master?.ReadHoldingRegisters(1, 500, 8); data_M6_M7 = master?.ReadCoils(1, 6, 2); data_M60_M65 = master?.ReadCoils(1, 60, 6); data_M90_M95 = master?.ReadCoils(1, 90, 6); data_M130_M135 = master?.ReadCoils(1, 130, 6); //显示PLC数据 experData.LiuLiang = dc.UshortToFloat(data_D300_D320[1], data_D300_D320[0]); experData.Mask_CO2NongDu = dc.UshortToFloat(data_D300_D320[3], data_D300_D320[2]); experData.CO2NongDu_Indoor = dc.UshortToFloat(data_D300_D320[5], data_D300_D320[4]); experData.O2NongDu = dc.UshortToFloat(data_D300_D320[7], data_D300_D320[6]); experData.HuanJingWenDu = dc.UshortToFloat(data_D300_D320[9], data_D300_D320[8]); experData.HuanJingShiDu = dc.UshortToFloat(data_D300_D320[11], data_D300_D320[10]); experData.InDoor_TSINongDu = Tsi.Indoor_Data;//modified by xyy experData.Mask_TSINongDu = Tsi.Outdoor_Data;//modified by xyy //experData.BenDiNongDu = Tsi.sp_bendiData.ToFloat(); TestTime = dc.UshortToInt1(data_D250_D299[0], data_D250_D299[1]); Thread.Sleep(100); } catch { break; } } } #endregion private void btn_Light_Click(object sender, EventArgs e) { fc.BtnClickFunction(Function.ButtonType.切换型, 7); } private void btn_风机_Click(object sender, EventArgs e) { fc.BtnClickFunction(Function.ButtonType.切换型, 6); } private void toolStripButton1_Click(object sender, EventArgs e) { frm_Motor _Motor = new frm_Motor(master); _Motor.ShowDialog(); } private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { int idx = comboBox1.SelectedIndex; if (idx < 0 || idx >= stepList.Count) return; string step = stepList[idx]; lb_status.Text = step; // 判断是否为“朗读文字”步骤 if (step.Contains("大声阅读屏幕显示文字")) { form2.uiLabel1.Text = step; form2.uiLabel1.Text += CreenValue; } else { form2.uiLabel1.Text = step; } _logger.Log(loginData.UserName, $"切换了实验状态为【{step}】", loginData.UserPower.ToString()); } #region 实验数据记录 List>> list_AllDaata = new List>>();//所有数据 List> list_AllTime = new List>();//所有时间 List> liatAll = new List>();//总数据汇总 List list_Time = new List();//时间 List list_Tsi_Indoor_Data = new List();//室内气溶胶浓度 List list_Tsi_Outdoor_Data = new List();//口罩内气溶胶浓度 List lsit_XieLoulv = new List();//泄露率 List list_CO2_Indoor_Data = new List();//室内CO2浓度 List list_CO2_Outdoor_Data = new List();//口罩内CO2浓度 List list_O2_Data = new List();//氧气浓度 List list_HuanJingWenDu_Data = new List();//环境内温度 List list_HuanJingShiDu_Data = new List();//环境湿度 // 修改timer_UpdataChart_Tick - 实时泄漏率计算 private void timer_UpdataChart_Tick(object sender, EventArgs e) { int currindex = 0; if (isNeedRun) { currindex = 3; } else { currindex = 2; } if (data_M130_M135[1] && currentStepIndex >= currindex) { list_Time.Add(DateTime.Now.ToString("HH:mm:ss")); list_Tsi_Indoor_Data.Add(experData.InDoor_TSINongDu); list_Tsi_Outdoor_Data.Add(experData.Mask_TSINongDu); // 使用最后100秒数据计算实时泄漏率 int dataCount = Math.Min(list_Tsi_Indoor_Data.Count, list_Tsi_Outdoor_Data.Count); int startIndex = Math.Max(0, dataCount - 100); List last100Indoor = new List(); List last100Outdoor = new List(); for (int i = startIndex; i < dataCount; i++) { last100Indoor.Add(list_Tsi_Indoor_Data[i]); last100Outdoor.Add(list_Tsi_Outdoor_Data[i]); } if (last100Indoor.Count > 0 && last100Outdoor.Count > 0) { experData.XieLouLv = experData.ContinuousSamplingLeakageRate( last100Indoor, last100Outdoor, experData.BenDiNongDu, experData.SamplingFlowRate, experData.DryingFlowRate); } lsit_XieLoulv.Add(experData.XieLouLv); list_CO2_Indoor_Data.Add(experData.CO2NongDu_Indoor); list_CO2_Outdoor_Data.Add(experData.Mask_CO2NongDu); list_O2_Data.Add(experData.O2NongDu); list_HuanJingWenDu_Data.Add(experData.HuanJingWenDu); list_HuanJingShiDu_Data.Add(experData.HuanJingShiDu); chart_TSI.Series[0].Points.DataBindXY(list_Time, list_Tsi_Indoor_Data); chart_TSI.Series[1].Points.DataBindXY(list_Time, list_Tsi_Outdoor_Data); chart_TSI.Series[2].Points.DataBindXY(list_Time, lsit_XieLoulv); chart2.Series[0].Points.DataBindXY(list_Time, list_CO2_Indoor_Data); chart2.Series[1].Points.DataBindXY(list_Time, list_CO2_Outdoor_Data); chart2.Series[2].Points.DataBindXY(list_Time, list_O2_Data); chart2.Series[3].Points.DataBindXY(list_Time, list_HuanJingWenDu_Data); chart2.Series[4].Points.DataBindXY(list_Time, list_HuanJingShiDu_Data); } } //结束时写入Excel public static int comindex; void bool_Stop_OnRisingEdge() { float xieLouLue_Agv = 0.0f; int selectIndex = 0; if (lsit_XieLoulv.Count != 0) xieLouLue_Agv = lsit_XieLoulv.Average(); if (xieLouLue_Agv > experData.ExperMaskType) { form2.Invoke(new Action(() => { form2.uiLabel1.Text = "测试失败!请等待指令重做实验!"; })); MessageBox.Show("泄露率达到" + xieLouLue_Agv.ToString("F2") + "\n" + "不符合标准!请重做实验", "失败", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // var result = MessageBox.Show("测试完成!" + "\n" + "是否保存数据?", "完成!", MessageBoxButtons.YesNo); //如果点Yes,则写入Excel //如果点No,则不写入Excel List _tempTimeList = new List(list_Time); List _templist_Tsi_Indoor_Data = new List(list_Tsi_Indoor_Data); List _templist_Tsi_Outdoor_Data = new List(list_Tsi_Outdoor_Data); List _templsit_XieLoulv = new List(lsit_XieLoulv); List _templist_CO2_Indoor_Data = new List(list_CO2_Indoor_Data); List _templist_CO2_Outdoor_Data = new List(list_CO2_Outdoor_Data); List _templist_O2_Data = new List(list_O2_Data); List _templist_HuanJingWenDu_Data = new List(list_HuanJingWenDu_Data); List _templist_HuanJingShiDu_Data = new List(list_HuanJingShiDu_Data); liatAll.Add(_templist_Tsi_Indoor_Data); liatAll.Add(_templist_Tsi_Outdoor_Data); liatAll.Add(_templsit_XieLoulv); liatAll.Add(_templist_CO2_Indoor_Data); liatAll.Add(_templist_CO2_Outdoor_Data); liatAll.Add(_templist_O2_Data); liatAll.Add(_templist_HuanJingWenDu_Data); liatAll.Add(_templist_HuanJingShiDu_Data); List> _templiatAll = new List>(liatAll); list_AllDaata.Add(_templiatAll); list_AllTime.Add(_tempTimeList); //AddDgv(); this.Invoke(new Action(() => { selectIndex = comboBox1.SelectedIndex; })); //if (selectIndex < stepList.Count - 1) //{ // form2.Invoke(new Action(() => // { // form2.uiLabel1.Text = "本次动作测试完成请等待10s后进行下一动作!"; // })); // Task.Delay(10000).Wait(); // this.Invoke(new Action(() => //{ // comboBox1.SelectedIndex += 1; // experData.TestStatus = comboBox1.Text; // btn_Start.PerformClick(); //})); //} //else //{ // MessageBox.Show("所有动作测试完成!"); //} } } #endregion #region 按钮事件 // 修改btn_Start_Click - 加载配置参数 private void btn_Start_Click(object sender, EventArgs e) { string configPath = Path.Combine(Application.StartupPath, "UserData.json"); if (File.Exists(configPath)) { var json = File.ReadAllText(configPath); dynamic config = Newtonsoft.Json.JsonConvert.DeserializeObject(json); experData.BenDiNongDu = config.BenDiNongdu; experData.SamplingFlowRate = config.SamplingFlowRate ?? 2.0f; experData.DryingFlowRate = config.DryingFlowRate ?? 1.0f; experData.ExperName = config.Category; } isPaused = false; Pause1.Text = "暂停测试"; Pause1.Style = UIStyle.Red; // 清空数据 list_Time.Clear(); list_Tsi_Indoor_Data.Clear(); list_Tsi_Outdoor_Data.Clear(); lsit_XieLoulv.Clear(); list_CO2_Indoor_Data.Clear(); list_CO2_Outdoor_Data.Clear(); list_O2_Data.Clear(); list_HuanJingWenDu_Data.Clear(); list_HuanJingShiDu_Data.Clear(); liatAll.Clear(); fc?.BtnClickFunction(Function.ButtonType.复归型, 130); LoadIsNeedRunConfig(); InitStepList(); InitAutoStepTimer(); InitAutoStepTimer2(); StartProcess(); _logger.Log(loginData.UserName, "点击了【测试开始】按钮", loginData.UserPower.ToString()); } private void btn_Stop_Click(object sender, EventArgs e) { isPaused = false; Pause1.Text = "暂停测试"; Pause1.Style = UIStyle.Red; form2.uiLabel1.Text = "测试停止"; fc?.BtnClickFunction(Function.ButtonType.复归型, 133); master?.WriteSingleCoil(1, 16, false); master?.WriteSingleCoil(1, 15, false); form2.axWindowsMediaPlayer1.URL = string.Empty; StopVideo(); autoStepTimer?.Stop(); autoStepTimer2?.Stop(); lb_status.Text = "测试停止"; _logger.Log(loginData.UserName, "点击了【测试停止】按钮", loginData.UserPower.ToString()); } //private void btn_SaveData_Click(object sender, EventArgs e) //{ // experExcleHelper.ExportToExcel(dt_Show, list_AllTime, list_AllDaata); // _logger.Log(loginData.UserName, "导出了实验数据", loginData.UserPower.ToString()); //} // 修正后的按钮点击事件 private void btn_SaveData_Click(object sender, EventArgs e) { // 使用支持按钮选择的重载方法(message, title, style, buttons) bool isYes = Sunny.UI.UIMessageBox.Show( "选择导出类型:\n确定 - 本次实验\n取消 - 历史实验", "导出选择", Sunny.UI.UIStyle.Blue, // 使用指定样式 Sunny.UI.UIMessageBoxButtons.OKCancel // 明确指定按钮类型 ); if (isYes) // 注意:此重载返回bool值,true表示Yes,false表示No { ExportLastExperimentToExcel(); } else { ExportSelectedHistoricalExperiments(); } _logger.Log(loginData.UserName, "导出了实验数据", loginData.UserPower.ToString()); } private void ExportLastExperimentToExcel() { string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data.db"); using (var db = new SQLiteConnection(dbPath)) { try { var latestRecord = db.Table() .Where(x => x.IdNumber != null && x.IdNumber != "" && x.Date != null && x.Date != "" && x.Time != null && x.Time != "") .OrderByDescending(x => x.Date) .ThenByDescending(x => x.Time) .FirstOrDefault(); if (latestRecord == null || string.IsNullOrEmpty(latestRecord.IdNumber)) { UIMessageBox.Show("没有找到有效的实验数据!", "提示", Sunny.UI.UIStyle.Blue); return; } string targetIdNumber = latestRecord.IdNumber; string targetName = latestRecord.Name ?? "未知姓名"; var latestRecordid = latestRecord.Id; var startindex = latestRecordid - 5; var experimentRecords = db.Table() .Where(x => x.IdNumber == targetIdNumber && x.Id > startindex) .OrderBy(x => x.Date) .ThenBy(x => x.Time) .ToList(); if (experimentRecords.Count == 0) { UIMessageBox.Show($"未找到身份证号{targetIdNumber}的实验数据!", "提示", Sunny.UI.UIStyle.Blue); return; } using (var package = new ExcelPackage()) { var worksheet = package.Workbook.Worksheets.Add($"[{targetName}]{targetIdNumber}_实验数据"); string maskType = experimentRecords.Max(f => f.MaskType); string maskTypeName = string.Empty; if (maskType != null && (maskType.Contains("半面罩") || maskType.Contains("全面罩"))) { maskTypeName = "IL"; } else if (maskType != null && (maskType.Contains("随弃式"))) { maskTypeName = "TIL"; } maskTypeName = $"泄漏率{maskTypeName}(%)"; // 中文表头映射 var chineseHeaders = new Dictionary { { "Name", "姓名" }, { "Sex", "性别" }, { "Age", "年龄" }, { "IdNumber", "身份证号码" }, { "Company", "工作单位" }, { "CompanyAge", "工龄" }, { "Date", "日期" }, { "Time", "时间" }, { "ExperName", "实验员" }, { "ExperType", "样品类别" }, { "MaskType", "面罩类型" }, { "TestStatus", "测试状态" }, { "HuanJingWenDu", "环境温度(℃)" }, { "HuanJingShiDu", "环境湿度(%RH)" }, { "BenDiNongDu", "本底浓度(mg/m³)" }, { "TsiIndoorAgv", "室内浓度平均值(mg/m³)" }, { "TsiOutdoorAgv", "呼吸器内气溶胶平均浓度(mg/m³)" }, { "Xieloulv", $"{maskTypeName}" }, { "TotalXieloulv", "总泄漏率(%)" }, { "OverallTotalXieloulv", "总体总泄漏率(%)" } // 新增总体总泄露率表头 }; var properties = typeof(Experiment).GetProperties().ToList(); // 计算5个动作的泄露率平均值(过滤无效的0值) var validXieloulvList = experimentRecords .Where(r => r.Xieloulv > 0) // 排除泄露率为0的无效数据 .Select(r => r.Xieloulv) .ToList(); var totalXieloulv = validXieloulvList.Any() ? validXieloulvList.Average() : 0; // === 新增:计算总体总泄露率(5个动作的平均值)=== var fiveActionRecords = experimentRecords .Where(r => r.Xieloulv > 0) // 过滤有效数据 .Take(5) // 取前5个动作 .ToList(); var overallTotalXieloulv = fiveActionRecords.Any() ? fiveActionRecords.Average(r => r.Xieloulv) : 0; // === 新增结束 === // 写入中文表头 int colIndex = 1; foreach (var prop in properties) { worksheet.Cells[1, colIndex].Value = chineseHeaders.ContainsKey(prop.Name) ? chineseHeaders[prop.Name] : prop.Name; worksheet.Cells[1, colIndex].Style.Font.Bold = true; colIndex++; } // 单独写入"总泄漏率(%)"表头 worksheet.Cells[1, colIndex].Value = chineseHeaders["TotalXieloulv"]; worksheet.Cells[1, colIndex].Style.Font.Bold = true; colIndex++; // 单独写入"总体总泄漏率(%)"表头 worksheet.Cells[1, colIndex].Value = chineseHeaders["OverallTotalXieloulv"]; worksheet.Cells[1, colIndex].Style.Font.Bold = true; // 写入数据行 int rowIndex = 2; foreach (var record in experimentRecords) { colIndex = 1; // 写入原有数据列 foreach (var prop in properties) { if (prop.Name == "ExperNum") { continue; } worksheet.Cells[rowIndex, colIndex].Value = prop.GetValue(record); if (prop.PropertyType == typeof(float) || prop.PropertyType == typeof(double)) { worksheet.Cells[rowIndex, colIndex].Style.Numberformat.Format = "0.00"; } colIndex++; } // 写入总泄漏率(每行都写入) worksheet.Cells[rowIndex, colIndex].Value = record.Xieloulv; worksheet.Cells[rowIndex, colIndex].Style.Numberformat.Format = "0.00"; colIndex++; // 写入总体总泄漏率(仅在最后一行显示) if (rowIndex == experimentRecords.Count + 1) { worksheet.Cells[rowIndex, colIndex].Value = overallTotalXieloulv; worksheet.Cells[rowIndex, colIndex].Style.Numberformat.Format = "0.00"; // 给总体总泄露率行添加背景色,方便识别 worksheet.Cells[rowIndex, colIndex].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; worksheet.Cells[rowIndex, colIndex].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue); } rowIndex++; } // 调整列宽 worksheet.Column(13).Width = 25; // 环境温度(℃) worksheet.Column(14).Width = 25; // 环境湿度(%RH) worksheet.Column(15).Width = 20; // 本底浓度(mg/m³) worksheet.Column(16).Width = 25; // 室内浓度平均值(mg/m³) worksheet.Column(17).Width = 25; // 口罩内浓度平均值(mg/m³) worksheet.Column(18).Width = 25; // 泄漏率IL/TIL(%) worksheet.Column(19).Width = 25; // 总泄漏率(%) worksheet.Column(20).Width = 25; // 总体总泄漏率(%)(新增列) SaveExcelFile(package, $"[{targetName}]的实验数据(共{experimentRecords.Count}条,总泄露率:{totalXieloulv:F2}%,总体总泄露率:{overallTotalXieloulv:F2}%)导出成功!", $"[{targetName}]{targetIdNumber}_实验数据"); } } catch (Exception ex) { UIMessageBox.Show($"导出失败: {ex.Message}", "错误", Sunny.UI.UIStyle.Blue); } } } private void ExportSelectedHistoricalExperiments() { string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data.db"); using (var db = new SQLiteConnection(dbPath)) { try { var personList = db.Table() .Where(x => x.IdNumber != null && x.IdNumber != "" && x.Name != null && x.Name != "") .Select(x => new { x.IdNumber, x.Name, x.Date, x.Time, x.Id, x.MaskType }) .OrderByDescending(x => x.Id).ThenByDescending(s => s.Date).ThenByDescending(s => s.Time) .ToList(); if (personList == null || personList.Count == 0) { UIMessageBox.Show("没有找到历史实验数据!", "提示", Sunny.UI.UIStyle.Blue); return; } string maskType = personList.Max(f => f.MaskType); string maskTypeName = string.Empty; if (maskType != null && (maskType.Contains("半面罩") || maskType.Contains("全面罩"))) { maskTypeName = "IL"; } else if (maskType != null && (maskType.Contains("随弃式"))) { maskTypeName = "TIL"; } maskTypeName = $"泄漏率{maskTypeName}(%)"; var displayList = personList.Select(p => { string idLast4 = p.IdNumber; return $"({p.Id})({p.Date} {p.Time})({p.Name})({idLast4})"; }).ToList(); var selectForm = new UISelectForm("选择要导出的人员", displayList); if (selectForm.ShowDialog() == DialogResult.OK && selectForm.SelectedItems.Count > 0) { var selectedIds = new List(); foreach (var selectedItem in selectForm.SelectedItems) { var itemStr = selectedItem.ToString(); var first = itemStr.IndexOf("(") + 1; var second = itemStr.IndexOf(")"); var nameStart = itemStr.LastIndexOf("(") + 1; var nameEnd = itemStr.LastIndexOf(")"); var idStart = itemStr.LastIndexOf("(") + 1; var idEnd = itemStr.LastIndexOf(")"); if (nameStart < nameEnd && idStart < idEnd) { var id = itemStr.Substring(first, second - first); var name = itemStr.Substring(nameStart, nameEnd - nameStart); var idNumber = itemStr.Substring(idStart, idEnd - idStart); var match = personList.FirstOrDefault(p => p.Name == name && p.IdNumber == idNumber); //if (match != null && !selectedIds.Contains(match.IdNumber)) //{ selectedIds.Add(id.ToString()); //} } } if (selectedIds.Count == 0) { UIMessageBox.Show("未匹配到有效人员数据!", "提示", Sunny.UI.UIStyle.Blue); return; } using (var package = new ExcelPackage()) { var worksheet = package.Workbook.Worksheets.Add($"选中的{selectedIds.Count / 5}位人员实验数据!"); // 中文表头映射(保持不变) var chineseHeaders = new Dictionary { { "Name", "姓名" }, { "Sex", "性别" }, { "Age", "年龄" }, { "IdNumber", "身份证号码" }, { "Company", "工作单位" }, { "CompanyAge", "工龄" }, { "Date", "日期" }, { "Time", "时间" }, { "ExperName", "实验员" }, { "ExperType", "样品类别" }, { "MaskType", "面罩类型" }, { "TestStatus", "测试状态" }, { "HuanJingWenDu", "环境温度(℃)" }, { "HuanJingShiDu", "环境湿度(%RH)" }, { "BenDiNongDu", "本底浓度(mg/m³)" }, { "TsiIndoorAgv", "室内浓度平均值(mg/m³)" }, { "TsiOutdoorAgv", "呼吸器内气溶胶平均浓度(mg/m³)" }, { "Xieloulv", $"{maskTypeName}" }, { "TotalXieloulv", "总体总泄漏率(%)" } }; var properties = typeof(Experiment).GetProperties().ToList(); var hiddenColumns = new List { "Sex", "Age", "Company", "CompanyAge" }; var visibleProperties = properties.Where(p => !hiddenColumns.Contains(p.Name)).ToList(); // 1. 写入中文表头(保持不变) int colIndex = 1; foreach (var prop in visibleProperties) { worksheet.Cells[1, colIndex].Value = chineseHeaders.ContainsKey(prop.Name) ? chineseHeaders[prop.Name] : prop.Name; worksheet.Cells[1, colIndex].Style.Font.Bold = true; colIndex++; } // 单独写入"总泄露率"表头(最后一列) worksheet.Cells[1, colIndex].Value = chineseHeaders["TotalXieloulv"]; worksheet.Cells[1, colIndex].Style.Font.Bold = true; var totalColIndex = colIndex; // 2. 按人员写入数据,并计算每人的总泄露率(保持不变) int rowIndex = 2; var allActionLeakRates = new List(); // 存储所有动作的泄露率 var allPersonTotalLeakRates = new List(); // 存储所有人的总泄露率 List ids = selectedIds.ConvertAll(int.Parse); var lsit = db.Table() .Where(x => ids.Contains(x.Id)) .OrderBy(x => x.Date) .ThenBy(x => x.Time) .ToList(); var selidNumber = lsit.Select(x => x.IdNumber).Distinct().ToList(); foreach (var id in selidNumber) { var personExperiments = lsit .Where(x => x.IdNumber == id) .OrderBy(x => x.Date) .ThenBy(x => x.Time) .ToList(); if (!personExperiments.Any()) continue; // 计算当前人员的总泄露率(过滤无效的0值) var validLeakRates = personExperiments .Where(exp => exp.Xieloulv > 0) .Select(exp => exp.Xieloulv) .ToList(); var personTotalLeakRate = validLeakRates.Any() ? validLeakRates.Average() : 0; // 收集所有动作泄露率和人员总泄露率 allActionLeakRates.AddRange(validLeakRates); allPersonTotalLeakRates.Add(personTotalLeakRate); // 写入当前人员的每条实验记录(保持不变) foreach (var exp in personExperiments) { colIndex = 1; foreach (var prop in visibleProperties) { if (prop.Name == "ExperNum") continue; worksheet.Cells[rowIndex, colIndex].Value = prop.GetValue(exp); if (prop.PropertyType == typeof(float) || prop.PropertyType == typeof(double)) { worksheet.Cells[rowIndex, colIndex].Style.Numberformat.Format = "0.00"; } colIndex++; } worksheet.Cells[rowIndex, totalColIndex].Value = ""; rowIndex++; } // 3. 在当前人员数据末尾添加"总泄露率"汇总行(保持不变) colIndex = 1; worksheet.Cells[rowIndex, 1].Value = $"{personExperiments[0].Name}(汇总)"; worksheet.Cells[rowIndex, 1, rowIndex, visibleProperties.Count].Merge = true; worksheet.Cells[rowIndex, 1].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center; worksheet.Cells[rowIndex, totalColIndex].Value = personTotalLeakRate; worksheet.Cells[rowIndex, totalColIndex].Style.Numberformat.Format = "0.00"; worksheet.Cells[rowIndex, 1, rowIndex, totalColIndex].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; worksheet.Cells[rowIndex, 1, rowIndex, totalColIndex].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightSkyBlue); rowIndex++; // 人员之间添加空行 rowIndex++; } // 4. 在表格最下方添加统计行(新增部分) if (selectedIds.Count > 0) { //// 获取主要口罩类型(假设同一组使用相同类型的口罩) //string mainMaskType = db.Table() // .Where(x => selectedIds.Contains(x.Id)) // .Select(x => x.MaskType) // .FirstOrDefault() ?? ""; var (actionStandard, personStandard) = GetMaskTypeStandards(maskType); // 50个动作合格性统计 var validActions = allActionLeakRates.Where(r => r > 0).Take(50).ToList(); int qualifiedActionCount = validActions.Count(r => r <= actionStandard); bool isActionsQualified = validActions.Count >= 50 && qualifiedActionCount >= 46; // 10人总体合格性统计 var validPersons = allPersonTotalLeakRates.Take(10).ToList(); int qualifiedPersonCount = validPersons.Count(r => r <= personStandard); bool isPersonsQualified = validPersons.Count >= 10 && qualifiedPersonCount >= 8; // 添加50个动作合格性统计行 colIndex = 1; worksheet.Cells[rowIndex, 1].Value = "50个动作是否合格"; worksheet.Cells[rowIndex, 1, rowIndex, visibleProperties.Count].Merge = true; worksheet.Cells[rowIndex, 1].Style.Font.Bold = true; string actionResult = isActionsQualified ? "合格" : "不合格"; if (selectedIds.Count < 50) { actionResult = $"数据不足({validActions.Count}/50个动作)"; } else { actionResult = $"{actionResult}({qualifiedActionCount}/50个动作)"; } worksheet.Cells[rowIndex, totalColIndex].Value = actionResult; worksheet.Cells[rowIndex, totalColIndex].Style.Font.Color.SetColor( isActionsQualified ? System.Drawing.Color.Green : System.Drawing.Color.Red); worksheet.Cells[rowIndex, 1, rowIndex, totalColIndex].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; worksheet.Cells[rowIndex, 1, rowIndex, totalColIndex].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightYellow); rowIndex++; // 添加10人总体合格性统计行 colIndex = 1; worksheet.Cells[rowIndex, 1].Value = "10人总体是否合格"; worksheet.Cells[rowIndex, 1, rowIndex, visibleProperties.Count].Merge = true; worksheet.Cells[rowIndex, 1].Style.Font.Bold = true; string personResult = isPersonsQualified ? "合格" : "不合格"; if (validPersons.Count < 10) { personResult = $"数据不足({validPersons.Count}/10人)"; } else { personResult = $"{personResult}({qualifiedPersonCount}/10人)"; } worksheet.Cells[rowIndex, totalColIndex].Value = personResult; worksheet.Cells[rowIndex, totalColIndex].Style.Font.Color.SetColor( isPersonsQualified ? System.Drawing.Color.Green : System.Drawing.Color.Red); worksheet.Cells[rowIndex, 1, rowIndex, totalColIndex].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; worksheet.Cells[rowIndex, 1, rowIndex, totalColIndex].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightYellow); } // 5. 调整列宽(保持不变) worksheet.Column(7).Width = 25; worksheet.Column(8).Width = 25; worksheet.Column(9).Width = 20; worksheet.Column(10).Width = 25; worksheet.Column(11).Width = 25; worksheet.Column(12).Width = 25; worksheet.Column(totalColIndex).Width = 25; SaveExcelFile(package, $"选中的{selectedIds.Count / 5}位人员实验数据导出成功!", $"选中的{selectedIds.Count / 5}位人员实验数据"); } } } catch (Exception ex) { UIMessageBox.Show($"导出失败: {ex.Message}", "错误", Sunny.UI.UIStyle.Blue); } } } // 新增辅助方法:根据口罩类型获取标准值 private (float actionStandard, float personStandard) GetMaskTypeStandards(string maskType) { if (string.IsNullOrEmpty(maskType)) return (0, 0); if (maskType.Contains("KN90") || maskType.Contains("KP90")) { return (13.0f, 10.0f); } else if (maskType.Contains("KN95") || maskType.Contains("KP95")) { return (11.0f, 8.0f); } else if (maskType.Contains("KN100") || maskType.Contains("KP100")) { return (5.0f, 2.0f); } else { return (0, 0); } } /// /// 保存Excel文件的通用方法 /// private void SaveExcelFile(ExcelPackage package, string successMessage, string fileName) { SaveFileDialog saveFileDialog = new SaveFileDialog { Filter = "Excel Files|*.xlsx", Title = "保存实验数据", FileName = $"{fileName}_{DateTime.Now:yyyyMMddHHmmss}.xlsx" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { try { FileInfo fileInfo = new FileInfo(saveFileDialog.FileName); package.SaveAs(fileInfo); // 成功提示(带标题) UIMessageBox.Show(successMessage, "成功", Sunny.UI.UIStyle.Blue); } catch (Exception ex) { // 错误提示 UIMessageBox.Show($"保存文件失败: {ex.Message}", "错误", Sunny.UI.UIStyle.Blue); } } } // 人员选择对话框(保持样式一致) //public class UISelectForm : Sunny.UI.UIForm //{ // private Sunny.UI.UIListBox listBox; // private Sunny.UI.UIButton okButton; // private Sunny.UI.UIButton cancelButton; // public List SelectedItems => listBox.SelectedItems.Cast().ToList(); // public UISelectForm(string title, List items) // { // Text = title; // Width = 600; // Height = 500; // StartPosition = FormStartPosition.CenterParent; // Style = Sunny.UI.UIStyle.Blue; // 匹配主窗体样式 // // 初始化列表框 // listBox = new Sunny.UI.UIListBox // { // Dock = DockStyle.Top, // Height = 400, // SelectionMode = SelectionMode.MultiSimple, // Style = this.Style // }; // listBox.Items.AddRange(items.ToArray()); // // 初始化按钮 // okButton = new Sunny.UI.UIButton // { // Text = "确定", // Dock = DockStyle.Left, // Width = 100, // Style = this.Style // }; // okButton.Click += (s, e) => // { // DialogResult = DialogResult.OK; // Close(); // }; // cancelButton = new Sunny.UI.UIButton // { // Text = "取消", // Dock = DockStyle.Right, // Width = 100, // Style = this.Style // }; // cancelButton.Click += (s, e) => // { // DialogResult = DialogResult.Cancel; // Close(); // }; // // 创建按钮容器 // var buttonPanel = new Panel // { // Dock = DockStyle.Bottom, // Height = 50, // BackColor = System.Drawing.Color.Transparent // }; // buttonPanel.Controls.Add(okButton); // buttonPanel.Controls.Add(cancelButton); // Controls.Add(listBox); // Controls.Add(buttonPanel); // } //} public class UISelectForm : UIForm { private UIListBox listBox; private UIButton okButton; private UIButton cancelButton; private UIButton queryButton; private UIDatePicker startDatePicker; private UIDatePicker endDatePicker; private UILabel startDateLabel; private UILabel endDateLabel; // 原始数据源(保存所有未过滤的item,用于重置) private List _originalItems; // 选中项集合(保持原有逻辑) public List SelectedItems => listBox.SelectedItems.Cast().ToList(); public UISelectForm(string title, List items) { // 保存原始数据源 _originalItems = new List(items); // 基础窗体设置 Text = title; Width = 800; // 加宽窗体以容纳日期控件 Height = 550; StartPosition = FormStartPosition.CenterParent; Style = UIStyle.Blue; // 初始化日期筛选控件 InitDateFilterControls(); // 初始化列表框 InitListBox(); // 初始化按钮(确定/取消/查询) InitButtons(); // 组装控件布局 AssembleControls(); } /// /// 初始化日期筛选控件(标签+日期选择器) /// private void InitDateFilterControls() { // 开始日期标签 startDateLabel = new UILabel { Text = "开始日期:", Size = new Size(80, 30), TextAlign = ContentAlignment.MiddleRight, Style = this.Style }; // 开始日期选择器 startDatePicker = new UIDatePicker { Size = new Size(200, 30), DateFormat = "yyyy-MM-dd HH:mm:ss", // 短日期格式(yyyy-MM-dd) Value = DateTime.Now.AddDays(-7), // 默认筛选近7天数据 Style = this.Style }; // 结束日期标签 endDateLabel = new UILabel { Text = "结束日期:", Size = new Size(80, 30), TextAlign = ContentAlignment.MiddleRight, Style = this.Style }; // 结束日期选择器 endDatePicker = new UIDatePicker { Size = new Size(200, 30), DateFormat = "yyyy-MM-dd HH:mm:ss", // 短日期格式(yyyy-MM-dd) Value = DateTime.Now, // 默认结束日期为今天 Style = this.Style }; // 查询按钮 queryButton = new UIButton { Text = "查询", Size = new Size(100, 30), Style = this.Style }; // 绑定查询事件 queryButton.Click += QueryButton_Click; } /// /// 初始化列表框(保持原有逻辑) /// private void InitListBox() { listBox = new UIListBox { Dock = DockStyle.Top, Height = 400, SelectionMode = SelectionMode.MultiSimple, Style = this.Style }; // 初始加载所有原始数据 listBox.Items.AddRange(_originalItems.ToArray()); } /// /// 初始化按钮(确定/取消) /// private void InitButtons() { okButton = new UIButton { Text = "确定", Dock = DockStyle.Left, Width = 100, Style = this.Style }; okButton.Click += (s, e) => { DialogResult = DialogResult.OK; Close(); }; cancelButton = new UIButton { Text = "取消", Dock = DockStyle.Right, Width = 100, Style = this.Style }; cancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); }; } /// /// 组装所有控件到窗体 /// private void AssembleControls() { // 日期筛选面板(横向排列日期控件和查询按钮) var filterPanel = new Panel { Dock = DockStyle.Top, Height = 40, BackColor = Color.Transparent }; // 添加日期控件到筛选面板(设置位置,避免重叠) filterPanel.Controls.Add(startDateLabel); startDateLabel.Location = new Point(20, 5); filterPanel.Controls.Add(startDatePicker); startDatePicker.Location = new Point(100, 5); filterPanel.Controls.Add(endDateLabel); endDateLabel.Location = new Point(320, 5); filterPanel.Controls.Add(endDatePicker); endDatePicker.Location = new Point(400, 5); filterPanel.Controls.Add(queryButton); queryButton.Location = new Point(620, 5); // 按钮面板(确定/取消) var buttonPanel = new Panel { Dock = DockStyle.Bottom, Height = 50, BackColor = Color.Transparent }; buttonPanel.Controls.Add(okButton); buttonPanel.Controls.Add(cancelButton); // 把控件添加到窗体 Controls.Add(filterPanel); // 筛选面板在最顶部 Controls.Add(listBox); // 列表框在中间 Controls.Add(buttonPanel); // 按钮面板在底部 } /// /// 查询按钮点击事件:根据日期筛选列表数据 /// private void QueryButton_Click(object sender, EventArgs e) { // 获取筛选条件(只取日期部分,忽略时间) DateTime startDate = startDatePicker.Value.Date; DateTime endDate = endDatePicker.Value.Date; // 校验日期逻辑(开始日期不能晚于结束日期) if (startDate > endDate) { UIMessageBox.Show("开始日期不能晚于结束日期!", "提示", UIStyle.Blue, UIMessageBoxButtons.OK); return; } // 清空现有列表 listBox.Items.Clear(); // 遍历原始数据,筛选符合日期条件的item foreach (var item in _originalItems) { // 解析item中的日期字符串 if (TryExtractDateFromItem(item, out DateTime itemDate)) { // 判断item日期是否在筛选范围内(包含开始和结束日期) if (itemDate.Date >= startDate && itemDate.Date <= endDate) { listBox.Items.Add(item); } } else { // 无法解析日期的item:可选择忽略或保留(这里默认保留,避免数据丢失) listBox.Items.Add(item); // 可选:输出日志提示格式异常 // Console.WriteLine($"警告:item格式异常,无法解析日期 - {item}"); } } // 提示筛选结果 UIMessageBox.Show($"筛选完成!共找到 {listBox.Items.Count} 条符合条件的数据", "提示", UIStyle.Blue, UIMessageBoxButtons.OK); } private bool TryExtractDateFromItem(string item, out DateTime itemDate) { itemDate = DateTime.MinValue; try { // 步骤1:按括号分割字符串(处理中英文括号,修正Split参数:先指定分隔符数组,再指定StringSplitOptions) char[] separators = new[] { '(', ')' }; var parts = item.Split(separators, StringSplitOptions.RemoveEmptyEntries); // 步骤2:取第二个有效部分(索引1),即日期字符串(格式:2025-11-25 15-42-04) if (parts.Length < 2) return false; string dateStr = parts[1].Trim(); // 步骤3:只替换时间部分的"-"为":"(保留日期部分的"-") if (dateStr.Contains(" ")) { var dateTimeParts = dateStr.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (dateTimeParts.Length == 2) { string datePart = dateTimeParts[0]; // 日期部分:2025-11-25(保留"-") string timePart = dateTimeParts[1]; // 时间部分:15-42-04(替换"-"为":") timePart = timePart.Replace("-", ":"); // 转换为:15:42:04 dateStr = $"{datePart} {timePart}"; // 重组为标准格式:2025-11-25 15:42:04 } } // 步骤4:解析标准日期时间格式 return DateTime.TryParse(dateStr, out itemDate); } catch (Exception ex) { Console.WriteLine($"解析item日期失败:{ex.Message} - {item}"); return false; } } } private void ExportCurrentExperimentToExcel(DataTable dataTable, List> stringData, List>> floatData) { using (var package = new ExcelPackage()) { var worksheet = package.Workbook.Worksheets.Add("Current Experiment"); // 写入数据(不包括姓名、性别、年龄、身份证号码、工作单位、工龄) string[] headers = { "时间", "室内气溶胶浓度", "口罩内气溶胶浓度", "泄露率", "室内CO2浓度", "口罩内CO2浓度", "室内氧浓度", "室内温度", "室内湿度" }; for (int col = 0; col < headers.Length; col++) { worksheet.Cells[1, col + 1].Value = headers[col]; } for (int sheetIndex = 0; sheetIndex < stringData.Count; sheetIndex++) { List currentStringData = stringData[sheetIndex]; List> currentFloatData = floatData[sheetIndex]; for (int row = 0; row < currentStringData.Count; row++) { worksheet.Cells[row + 2, 1].Value = currentStringData[row]; } for (int col = 0; col < currentFloatData.Count; col++) { List columnData = currentFloatData[col]; for (int row = 0; row < columnData.Count; row++) { worksheet.Cells[row + 2, col + 2].Value = columnData[row]; } } } SaveExcelFile(package); } } private void ExportHistoricalExperimentToExcel() { // 从数据库读取历史实验数据 List names = GetNamesFromDatabase(); using (var package = new ExcelPackage()) { foreach (var name in names) { var worksheet = package.Workbook.Worksheets.Add(name); // 从数据库获取该姓名的实验数据 DataTable dataTable = GetExperimentDataByName(name); // 写入数据 for (int col = 0; col < dataTable.Columns.Count; col++) { worksheet.Cells[1, col + 1].Value = dataTable.Columns[col].ColumnName; } for (int row = 0; row < dataTable.Rows.Count; row++) { for (int col = 0; col < dataTable.Columns.Count; col++) { worksheet.Cells[row + 2, col + 1].Value = dataTable.Rows[row][col]; } } } SaveExcelFile(package); } } private void SaveExcelFile(ExcelPackage package) { SaveFileDialog saveFileDialog = new SaveFileDialog { Filter = "Excel Files|*.xlsx", Title = "保存 Excel 文件" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { FileInfo fileInfo = new FileInfo(saveFileDialog.FileName); package.SaveAs(fileInfo); MessageBox.Show("数据导出成功!"); } } private List GetNamesFromDatabase() { // 从数据库获取所有姓名 // 示例代码,需根据实际数据库结构实现 return new List { "姓名1", "姓名2" }; } private DataTable GetExperimentDataByName(string name) { // 从数据库获取指定姓名的实验数据 // 示例代码,需根据实际数据库结构实现 return new DataTable(); } #endregion private void btn_ClearData_Click(object sender, EventArgs e) { dt_Show.Clear(); list_Time.Clear(); list_Tsi_Indoor_Data.Clear(); list_Tsi_Outdoor_Data.Clear(); //experData.XieLouLv = experData.CumulativeLeakageRate(list_Tsi_Indoor_Data, list_Tsi_Outdoor_Data, experData.BenDiNongDu, 1.0f); lsit_XieLoulv.Clear(); list_CO2_Indoor_Data.Clear(); list_CO2_Outdoor_Data.Clear(); list_O2_Data.Clear(); list_HuanJingWenDu_Data.Clear(); list_HuanJingShiDu_Data.Clear(); liatAll.Clear(); list_AllTime.Clear(); _logger.Log(loginData.UserName, "点击了【数据清除】按钮", loginData.UserPower.ToString()); } private void btn_Next_Click(object sender, EventArgs e) { GoToNextStep(); _logger.Log(loginData.UserName, "进入流程", loginData.UserPower.ToString()); } private void Pause_Click(object sender, EventArgs e) { try { if (!isPaused) { // 暂停操作 PauseTest(); master?.WriteSingleCoil(1, 40, true); } else { // 继续操作 ResumeTest(); master?.WriteSingleCoil(1, 40, false); } _logger.Log(loginData.UserName, isPaused ? "点击了【暂停】按钮" : "点击了【继续】按钮", loginData.UserPower.ToString()); } catch (Exception ex) { MessageBox.Show($"暂停/继续操作失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); _logger.Log(loginData.UserName, $"暂停/继续操作失败: {ex.Message}", loginData.UserPower.ToString()); } } /// /// 暂停测试 /// private void PauseTest() { // 停止自动步骤计时器 autoStepTimer?.Stop(); // 保存剩余的秒数 pausedRemainingSeconds = countdownSeconds; // 暂停视频播放 if (form2.axWindowsMediaPlayer1.playState == WMPLib.WMPPlayState.wmppsPlaying) { form2.axWindowsMediaPlayer1.Ctlcontrols.pause(); // 保存当前视频路径以便恢复时使用 currentVideoPath = form2.axWindowsMediaPlayer1.URL; } // 更新界面状态 lb_status.Text = $"{stepList[currentStepIndex]} - 已暂停 (剩余: {pausedRemainingSeconds}秒)"; //form2.uiLabel1.Text = $"{form2.uiLabel1.Text.Replace("倒计时:", "已暂停 - 剩余: ")}"; // 更新按钮文本和颜色 Pause1.Text = "继续测试"; Pause1.Style = UIStyle.Green; // 使用绿色表示继续 // 禁用其他控制按钮(可选) //btn_Start.Enabled = false; //btn_Stop.Enabled = false; //btn_NextStep.Enabled = false; isPaused = true; // 记录暂停日志 _logger.Log(loginData.UserName, $"测试暂停 - 步骤[{currentStepIndex + 1}]: {stepList[currentStepIndex]}, 剩余时间: {pausedRemainingSeconds}秒", loginData.UserPower.ToString()); } /// /// 继续测试 /// private void ResumeTest() { // 恢复倒计时秒数 countdownSeconds = pausedRemainingSeconds; // 恢复视频播放 if (!string.IsNullOrEmpty(currentVideoPath) && File.Exists(currentVideoPath)) { // 如果视频路径有效,恢复播放 form2.axWindowsMediaPlayer1.Ctlcontrols.play(); } else if (form2.axWindowsMediaPlayer1.URL != null && File.Exists(form2.axWindowsMediaPlayer1.URL)) { // 如果当前有视频在加载,也尝试恢复 form2.axWindowsMediaPlayer1.Ctlcontrols.play(); } // 重新启动计时器 autoStepTimer?.Start(); // 更新界面状态 lb_status.Text = $"{stepList[currentStepIndex]} 倒计时: {countdownSeconds}秒"; //// 更新form2的标签文本,移除暂停提示 //int index = form2.uiLabel1.Text.LastIndexOf("\r\n[已暂停"); //if (index > 0) //{ // form2.uiLabel1.Text = form2.uiLabel1.Text.Substring(0, index) + $"\r\n倒计时: {countdownSeconds}秒"; //} //else //{ // form2.uiLabel1.Text = form2.uiLabel1.Text.Replace("[已暂停]", "") + $"\r\n倒计时: {countdownSeconds}秒"; //} // 更新按钮文本和颜色 Pause1.Text = "暂停测试"; Pause1.Style = UIStyle.Red; // 使用红色表示暂停 //// 启用其他控制按钮 //btn_Start.Enabled = true; //btn_Stop.Enabled = true; btn_NextStep.Enabled = true; isPaused = false; // 记录继续日志 _logger.Log(loginData.UserName, $"测试继续 - 步骤[{currentStepIndex + 1}]: {stepList[currentStepIndex]}, 剩余时间: {countdownSeconds}秒", loginData.UserPower.ToString()); } } }