diff --git a/App.config b/App.config
new file mode 100644
index 0000000..fb0c1f3
--- /dev/null
+++ b/App.config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/App.xaml b/App.xaml
new file mode 100644
index 0000000..5041b34
--- /dev/null
+++ b/App.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
diff --git a/App.xaml.cs b/App.xaml.cs
new file mode 100644
index 0000000..dfdc8cf
--- /dev/null
+++ b/App.xaml.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Security.Principal;
+using System.Windows;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+ // 捕获UI线程异常
+ DispatcherUnhandledException += App_DispatcherUnhandledException;
+ // 捕获非UI线程异常
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ // 捕获任务线程异常
+ System.Threading.Tasks.TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+ }
+
+ private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
+ {
+ LogError(e.Exception, "UI线程异常");
+ e.Handled = true; // 标记为已处理,避免崩溃(仅用于调试)
+ }
+
+ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ if (e.ExceptionObject is Exception ex)
+ LogError(ex, "应用域未处理异常");
+ }
+
+ private void TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e)
+ {
+ LogError(e.Exception, "任务线程异常");
+ e.SetObserved();
+ }
+
+ private void LogError(Exception ex, string type)
+ {
+ string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "error.log");
+ string logContent = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {type}:\n{ex.ToString()}\n\n";
+ File.AppendAllText(logPath, logContent);
+ MessageBox.Show($"发生错误,详情已记录到 {logPath}", "错误");
+ }
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+ if (!IsRunAsAdmin())
+ {
+ RestartAsAdmin();
+ Shutdown();
+ }
+ }
+
+ private bool IsRunAsAdmin()
+ {
+ WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+
+ private void RestartAsAdmin()
+ {
+ ProcessStartInfo startInfo = new ProcessStartInfo();
+ startInfo.UseShellExecute = true;
+ startInfo.WorkingDirectory = Environment.CurrentDirectory;
+ startInfo.FileName = Process.GetCurrentProcess().MainModule.FileName;
+ startInfo.Verb = "runas"; // Run as administrator
+
+ try
+ {
+ Process.Start(startInfo);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("Error: " + ex.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs
new file mode 100644
index 0000000..b0ec827
--- /dev/null
+++ b/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/Constant/CSConstant.cs b/Constant/CSConstant.cs
new file mode 100644
index 0000000..e0cbd70
--- /dev/null
+++ b/Constant/CSConstant.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public static class CSConstant
+ {
+ public static string DbConnectionString = "Data Source=FlowPressure.db;Version=3;";
+ }
+
+ ///
+ /// 排气流表映射
+ ///
+ public class FlowPressureRecord
+ {
+ public int Id { get; set; }
+ public double Flow { get; set; }
+ public double Pressure { get; set; }
+
+ public double BeginCO2 { get; set; }
+ public double EndCO2 { get; set; }
+ public double CO2Added { get; set; }
+
+ public DateTime RecordTime { get; set; }
+ }
+}
diff --git a/Constant/PLCDevice.cs b/Constant/PLCDevice.cs
new file mode 100644
index 0000000..a9054c1
--- /dev/null
+++ b/Constant/PLCDevice.cs
@@ -0,0 +1,11 @@
+using Modbus.Device;
+using Modbus;
+using System.Net.Sockets;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public class PLCDevice
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Data/BoolSign.cs b/Data/BoolSign.cs
new file mode 100644
index 0000000..8b91fb1
--- /dev/null
+++ b/Data/BoolSign.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace 口罩泄露定制款.Data
+{
+
+ public class BoolSignal
+ {
+ private bool _previousValue;
+
+ public event Action OnRisingEdge;
+ // bool value = true;
+ public bool Value { get; set; }
+
+ public void CheckRisingEdge()
+ {
+ if (Value && !_previousValue)
+ {
+ OnRisingEdge?.Invoke();
+ }
+ _previousValue = Value;
+ }
+ }
+
+}
diff --git a/Data/DataChange.cs b/Data/DataChange.cs
new file mode 100644
index 0000000..4b8466a
--- /dev/null
+++ b/Data/DataChange.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace 口罩泄露定制款
+{
+ public class DataChange
+ {
+ ///
+ /// ushort转为float类型
+ ///
+ ///
+ ///
+ /// float型数据
+ public float UshortToFloat(ushort P1, ushort P2)
+ {
+ int intSign, intSignRest, intExponent, intExponentRest;
+ float faResult, faDigit;
+ intSign = P1 / 32768;
+ intSignRest = P1 % 32768;
+ intExponent = intSignRest / 128;
+ intExponentRest = intSignRest % 128;
+ faDigit = (float)(intExponentRest * 65536 + P2) / 8388608;
+ faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
+ return faResult;
+ }
+ ///
+ /// ushort转为int类型
+ ///
+ ///
+ ///
+ /// 返回int型数据
+ public int UshortToInt1(ushort u1, ushort u2)
+ {
+ ushort[] maidong = new ushort[2] { u1, u2 };
+ byte[] bytes = new byte[maidong.Length * 2];
+ Buffer.BlockCopy(maidong, 0, bytes, 0, bytes.Length);
+ int result = BitConverter.ToInt32(bytes, 0);
+ // 将字节数组转换为32位无符号整数
+ return result;
+
+ }
+ ///
+ /// Float转为Ushort数组发送
+ ///
+ ///
+ /// 返回ushort数组
+ public ushort[] SplitFloatToUShortArray(float value)
+ {
+ byte[] floatBytes = BitConverter.GetBytes(value);
+ ushort[] ushortArray = new ushort[floatBytes.Length / 2];
+
+ for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
+ {
+ ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
+ }
+
+ return ushortArray;
+ }
+ ///
+ /// Int转为ushort数组发送
+ ///
+ ///
+ /// 返回ushort数组
+ public ushort[] intToushorts(int res)
+ {
+ ushort ust1 = (ushort)(res >> 16);
+ ushort ust2 = (ushort)res;
+ return new ushort[] { ust2, ust1 };
+ }
+ }
+}
diff --git a/Data/ExperData.cs b/Data/ExperData.cs
new file mode 100644
index 0000000..590d24d
--- /dev/null
+++ b/Data/ExperData.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace 口罩泄露定制款
+{
+ public class ExperData: INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+ #region 实验属性
+ static string experName = "";
+ ///
+ /// 实验人员
+ ///
+ public string ExperName { get { return experName; } set { experName = value; } }
+
+ static string experDate = "";
+ ///
+ /// 实验日期
+ ///
+ public string ExperDate { get { return experDate; } set { experDate = value; } }
+
+ static string experNum = "";
+ ///
+ /// 实验编号
+ ///
+ public string ExperNum { get { return experNum; } set { experNum = value; } }
+
+ static string experType = " ";
+ ///
+ /// 实验种类
+ ///
+ public string ExperType { get { return experType; } set { experType = value; } }
+ static float experMaskType = 2.0f;
+ ///
+ /// 口罩类型
+ ///
+ public float ExperMaskType { get { return experMaskType; } set { experMaskType = value; } }
+ ///
+ /// 面罩类型
+ ///
+ static string maskType;
+ public string MaskType { get { return maskType; } set { maskType = value; } }
+ static float _yangPinXiShu = 1.0f;
+ ///
+ /// 样品系数
+ ///
+ public float YangPinXiShu { get { return _yangPinXiShu; } set { _yangPinXiShu = value; } }
+ static string _testStatus = "";
+
+
+ ///
+ /// 实验状态
+ ///
+ public string TestStatus { get { return _testStatus; } set { _testStatus = value; } }
+ static float benDiNongDu = 0.00f;
+ ///
+ /// 本底浓度
+ ///
+ public float BenDiNongDu { get { return benDiNongDu; } set { benDiNongDu = value; } }
+
+ static float huanJingWenDu = 0.00f;
+ ///
+ /// 环境温度
+ ///
+ public float HuanJingWenDu { get { return huanJingWenDu; } set { huanJingWenDu = value; } }
+
+
+ static float huanJingShiDu = 0.00f;
+ ///
+ /// 环境湿度
+ ///
+ public float HuanJingShiDu { get { return huanJingShiDu; } set { huanJingShiDu = value; } }
+
+ static float o2NongDu = 0.00f;
+ ///
+ /// 环境氧浓度
+ ///
+ public float O2NongDu { get { return o2NongDu; } set { o2NongDu = value; } }
+
+ static float cO2NongDu = 0.00f;
+ ///
+ /// 环境C02浓度
+ ///
+ public float CO2NongDu_Indoor { get { return cO2NongDu; } set { cO2NongDu = value; } }
+
+ static float inDoor_TSINongDu = 0.00f;
+ ///
+ /// 环境气溶胶浓度
+ ///
+ public float InDoor_TSINongDu { get { return inDoor_TSINongDu; } set { inDoor_TSINongDu = value; } }
+
+ static float mask_CO2NongDu = 0.00f;
+ ///
+ /// 口罩内CO2浓度
+ ///
+ public float Mask_CO2NongDu { get { return mask_CO2NongDu; } set { mask_CO2NongDu = value; } }
+
+ static float mask_TSINongDu = 0.00f;
+ ///
+ /// 口罩内气溶胶浓度
+ ///
+ public float Mask_TSINongDu { get { return mask_TSINongDu; } set { mask_TSINongDu = value; } }
+
+ static float _liuLiang = 0.00f;
+ ///
+ /// 流量
+ ///
+ public float LiuLiang { get { return _liuLiang; } set { _liuLiang = value; } }
+ static float _xieloulv = 0.00f;
+ ///
+ /// 泄露率
+ ///
+ public float XieLouLv { get { return _xieloulv; } set { _xieloulv = value; } }
+
+
+ #endregion
+
+ #region 泄露率计算
+ //单独动作泄露率
+ public float CumulativeLeakageRate(List inDoor_Tsi,List out_Door_Tsi,float benDiNongdu,float xiShu)
+ {
+ if (inDoor_Tsi.Count != 0 && out_Door_Tsi.Count != 0&& benDiNongdu!=0.0f)
+ {
+ //计算室内Tsi浓度平均值
+ float inDoor_Tsi_Avg = inDoor_Tsi.Average();
+ //计算室外Tsi浓度平均值
+ float out_Door_Tsi_Avg = out_Door_Tsi.Average();
+ //计算泄露率
+ float leakageRate = ((out_Door_Tsi_Avg - inDoor_Tsi_Avg) * xiShu) / benDiNongdu;
+ return leakageRate;
+ }
+ else
+ {
+ return 0.00f;
+ }
+
+ }
+ //全部动作泄露率
+ public float CumulativeLeakageRate_All(List All_Cv_List)
+
+ {
+ return All_Cv_List.Average();
+ }
+ #endregion
+
+ }
+}
diff --git a/Data/Function.cs b/Data/Function.cs
new file mode 100644
index 0000000..764c80f
--- /dev/null
+++ b/Data/Function.cs
@@ -0,0 +1,185 @@
+using Modbus.Device;
+using Modbus;
+using Sunny.UI;
+using System;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace 口罩泄露定制款
+{
+ public class Function
+ {
+ ModbusMaster master;
+ IModbusMaster modbusMaster;
+ DataChange dc = new DataChange();
+ public enum ButtonType
+ {
+ 复归型,
+ 切换型,
+ 置位型,
+ 复位型
+ }
+ public enum DataType
+ {
+ 整形,
+ 浮点型
+ }
+ public Function(ModbusMaster master_in)
+ {
+ this.master = master_in;
+ }
+
+ public Function(IModbusMaster modbusMaster)
+ {
+ this.modbusMaster = modbusMaster;
+ }
+
+ public void BtnClickFunction(ButtonType buttonType, ushort address)
+ {
+ try
+ {
+ switch (buttonType)
+ {
+ case ButtonType.复归型:
+ master.WriteSingleCoil(1, address, true);
+ Thread.Sleep(100);
+ master.WriteSingleCoil(1, address, false);
+ Thread.Sleep(100);
+ break;
+ case ButtonType.切换型:
+ if (master.ReadCoils(1, address, 1)[0])
+ {
+ master.WriteSingleCoil(1, address, false); Thread.Sleep(100);
+ }
+ else
+ { master.WriteSingleCoil(1, address, true); Thread.Sleep(100); }
+ break;
+ case ButtonType.置位型:
+ master.WriteSingleCoil(1, address, true);
+ Thread.Sleep(100);
+ break;
+ case ButtonType.复位型:
+ master.WriteSingleCoil(1, address, false);
+ Thread.Sleep(100);
+ break;
+ default:
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+
+ }
+
+ }
+
+ public void BtnClickFunctionForNew(ButtonType buttonType, ushort address)
+ {
+ try
+ {
+ switch (buttonType)
+ {
+ case ButtonType.复归型:
+ modbusMaster.WriteSingleCoil(1, address, true);
+ Thread.Sleep(100);
+ modbusMaster.WriteSingleCoil(1, address, false);
+ Thread.Sleep(100);
+ break;
+ case ButtonType.切换型:
+ if (modbusMaster.ReadCoils(1, address, 1)[0])
+ {
+ modbusMaster.WriteSingleCoil(1, address, false); Thread.Sleep(100);
+ }
+ else
+ { modbusMaster.WriteSingleCoil(1, address, true); Thread.Sleep(100); }
+ break;
+ case ButtonType.置位型:
+ modbusMaster.WriteSingleCoil(1, address, true);
+ Thread.Sleep(100);
+ break;
+ case ButtonType.复位型:
+ modbusMaster.WriteSingleCoil(1, address, false);
+ Thread.Sleep(100);
+ break;
+ default:
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+
+ }
+
+ }
+ public void WriteToPLC(string inPutValue, ushort address, DataType dataType)
+ {
+
+ try
+ {
+ switch (dataType)
+ {
+ case DataType.浮点型:
+ double value = inPutValue.ToDouble();
+ if (UIInputDialog.ShowInputDoubleDialog(ref value, UIStyle.Inherited, desc: "请输入值", showMask: false))
+ {
+
+ master.WriteMultipleRegisters(1, address, dc.SplitFloatToUShortArray((float)value));
+ }
+ break;
+ case DataType.整形:
+ int value_int = inPutValue.ToInt();
+ if (UIInputDialog.ShowInputIntegerDialog(ref value_int, UIStyle.Inherited, desc: "请输入数据:"))
+ {
+
+ master.WriteMultipleRegisters(1, address, dc.intToushorts(value_int));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("操作失败!" + "\n" + "\n" + ex.Message, "错误");
+ }
+
+ }
+
+ public void WriteToPLCForNew(string inPutValue, ushort address, DataType dataType)
+ {
+ try
+ {
+ KeyboardHelper.ShowSoftKeyboard();
+ switch (dataType)
+ {
+ case DataType.浮点型:
+ double value = inPutValue.ToDouble();
+
+ if (UIInputDialog.ShowInputDoubleDialog(ref value, UIStyle.Inherited, desc: "请输入值", showMask: false))
+ {
+
+ modbusMaster.WriteMultipleRegisters(1, address, dc.SplitFloatToUShortArray((float)value));
+ }
+ break;
+ case DataType.整形:
+ int value_int = inPutValue.ToInt();
+ if (UIInputDialog.ShowInputIntegerDialog(ref value_int, UIStyle.Inherited, desc: "请输入数据:"))
+ {
+ modbusMaster.WriteMultipleRegisters(1, address, dc.intToushorts(value_int));
+ }
+ break;
+ default:
+ break;
+ }
+
+ KeyboardHelper.HideSoftKeyboard();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("操作失败!" + "\n" + "\n" + ex.Message, "错误");
+ }
+
+ }
+ }
+}
diff --git a/Data/LoginData.cs b/Data/LoginData.cs
new file mode 100644
index 0000000..4cb62c4
--- /dev/null
+++ b/Data/LoginData.cs
@@ -0,0 +1,21 @@
+namespace 口罩泄露定制款
+{
+ public class LoginData
+ {
+ static string _userName = "";
+ public string UserName
+ {
+ get { return _userName; }
+ set { _userName = value; }
+ }
+
+ //登陆权限
+ static int _userPower = 0;//0为普通用户,1为管理员
+ public int UserPower
+ {
+ get { return _userPower; }
+ set { _userPower = value; }
+ }
+
+ }
+}
diff --git a/Data/PLC_Data.cs b/Data/PLC_Data.cs
new file mode 100644
index 0000000..8c05c69
--- /dev/null
+++ b/Data/PLC_Data.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace 口罩泄露定制款.Data
+{
+ internal class PLC_Data
+ {
+
+ }
+}
diff --git a/Data/keyboard.cs b/Data/keyboard.cs
new file mode 100644
index 0000000..d1b52fb
--- /dev/null
+++ b/Data/keyboard.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Diagnostics;
+using System.Security.Principal;
+
+public class KeyboardHelper
+{
+ public static void ShowSoftKeyboard()
+ {
+ if (!IsRunAsAdmin())
+ {
+ RestartAsAdmin();
+ return;
+ }
+
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "osk.exe",
+ UseShellExecute = true
+ });
+ }
+
+ public static void HideSoftKeyboard()
+ {
+ try
+ {
+ Process.Start("cmd.exe", "/C taskkill /IM osk.exe /F");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error: " + ex.Message);
+ }
+ }
+
+ private static bool IsRunAsAdmin()
+ {
+ WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+
+ private static void RestartAsAdmin()
+ {
+ ProcessStartInfo startInfo = new ProcessStartInfo();
+ startInfo.UseShellExecute = true;
+ startInfo.WorkingDirectory = Environment.CurrentDirectory;
+ startInfo.FileName = Process.GetCurrentProcess().MainModule.FileName;
+ startInfo.Verb = "runas";
+
+ try
+ {
+ Process.Start(startInfo);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error: " + ex.Message);
+ }
+
+ Environment.Exit(0);
+ }
+}
diff --git a/Main.xaml b/Main.xaml
new file mode 100644
index 0000000..cd64874
--- /dev/null
+++ b/Main.xaml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Main.xaml.cs b/Main.xaml.cs
new file mode 100644
index 0000000..e257373
--- /dev/null
+++ b/Main.xaml.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+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.Shapes;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ ///
+ /// Main.xaml 的交互逻辑
+ ///
+ public partial class Main : Window
+ {
+ public Main()
+ {
+ InitializeComponent();
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+ ///
+ /// 进入主窗体事件
+ ///
+ ///
+ ///
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+
+ MainWindow mainWindow = MainWindow.Instance;
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+ }
+}
diff --git a/MainWindow.xaml b/MainWindow.xaml
new file mode 100644
index 0000000..793ae68
--- /dev/null
+++ b/MainWindow.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
new file mode 100644
index 0000000..d0fa74e
--- /dev/null
+++ b/MainWindow.xaml.cs
@@ -0,0 +1,92 @@
+using System.Windows;
+using System.Windows.Media.Imaging;
+using System.Windows.Media;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+
+
+ }
+
+ private static MainWindow _instance;
+ public static MainWindow Instance
+ {
+ get
+ {
+ if (_instance == null || !_instance.IsLoaded)
+ {
+ _instance = new MainWindow();
+ }
+ return _instance;
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+
+ Window1 mainWindow = new Window1();
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ //this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+
+ private void Button1_Click(object sender, RoutedEventArgs e)
+ {
+ Window2 mainWindow = new Window2();
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ //this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+
+ private void Button2_Click(object sender, RoutedEventArgs e)
+ {
+ Window3 mainWindow = new Window3();
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ //this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+
+ private void Button3_Click(object sender, RoutedEventArgs e)
+ {
+ Window4 mainWindow = new Window4();
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ //this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+
+ private void Button4_Click(object sender, RoutedEventArgs e)
+ {
+ Window5 mainWindow = new Window5();
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ //this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+
+ private void Button5_Click(object sender, RoutedEventArgs e)
+ {
+ ParameterConfigWindow mainWindow = new ParameterConfigWindow();
+ mainWindow.ShowDialog(); // 显示 MainWindow
+
+
+ //this.Close(); // 如果您希望在打开新窗口时关闭当前窗口
+ }
+ }
+}
diff --git a/Properties/Licenses.licx b/Properties/Licenses.licx
new file mode 100644
index 0000000..6ce1b4d
--- /dev/null
+++ b/Properties/Licenses.licx
@@ -0,0 +1 @@
+DevExpress.Xpf.Editors.ButtonEdit, DevExpress.Xpf.Core.v22.2, Version=22.2.3.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a
diff --git a/ReportWindow1.xaml b/ReportWindow1.xaml
new file mode 100644
index 0000000..a61c7b5
--- /dev/null
+++ b/ReportWindow1.xaml
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReportWindow1.xaml.cs b/ReportWindow1.xaml.cs
new file mode 100644
index 0000000..e7b9ff3
--- /dev/null
+++ b/ReportWindow1.xaml.cs
@@ -0,0 +1,402 @@
+using Microsoft.Win32;
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data.SQLite;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ // 新增:为支持批量选择添加带选中状态的模型类
+ public class FlowPressureRecordWithSelection : FlowPressureRecord
+ {
+ public bool IsSelected { get; set; }
+ }
+
+ public partial class ReportWindow1 : Window
+ {
+ private const int PageSize = 10;
+ private int currentPage = 1;
+ private int totalRecords = 0;
+ // 新增:保存当前查询条件,用于删除后刷新
+ private string _currentStartDate = null;
+ private string _currentEndDate = null;
+ // 新增:使用ObservableCollection支持数据绑定刷新
+ private ObservableCollection _records = new ObservableCollection();
+
+ public ReportWindow1()
+ {
+ InitializeComponent();
+ // 修改:绑定到带选中状态的集合
+ DataGridReport.ItemsSource = _records;
+ LoadData();
+ }
+
+ private void LoadData(string startDate = null, string endDate = null)
+ {
+ // 保存当前查询条件
+ _currentStartDate = startDate;
+ _currentEndDate = endDate;
+
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Id, Flow, Pressure, RecordTime FROM FlowPressureRecords ";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ query += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+ query += " LIMIT @offset, @limit";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ cmd.Parameters.AddWithValue("@startDate", startDate);
+ cmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+ cmd.Parameters.AddWithValue("@offset", (currentPage - 1) * PageSize);
+ cmd.Parameters.AddWithValue("@limit", PageSize);
+
+ _records.Clear(); // 清空现有数据
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ // 转换为带选中状态的模型
+ _records.Add(new FlowPressureRecordWithSelection
+ {
+ Id = reader.GetInt32(0),
+ Flow = reader.GetDouble(1),
+ Pressure = reader.GetDouble(2),
+ RecordTime = reader.GetDateTime(3),
+ IsSelected = false // 默认未选中
+ });
+ }
+ }
+ }
+
+ // 获取总记录数,考虑当前查询条件
+ string countQuery = "SELECT COUNT(*) FROM FlowPressureRecords";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countQuery += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+
+ using (SQLiteCommand countCmd = new SQLiteCommand(countQuery, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countCmd.Parameters.AddWithValue("@startDate", startDate);
+ countCmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+
+ totalRecords = Convert.ToInt32(countCmd.ExecuteScalar());
+ }
+
+ UpdatePageInfo();
+ }
+ }
+
+
+ private void QueryButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 保持原有查询逻辑
+ string? startDate = StartDatePicker.SelectedDate?.ToString("yyyy-MM-dd") ?? null;
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ string? endDateString = endDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? null;
+
+ currentPage = 1;
+ LoadData(startDate, endDateString);
+ }
+
+ private void UpdatePageInfo()
+ {
+ // 保持原有分页信息逻辑
+ PageInfo.Text = $"第 {currentPage} 页 / 共 {Math.Ceiling((double)totalRecords / PageSize)} 页 {totalRecords}条记录";
+ }
+
+ private void PreviousPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage > 1)
+ {
+ currentPage--;
+ // 修改:使用当前查询条件刷新
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ private void NextPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage * PageSize < totalRecords)
+ {
+ currentPage++;
+ // 修改:使用当前查询条件刷新
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ // 保持原有背景设置逻辑
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+ private void DeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is int id)
+ {
+ var result = MessageBox.Show($"确定要删除ID为 {id} 的记录吗?", "确认删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ DeleteRecordFromDb(id);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+ }
+
+ // 新增:批量删除功能
+ private void BatchDeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedIds = _records.Where(r => r.IsSelected).Select(r => r.Id).ToList();
+ if (!selectedIds.Any())
+ {
+ MessageBox.Show("请先选择要删除的记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var result = MessageBox.Show($"确定要删除选中的 {selectedIds.Count} 条记录吗?", "确认批量删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ BatchDeleteFromDb(selectedIds);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ // 新增:数据库单条删除操作
+ private void DeleteRecordFromDb(int id)
+ {
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ using (var cmd = new SQLiteCommand("DELETE FROM FlowPressureRecords WHERE Id = @Id", conn))
+ {
+ cmd.Parameters.AddWithValue("@Id", id);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ // 新增:数据库批量删除操作
+ private void BatchDeleteFromDb(List ids)
+ {
+ if (!ids.Any()) return;
+
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ // 构建批量删除SQL(参数化防止注入)
+ string idsParam = string.Join(",", ids.Select((_, i) => $"@Id{i}"));
+ using (var cmd = new SQLiteCommand($"DELETE FROM FlowPressureRecords WHERE Id IN ({idsParam})", conn))
+ {
+ // 添加参数
+ for (int i = 0; i < ids.Count; i++)
+ {
+ cmd.Parameters.AddWithValue($"@Id{i}", ids[i]);
+ }
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void Export_Click(object sender, RoutedEventArgs e)
+ {
+ DateTime? startDate = null;
+ if (DateTime.TryParse(StartDatePicker.SelectedDate?.ToString(), out DateTime tempStartDate))
+ {
+ startDate = tempStartDate;
+ }
+
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ // 步骤2:读取CO2表数据
+ List co2Records = ReadCO2RecordsFromDatabase(startDate, endDate);
+ if (co2Records == null || !co2Records.Any())
+ {
+ MessageBox.Show("排气流表中无数据,无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 步骤3:导出Excel
+ bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
+ if (exportSuccess)
+ {
+ MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show("Excel导出失败,请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private bool ExportCO2RecordsToExcel(List records)
+ {
+ try
+ {
+ // WPF 原生保存对话框(替代 WinForms)
+ SaveFileDialog saveDialog = new SaveFileDialog
+ {
+ Filter = "Excel文件 (*.xlsx)|*.xlsx",
+ FileName = $"排气流记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
+ Title = "保存排气流记录"
+ };
+
+ bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
+
+
+
+ // EPPlus 许可设置(.NET 8 必须显式设置)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 非商业用途
+
+ // 创建并写入Excel
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("排气流记录");
+
+ // 表头(对应DataGrid列)
+ worksheet.Cells[1, 1].Value = "排气流流量L/min";
+ worksheet.Cells[1, 2].Value = "压力(pa)";
+ worksheet.Cells[1, 3].Value = "时间";
+
+ // 表头样式(加粗、居中)
+ using (var headerRange = worksheet.Cells[1, 1, 1, 6])
+ {
+ headerRange.Style.Font.Bold = true;
+ headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
+ }
+
+ // 填充数据(从第2行开始)
+ for (int i = 0; i < records.Count; i++)
+ {
+ int row = i + 2;
+ var record = records[i];
+ worksheet.Cells[row, 1].Value = record.Flow;
+ worksheet.Cells[row, 2].Value = record.Pressure;
+ worksheet.Cells[row, 3].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 自动调整列宽
+ worksheet.Cells.AutoFitColumns();
+
+ // 保存文件
+ package.Save();
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ private List ReadCO2RecordsFromDatabase(DateTime? startDate, DateTime? endDate)
+ {
+ List records = new List();
+ try
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Flow, Pressure, RecordTime FROM FlowPressureRecords";
+
+ if (startDate != null && endDate != null)
+ {
+ query += " WHERE RecordTime >= @StartDate AND RecordTime <= @EndDate";
+ }
+
+ query += " ORDER BY RecordTime";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (startDate != null && endDate != null)
+ {
+ cmd.Parameters.AddWithValue("@StartDate", startDate);
+ cmd.Parameters.AddWithValue("@EndDate", endDate);
+ }
+
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ records.Add(new CO2Record
+ {
+ Flow = reader.GetDouble(0), // 二氧化碳浓度(%)
+ Pressure = reader.GetDouble(1), // 压力(pa)
+ RecordTime = reader.GetDateTime(2) // 时间
+ });
+ }
+ }
+ }
+ }
+ return records;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取排气流表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+
+ private void HomePage_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 检查是否有数据
+ if (_records == null || !_records.Any())
+ return;
+
+ // 判断当前是否已经全选
+ bool allSelected = _records.All(r => r.IsSelected);
+
+ // 如果已经全选则取消全选,否则全选
+ foreach (var record in _records)
+ {
+ record.IsSelected = !allSelected;
+ }
+
+ // 刷新DataGrid以显示更新后的选中状态
+ DataGridReport.Items.Refresh();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReportWindow2.xaml b/ReportWindow2.xaml
new file mode 100644
index 0000000..d4b02fa
--- /dev/null
+++ b/ReportWindow2.xaml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReportWindow2.xaml.cs b/ReportWindow2.xaml.cs
new file mode 100644
index 0000000..50d4aad
--- /dev/null
+++ b/ReportWindow2.xaml.cs
@@ -0,0 +1,408 @@
+using Microsoft.Win32;
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data.SQLite;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public class AirRecordsRecordWithSelection : FlowPressureRecord
+ {
+ public bool IsSelected { get; set; }
+ }
+
+ public partial class ReportWindow2 : Window
+ {
+ private const int PageSize = 10;
+ private int currentPage = 1;
+ private int totalRecords = 0;
+
+ // 新增:保存当前查询条件,用于删除后刷新
+ private string _currentStartDate = null;
+ private string _currentEndDate = null;
+ // 新增:使用ObservableCollection支持数据绑定刷新
+ private ObservableCollection _records = new ObservableCollection();
+
+ public ReportWindow2()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+
+ private void LoadData(string startDate = null, string endDate = null)
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Id, Flow, Pressure, RecordTime FROM AirRecords";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ query += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+ query += " LIMIT @offset, @limit";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ cmd.Parameters.AddWithValue("@startDate", startDate);
+ cmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+ cmd.Parameters.AddWithValue("@offset", (currentPage - 1) * PageSize);
+ cmd.Parameters.AddWithValue("@limit", PageSize);
+
+ //List records = new List();
+ _records.Clear(); // 清空现有数据
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ _records.Add(new AirRecordsRecordWithSelection
+ {
+ Id = reader.GetInt32(0),
+ Flow = reader.GetDouble(1),
+ Pressure = reader.GetDouble(2),
+ RecordTime = reader.GetDateTime(3),
+ IsSelected = false // 默认未选中
+ });
+ }
+ }
+
+ DataGridReport.ItemsSource = _records;
+ }
+
+ // 获取总记录数,考虑当前查询条件
+ string countQuery = "SELECT COUNT(*) FROM AirRecords";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countQuery += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+
+ using (SQLiteCommand countCmd = new SQLiteCommand(countQuery, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countCmd.Parameters.AddWithValue("@startDate", startDate);
+ countCmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+
+ totalRecords = Convert.ToInt32(countCmd.ExecuteScalar());
+ }
+
+ UpdatePageInfo();
+ }
+ }
+
+
+ private void QueryButton_Click(object sender, RoutedEventArgs e)
+ {
+ string? startDate = StartDatePicker.SelectedDate?.ToString("yyyy-MM-dd") ?? null;
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1); // 设置为23:59:59.9999999
+ }
+
+ string? endDateString = endDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? null;
+
+ currentPage = 1;
+ LoadData(startDate, endDateString);
+ }
+
+ private void UpdatePageInfo()
+ {
+ PageInfo.Text = $"第 {currentPage} 页 / 共 {Math.Ceiling((double)totalRecords / PageSize)} 页 {totalRecords}条记录";
+ }
+
+ private void PreviousPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage > 1)
+ {
+ currentPage--;
+ LoadData();
+ }
+ }
+
+ private void NextPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage * PageSize < totalRecords)
+ {
+ currentPage++;
+ LoadData();
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+
+ private void DeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is int id)
+ {
+ var result = MessageBox.Show($"确定要删除ID为 {id} 的记录吗?", "确认删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ DeleteRecordFromDb(id);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+ }
+
+ // 新增:批量删除功能
+ private void BatchDeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedIds = _records.Where(r => r.IsSelected).Select(r => r.Id).ToList();
+ if (!selectedIds.Any())
+ {
+ MessageBox.Show("请先选择要删除的记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var result = MessageBox.Show($"确定要删除选中的 {selectedIds.Count} 条记录吗?", "确认批量删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ BatchDeleteFromDb(selectedIds);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ // 新增:数据库单条删除操作
+ private void DeleteRecordFromDb(int id)
+ {
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ using (var cmd = new SQLiteCommand("DELETE FROM AirRecords WHERE Id = @Id", conn))
+ {
+ cmd.Parameters.AddWithValue("@Id", id);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ // 新增:数据库批量删除操作
+ private void BatchDeleteFromDb(List ids)
+ {
+ if (!ids.Any()) return;
+
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+
+ string idsParam = string.Join(",", ids.Select((_, i) => $"@Id{i}"));
+ using (var cmd = new SQLiteCommand($"DELETE FROM AirRecords WHERE Id IN ({idsParam})", conn))
+ {
+ // 添加参数
+ for (int i = 0; i < ids.Count; i++)
+ {
+ cmd.Parameters.AddWithValue($"@Id{i}", ids[i]);
+ }
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void Export_Click(object sender, RoutedEventArgs e)
+ {
+
+ DateTime? startDate = null;
+ if (DateTime.TryParse(StartDatePicker.SelectedDate?.ToString(), out DateTime tempStartDate))
+ {
+ startDate = tempStartDate;
+ }
+
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ // 步骤2:读取CO2表数据
+ List co2Records = ReadCO2RecordsFromDatabase(startDate, endDate);
+
+ if (co2Records == null || !co2Records.Any())
+ {
+ MessageBox.Show("气阻表中无数据,无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 步骤3:导出Excel
+ bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
+ if (exportSuccess)
+ {
+ MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show("Excel导出失败,请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private bool ExportCO2RecordsToExcel(List records)
+ {
+ try
+ {
+ SaveFileDialog saveDialog = new SaveFileDialog
+ {
+ Filter = "Excel文件 (*.xlsx)|*.xlsx",
+ FileName = $"气阻记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
+ Title = "保存气阻记录"
+ };
+
+ // 用户取消选择则返回
+ bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
+
+
+ // EPPlus 许可设置(.NET 8 必须显式设置)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 非商业用途
+
+ // 创建并写入Excel
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("气阻记录");
+
+ // 表头(对应DataGrid列)
+ worksheet.Cells[1, 1].Value = "气阻hpa";
+ worksheet.Cells[1, 2].Value = "流量L/min";
+ worksheet.Cells[1, 3].Value = "时间";
+
+ // 表头样式(加粗、居中)
+ using (var headerRange = worksheet.Cells[1, 1, 1, 6])
+ {
+ headerRange.Style.Font.Bold = true;
+ headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
+ }
+
+ // 填充数据(从第2行开始)
+ for (int i = 0; i < records.Count; i++)
+ {
+ int row = i + 2;
+ var record = records[i];
+ worksheet.Cells[row, 1].Value = record.Flow;
+ worksheet.Cells[row, 2].Value = record.Pressure;
+ worksheet.Cells[row, 3].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 自动调整列宽
+ worksheet.Cells.AutoFitColumns();
+
+ // 保存文件
+ package.Save();
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ ///
+ ///
+ ///
+ private List ReadCO2RecordsFromDatabase(DateTime? startDate, DateTime? endDate)
+ {
+ List records = new List();
+ try
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ // 构建查询语句,根据开始时间和结束时间进行筛选
+ string query = "SELECT Flow, Pressure, RecordTime FROM AirRecords WHERE 1=1";
+
+ if (startDate != null)
+ {
+ query += " AND RecordTime >= @StartDate";
+ }
+
+ if (endDate != null)
+ {
+ query += " AND RecordTime <= @EndDate";
+ }
+
+ query += " ORDER BY RecordTime";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (startDate != null)
+ {
+ cmd.Parameters.AddWithValue("@StartDate", startDate);
+ }
+
+ if (endDate != null)
+ {
+ cmd.Parameters.AddWithValue("@EndDate", endDate);
+ }
+
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ records.Add(new CO2Record
+ {
+ Flow = reader.GetDouble(0), // 二氧化碳浓度(%)
+ Pressure = reader.GetDouble(1), // 压力(pa)
+ RecordTime = reader.GetDateTime(2) // 时间
+ });
+ }
+ }
+ }
+ }
+ return records;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取气阻表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+
+
+ private void HomePage_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 检查是否有数据
+ if (_records == null || !_records.Any())
+ return;
+
+ // 判断当前是否已经全选
+ bool allSelected = _records.All(r => r.IsSelected);
+
+ // 如果已经全选则取消全选,否则全选
+ foreach (var record in _records)
+ {
+ record.IsSelected = !allSelected;
+ }
+
+ // 刷新DataGrid以显示更新后的选中状态
+ DataGridReport.Items.Refresh();
+ }
+ }
+}
diff --git a/ReportWindow3.xaml b/ReportWindow3.xaml
new file mode 100644
index 0000000..7f197ee
--- /dev/null
+++ b/ReportWindow3.xaml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReportWindow3.xaml.cs b/ReportWindow3.xaml.cs
new file mode 100644
index 0000000..b65c83c
--- /dev/null
+++ b/ReportWindow3.xaml.cs
@@ -0,0 +1,405 @@
+using Microsoft.Win32;
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data.SQLite;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+
+ public class PreventSuffocationRecordWithSelection : FlowPressureRecord
+ {
+ public bool IsSelected { get; set; }
+ }
+ public partial class ReportWindow3 : Window
+ {
+ private const int PageSize = 10;
+ private int currentPage = 1;
+ private int totalRecords = 0;
+
+ // 新增:保存当前查询条件,用于删除后刷新
+ private string _currentStartDate = null;
+ private string _currentEndDate = null;
+ // 新增:使用ObservableCollection支持数据绑定刷新
+ private ObservableCollection _records = new ObservableCollection();
+
+ public ReportWindow3()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+
+ private void LoadData(string startDate = null, string endDate = null)
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Id, Flow, Pressure, RecordTime FROM PreventSuffocation";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ query += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+ query += " LIMIT @offset, @limit";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ cmd.Parameters.AddWithValue("@startDate", startDate);
+ cmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+ cmd.Parameters.AddWithValue("@offset", (currentPage - 1) * PageSize);
+ cmd.Parameters.AddWithValue("@limit", PageSize);
+
+ _records.Clear(); // 清空现有数据
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ _records.Add(new PreventSuffocationRecordWithSelection
+ {
+ Id = reader.GetInt32(0),
+ Flow = reader.GetDouble(1),
+ Pressure = reader.GetDouble(2),
+ RecordTime = reader.GetDateTime(3),
+ IsSelected = false
+ });
+ }
+ }
+
+ DataGridReport.ItemsSource = _records;
+ }
+
+ // 获取总记录数,考虑当前查询条件
+ string countQuery = "SELECT COUNT(*) FROM PreventSuffocation";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countQuery += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+
+ using (SQLiteCommand countCmd = new SQLiteCommand(countQuery, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countCmd.Parameters.AddWithValue("@startDate", startDate);
+ countCmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+
+ totalRecords = Convert.ToInt32(countCmd.ExecuteScalar());
+ }
+
+ UpdatePageInfo();
+ }
+ }
+
+
+ private void QueryButton_Click(object sender, RoutedEventArgs e)
+ {
+ string? startDate = StartDatePicker.SelectedDate?.ToString("yyyy-MM-dd") ?? null;
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1); // 设置为23:59:59.9999999
+ }
+
+ string? endDateString = endDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? null;
+
+ currentPage = 1;
+ LoadData(startDate, endDateString);
+ }
+
+ private void UpdatePageInfo()
+ {
+ PageInfo.Text = $"第 {currentPage} 页 / 共 {Math.Ceiling((double)totalRecords / PageSize)} 页 {totalRecords}条记录";
+ }
+
+ private void PreviousPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage > 1)
+ {
+ currentPage--;
+ LoadData();
+ }
+ }
+
+ private void NextPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage * PageSize < totalRecords)
+ {
+ currentPage++;
+ LoadData();
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+
+ private void DeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is int id)
+ {
+ var result = MessageBox.Show($"确定要删除ID为 {id} 的记录吗?", "确认删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ DeleteRecordFromDb(id);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+ }
+
+ // 新增:批量删除功能
+ private void BatchDeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedIds = _records.Where(r => r.IsSelected).Select(r => r.Id).ToList();
+ if (!selectedIds.Any())
+ {
+ MessageBox.Show("请先选择要删除的记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var result = MessageBox.Show($"确定要删除选中的 {selectedIds.Count} 条记录吗?", "确认批量删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ BatchDeleteFromDb(selectedIds);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ // 新增:数据库单条删除操作
+ private void DeleteRecordFromDb(int id)
+ {
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ using (var cmd = new SQLiteCommand("DELETE FROM PreventSuffocation WHERE Id = @Id", conn))
+ {
+ cmd.Parameters.AddWithValue("@Id", id);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ // 新增:数据库批量删除操作
+ private void BatchDeleteFromDb(List ids)
+ {
+ if (!ids.Any()) return;
+
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+
+ string idsParam = string.Join(",", ids.Select((_, i) => $"@Id{i}"));
+ using (var cmd = new SQLiteCommand($"DELETE FROM PreventSuffocation WHERE Id IN ({idsParam})", conn))
+ {
+ // 添加参数
+ for (int i = 0; i < ids.Count; i++)
+ {
+ cmd.Parameters.AddWithValue($"@Id{i}", ids[i]);
+ }
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void Export_Click(object sender, RoutedEventArgs e)
+ {
+
+ DateTime? startDate = null;
+ if (DateTime.TryParse(StartDatePicker.SelectedDate?.ToString(), out DateTime tempStartDate))
+ {
+ startDate = tempStartDate;
+ }
+
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ // 步骤2:读取CO2表数据
+ List co2Records = ReadCO2RecordsFromDatabase(startDate, endDate);
+
+ if (co2Records == null || !co2Records.Any())
+ {
+ MessageBox.Show("防室息阀压力表中无数据,无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 步骤3:导出Excel
+ bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
+ if (exportSuccess)
+ {
+ MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show("Excel导出失败,请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+ private bool ExportCO2RecordsToExcel(List records)
+ {
+ try
+ {
+ SaveFileDialog saveDialog = new SaveFileDialog
+ {
+ Filter = "Excel文件 (*.xlsx)|*.xlsx",
+ FileName = $"防室息阀压力记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
+ Title = "保存防室息阀压力记录"
+ };
+
+ // 用户取消选择则返回
+ bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
+
+
+ // EPPlus 许可设置(.NET 8 必须显式设置)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 非商业用途
+
+ // 创建并写入Excel
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("防室息阀压力记录");
+
+ // 表头(对应DataGrid列)
+ worksheet.Cells[1, 1].Value = "开阀压力pa";
+ worksheet.Cells[1, 2].Value = "关阀压力pa";
+ worksheet.Cells[1, 3].Value = "时间";
+
+ // 表头样式(加粗、居中)
+ using (var headerRange = worksheet.Cells[1, 1, 1, 6])
+ {
+ headerRange.Style.Font.Bold = true;
+ headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
+ }
+
+ // 填充数据(从第2行开始)
+ for (int i = 0; i < records.Count; i++)
+ {
+ int row = i + 2;
+ var record = records[i];
+ worksheet.Cells[row, 1].Value = record.Flow;
+ worksheet.Cells[row, 2].Value = record.Pressure;
+ worksheet.Cells[row, 3].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 自动调整列宽
+ worksheet.Cells.AutoFitColumns();
+
+ // 保存文件
+ package.Save();
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ ///
+ ///
+ ///
+ private List ReadCO2RecordsFromDatabase(DateTime? startDate, DateTime? endDate)
+ {
+ List records = new List();
+ try
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ // 构建查询语句,根据开始时间和结束时间进行筛选
+ string query = "SELECT Flow, Pressure, RecordTime FROM PreventSuffocation WHERE 1=1";
+
+ if (startDate != null)
+ {
+ query += " AND RecordTime >= @StartDate";
+ }
+
+ if (endDate != null)
+ {
+ query += " AND RecordTime <= @EndDate";
+ }
+
+ query += " ORDER BY RecordTime";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (startDate != null)
+ {
+ cmd.Parameters.AddWithValue("@StartDate", startDate);
+ }
+
+ if (endDate != null)
+ {
+ cmd.Parameters.AddWithValue("@EndDate", endDate);
+ }
+
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ records.Add(new CO2Record
+ {
+ Flow = reader.GetDouble(0), // 二氧化碳浓度(%)
+ Pressure = reader.GetDouble(1), // 压力(pa)
+ RecordTime = reader.GetDateTime(2) // 时间
+ });
+ }
+ }
+ }
+ }
+ return records;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取防室息阀压力表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+
+ private void HomePage_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 检查是否有数据
+ if (_records == null || !_records.Any())
+ return;
+
+ // 判断当前是否已经全选
+ bool allSelected = _records.All(r => r.IsSelected);
+
+ // 如果已经全选则取消全选,否则全选
+ foreach (var record in _records)
+ {
+ record.IsSelected = !allSelected;
+ }
+
+ // 刷新DataGrid以显示更新后的选中状态
+ DataGridReport.Items.Refresh();
+ }
+ }
+}
diff --git a/ReportWindow4.xaml b/ReportWindow4.xaml
new file mode 100644
index 0000000..79b9f92
--- /dev/null
+++ b/ReportWindow4.xaml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReportWindow4.xaml.cs b/ReportWindow4.xaml.cs
new file mode 100644
index 0000000..b0039fb
--- /dev/null
+++ b/ReportWindow4.xaml.cs
@@ -0,0 +1,407 @@
+using Microsoft.Win32;
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data.SQLite;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public class ExpiratoryResistanceRecordWithSelection : FlowPressureRecord
+ {
+ public bool IsSelected { get; set; }
+ }
+ public partial class ReportWindow4 : Window
+ {
+ private const int PageSize = 10;
+ private int currentPage = 1;
+ private int totalRecords = 0;
+
+ // 新增:保存当前查询条件,用于删除后刷新
+ private string _currentStartDate = null;
+ private string _currentEndDate = null;
+ // 新增:使用ObservableCollection支持数据绑定刷新
+ private ObservableCollection _records = new ObservableCollection();
+
+ public ReportWindow4()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+
+ private void LoadData(string startDate = null, string endDate = null)
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Id, Flow, Pressure, RecordTime FROM ExpiratoryResistance";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ query += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+ query += " LIMIT @offset, @limit";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ cmd.Parameters.AddWithValue("@startDate", startDate);
+ cmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+ cmd.Parameters.AddWithValue("@offset", (currentPage - 1) * PageSize);
+ cmd.Parameters.AddWithValue("@limit", PageSize);
+
+ _records.Clear(); // 清空现有数据
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ _records.Add(new ExpiratoryResistanceRecordWithSelection
+ {
+ Id = reader.GetInt32(0),
+ Flow = reader.GetDouble(1),
+ Pressure = reader.GetDouble(2),
+ RecordTime = reader.GetDateTime(3),
+ IsSelected = false
+ });
+ }
+ }
+
+ DataGridReport.ItemsSource = _records;
+ }
+
+ // 获取总记录数,考虑当前查询条件
+ string countQuery = "SELECT COUNT(*) FROM ExpiratoryResistance";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countQuery += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+
+ using (SQLiteCommand countCmd = new SQLiteCommand(countQuery, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countCmd.Parameters.AddWithValue("@startDate", startDate);
+ countCmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+
+ totalRecords = Convert.ToInt32(countCmd.ExecuteScalar());
+ }
+
+ UpdatePageInfo();
+ }
+ }
+
+
+ private void QueryButton_Click(object sender, RoutedEventArgs e)
+ {
+ string? startDate = StartDatePicker.SelectedDate?.ToString("yyyy-MM-dd") ?? null;
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1); // 设置为23:59:59.9999999
+ }
+
+ string? endDateString = endDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? null;
+
+ currentPage = 1;
+ LoadData(startDate, endDateString);
+ }
+
+ private void UpdatePageInfo()
+ {
+ PageInfo.Text = $"第 {currentPage} 页 / 共 {Math.Ceiling((double)totalRecords / PageSize)} 页 {totalRecords}条记录";
+ }
+
+ private void PreviousPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage > 1)
+ {
+ currentPage--;
+ LoadData();
+ }
+ }
+
+ private void NextPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage * PageSize < totalRecords)
+ {
+ currentPage++;
+ LoadData();
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+
+ private void DeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is int id)
+ {
+ var result = MessageBox.Show($"确定要删除ID为 {id} 的记录吗?", "确认删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ DeleteRecordFromDb(id);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+ }
+
+ // 新增:批量删除功能
+ private void BatchDeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedIds = _records.Where(r => r.IsSelected).Select(r => r.Id).ToList();
+ if (!selectedIds.Any())
+ {
+ MessageBox.Show("请先选择要删除的记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var result = MessageBox.Show($"确定要删除选中的 {selectedIds.Count} 条记录吗?", "确认批量删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ BatchDeleteFromDb(selectedIds);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ // 新增:数据库单条删除操作
+ private void DeleteRecordFromDb(int id)
+ {
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ using (var cmd = new SQLiteCommand("DELETE FROM ExpiratoryResistance WHERE Id = @Id", conn))
+ {
+ cmd.Parameters.AddWithValue("@Id", id);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ // 新增:数据库批量删除操作
+ private void BatchDeleteFromDb(List ids)
+ {
+ if (!ids.Any()) return;
+
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+
+ string idsParam = string.Join(",", ids.Select((_, i) => $"@Id{i}"));
+ using (var cmd = new SQLiteCommand($"DELETE FROM ExpiratoryResistance WHERE Id IN ({idsParam})", conn))
+ {
+ // 添加参数
+ for (int i = 0; i < ids.Count; i++)
+ {
+ cmd.Parameters.AddWithValue($"@Id{i}", ids[i]);
+ }
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void Export_Click(object sender, RoutedEventArgs e)
+ {
+
+ DateTime? startDate = null;
+ if (DateTime.TryParse(StartDatePicker.SelectedDate?.ToString(), out DateTime tempStartDate))
+ {
+ startDate = tempStartDate;
+ }
+
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ // 步骤2:读取CO2表数据
+ List co2Records = ReadCO2RecordsFromDatabase(startDate, endDate);
+
+ if (co2Records == null || !co2Records.Any())
+ {
+ MessageBox.Show("单一故障气阻表中无数据,无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 步骤3:导出Excel
+ bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
+ if (exportSuccess)
+ {
+ MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show("Excel导出失败,请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+
+ private bool ExportCO2RecordsToExcel(List records)
+ {
+ try
+ {
+ // WPF 原生保存对话框(替代 WinForms)
+ SaveFileDialog saveDialog = new SaveFileDialog
+ {
+ Filter = "Excel文件 (*.xlsx)|*.xlsx",
+ FileName = $"单一故障气阻记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
+ Title = "保存单一故障气阻记录"
+ };
+
+ // 用户取消选择则返回
+ bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
+
+
+ // EPPlus 许可设置(.NET 8 必须显式设置)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 非商业用途
+
+ // 创建并写入Excel
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("单一故障气阻记录");
+
+ // 表头(对应DataGrid列)
+ worksheet.Cells[1, 1].Value = "呼气阻力pa";
+ worksheet.Cells[1, 2].Value = "吸气阻力pa";
+ worksheet.Cells[1, 3].Value = "时间";
+
+ // 表头样式(加粗、居中)
+ using (var headerRange = worksheet.Cells[1, 1, 1, 6])
+ {
+ headerRange.Style.Font.Bold = true;
+ headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
+ }
+
+ // 填充数据(从第2行开始)
+ for (int i = 0; i < records.Count; i++)
+ {
+ int row = i + 2;
+ var record = records[i];
+ worksheet.Cells[row, 1].Value = record.Flow;
+ worksheet.Cells[row, 2].Value = record.Pressure;
+ worksheet.Cells[row, 3].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 自动调整列宽
+ worksheet.Cells.AutoFitColumns();
+
+ // 保存文件
+ package.Save();
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ ///
+ ///
+ ///
+ private List ReadCO2RecordsFromDatabase(DateTime? startDate, DateTime? endDate)
+ {
+ List records = new List();
+ try
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ // 构建查询语句,根据开始时间和结束时间进行筛选
+ string query = "SELECT Flow, Pressure, RecordTime FROM ExpiratoryResistance WHERE 1=1";
+
+ if (startDate != null)
+ {
+ query += " AND RecordTime >= @StartDate";
+ }
+
+ if (endDate != null)
+ {
+ query += " AND RecordTime <= @EndDate";
+ }
+
+ query += " ORDER BY RecordTime";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (startDate != null)
+ {
+ cmd.Parameters.AddWithValue("@StartDate", startDate);
+ }
+
+ if (endDate != null)
+ {
+ cmd.Parameters.AddWithValue("@EndDate", endDate);
+ }
+
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ records.Add(new CO2Record
+ {
+ Flow = reader.GetDouble(0), // 二氧化碳浓度(%)
+ Pressure = reader.GetDouble(1), // 压力(pa)
+ RecordTime = reader.GetDateTime(2) // 时间
+ });
+ }
+ }
+ }
+ }
+ return records;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取单一故障气阻表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+
+ private void HomePage_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 检查是否有数据
+ if (_records == null || !_records.Any())
+ return;
+
+ // 判断当前是否已经全选
+ bool allSelected = _records.All(r => r.IsSelected);
+
+ // 如果已经全选则取消全选,否则全选
+ foreach (var record in _records)
+ {
+ record.IsSelected = !allSelected;
+ }
+
+ // 刷新DataGrid以显示更新后的选中状态
+ DataGridReport.Items.Refresh();
+ }
+ }
+}
diff --git a/ReportWindow5.xaml b/ReportWindow5.xaml
new file mode 100644
index 0000000..dc55210
--- /dev/null
+++ b/ReportWindow5.xaml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReportWindow5.xaml.cs b/ReportWindow5.xaml.cs
new file mode 100644
index 0000000..2654e64
--- /dev/null
+++ b/ReportWindow5.xaml.cs
@@ -0,0 +1,417 @@
+using Microsoft.Win32;
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data.SQLite;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public class CO2RecordWithSelection : FlowPressureRecord
+ {
+ public bool IsSelected { get; set; }
+ }
+ public partial class ReportWindow5 : Window
+ {
+ private const int PageSize = 10;
+ private int currentPage = 1;
+ private int totalRecords = 0;
+ // 新增:保存当前查询条件,用于删除后刷新
+ private string _currentStartDate = null;
+ private string _currentEndDate = null;
+ // 新增:使用ObservableCollection支持数据绑定刷新
+ private ObservableCollection _records = new ObservableCollection();
+
+ public ReportWindow5()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+
+ private void LoadData(string startDate = null, string endDate = null)
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Id, Flow, Pressure, BeginCO2, EndCO2, CO2Added, RecordTime FROM CO2";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ query += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+ query += " LIMIT @offset, @limit";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ cmd.Parameters.AddWithValue("@startDate", startDate);
+ cmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+ cmd.Parameters.AddWithValue("@offset", (currentPage - 1) * PageSize);
+ cmd.Parameters.AddWithValue("@limit", PageSize);
+
+ _records.Clear(); // 清空现有数据
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ _records.Add(new CO2RecordWithSelection
+ {
+ Id = reader.GetInt32(0),
+ Flow = reader.GetDouble(1),
+ Pressure = reader.GetDouble(2),
+ BeginCO2 = reader.GetDouble(3),
+ EndCO2 = reader.GetDouble(4),
+ CO2Added = reader.GetDouble(5),
+ RecordTime = reader.GetDateTime(6),
+ IsSelected = false
+ });
+ }
+ }
+
+ DataGridReport.ItemsSource = _records;
+ }
+
+ // 获取总记录数,考虑当前查询条件
+ string countQuery = "SELECT COUNT(*) FROM CO2";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countQuery += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+
+ using (SQLiteCommand countCmd = new SQLiteCommand(countQuery, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countCmd.Parameters.AddWithValue("@startDate", startDate);
+ countCmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+
+ totalRecords = Convert.ToInt32(countCmd.ExecuteScalar());
+ }
+
+ UpdatePageInfo();
+ }
+ }
+
+
+ private void QueryButton_Click(object sender, RoutedEventArgs e)
+ {
+ string? startDate = StartDatePicker.SelectedDate?.ToString("yyyy-MM-dd") ?? null;
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1); // 设置为23:59:59.9999999
+ }
+
+ string? endDateString = endDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? null;
+
+ currentPage = 1;
+ LoadData(startDate, endDateString);
+ }
+
+ private void UpdatePageInfo()
+ {
+ PageInfo.Text = $"第 {currentPage} 页 / 共 {Math.Ceiling((double)totalRecords / PageSize)} 页 {totalRecords}条记录";
+ }
+
+ private void PreviousPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage > 1)
+ {
+ currentPage--;
+ LoadData();
+ }
+ }
+
+ private void NextPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage * PageSize < totalRecords)
+ {
+ currentPage++;
+ LoadData();
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+ private void DeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is int id)
+ {
+ var result = MessageBox.Show($"确定要删除ID为 {id} 的记录吗?", "确认删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ DeleteRecordFromDb(id);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+ }
+
+ // 新增:批量删除功能
+ private void BatchDeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedIds = _records.Where(r => r.IsSelected).Select(r => r.Id).ToList();
+ if (!selectedIds.Any())
+ {
+ MessageBox.Show("请先选择要删除的记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var result = MessageBox.Show($"确定要删除选中的 {selectedIds.Count} 条记录吗?", "确认批量删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ BatchDeleteFromDb(selectedIds);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ // 新增:数据库单条删除操作
+ private void DeleteRecordFromDb(int id)
+ {
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ using (var cmd = new SQLiteCommand("DELETE FROM CO2 WHERE Id = @Id", conn))
+ {
+ cmd.Parameters.AddWithValue("@Id", id);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ // 新增:数据库批量删除操作
+ private void BatchDeleteFromDb(List ids)
+ {
+ if (!ids.Any()) return;
+
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+
+ string idsParam = string.Join(",", ids.Select((_, i) => $"@Id{i}"));
+ using (var cmd = new SQLiteCommand($"DELETE FROM CO2 WHERE Id IN ({idsParam})", conn))
+ {
+ // 添加参数
+ for (int i = 0; i < ids.Count; i++)
+ {
+ cmd.Parameters.AddWithValue($"@Id{i}", ids[i]);
+ }
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void Export_Click(object sender, RoutedEventArgs e)
+ {
+
+ DateTime? startDate = null;
+ if (DateTime.TryParse(StartDatePicker.SelectedDate?.ToString(), out DateTime tempStartDate))
+ {
+ startDate = tempStartDate;
+ }
+
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ // 步骤2:读取CO2表数据
+ List co2Records = ReadCO2RecordsFromDatabase(startDate, endDate);
+
+ if (co2Records == null || !co2Records.Any())
+ {
+ MessageBox.Show("CO2表中无数据,无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 步骤3:导出Excel
+ bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
+ if (exportSuccess)
+ {
+ MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show("Excel导出失败,请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ ///
+ /// 从数据库CO2表读取数据(不变)
+ ///
+ private List ReadCO2RecordsFromDatabase(DateTime? startDate, DateTime? endDate)
+ {
+ List records = new List();
+ try
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ // 构建查询语句,根据开始时间和结束时间进行筛选
+ string query = "SELECT BeginCO2, EndCO2, CO2Added, RecordTime FROM CO2 WHERE 1=1";
+
+ if (startDate != null)
+ {
+ query += " AND RecordTime >= @StartDate";
+ }
+
+ if (endDate != null)
+ {
+ query += " AND RecordTime <= @EndDate";
+ }
+
+ query += " ORDER BY RecordTime";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (startDate != null)
+ {
+ cmd.Parameters.AddWithValue("@StartDate", startDate);
+ }
+
+ if (endDate != null)
+ {
+ cmd.Parameters.AddWithValue("@EndDate", endDate);
+ }
+
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ records.Add(new CO2Record
+ {
+ BeginCO2 = reader.GetDouble(0), // 开始CO2浓度(%)
+ EndCO2 = reader.GetDouble(1), // 终CO2浓度(%)
+ CO2Added = reader.GetDouble(2), // CO2浓度相对增加(%)
+ RecordTime = reader.GetDateTime(3) // 时间
+ });
+ }
+ }
+ }
+ }
+ return records;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取CO2表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+
+ ///
+ /// 将CO2数据导出到Excel(WPF .NET 8 适配版)
+ ///
+ private bool ExportCO2RecordsToExcel(List records)
+ {
+ try
+ {
+ // WPF 原生保存对话框(替代 WinForms)
+ SaveFileDialog saveDialog = new SaveFileDialog
+ {
+ Filter = "Excel文件 (*.xlsx)|*.xlsx",
+ FileName = $"CO2记录画面导出_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
+ Title = "保存CO2记录"
+ };
+
+ // 用户取消选择则返回
+ bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
+
+
+ // EPPlus 许可设置(.NET 8 必须显式设置)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 非商业用途
+
+ // 创建并写入Excel
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("CO2记录");
+
+ // 表头(对应DataGrid列)
+ //worksheet.Cells[1, 1].Value = "二氧化碳浓度(%)";
+ //worksheet.Cells[1, 2].Value = "压力(pa)";
+ worksheet.Cells[1, 1].Value = "开始CO2浓度(%)";
+ worksheet.Cells[1, 2].Value = "终CO2浓度(%)";
+ worksheet.Cells[1, 3].Value = "CO2浓度相对增加(%)";
+ worksheet.Cells[1, 4].Value = "时间";
+
+ // 表头样式(加粗、居中)
+ using (var headerRange = worksheet.Cells[1, 1, 1, 6])
+ {
+ headerRange.Style.Font.Bold = true;
+ headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
+ }
+
+ // 填充数据(从第2行开始)
+ for (int i = 0; i < records.Count; i++)
+ {
+ int row = i + 2;
+ var record = records[i];
+ //worksheet.Cells[row, 1].Value = record.Flow;
+ //worksheet.Cells[row, 2].Value = record.Pressure;
+ worksheet.Cells[row, 1].Value = record.BeginCO2;
+ worksheet.Cells[row, 2].Value = record.EndCO2;
+ worksheet.Cells[row, 3].Value = record.CO2Added;
+ worksheet.Cells[row, 4].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 自动调整列宽
+ worksheet.Cells.AutoFitColumns();
+
+ // 保存文件
+ package.Save();
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ private void HomePage_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 检查是否有数据
+ if (_records == null || !_records.Any())
+ return;
+
+ // 判断当前是否已经全选
+ bool allSelected = _records.All(r => r.IsSelected);
+
+ // 如果已经全选则取消全选,否则全选
+ foreach (var record in _records)
+ {
+ record.IsSelected = !allSelected;
+ }
+
+ // 刷新DataGrid以显示更新后的选中状态
+ DataGridReport.Items.Refresh();
+ }
+ }
+}
diff --git a/ReportWindow6.xaml b/ReportWindow6.xaml
new file mode 100644
index 0000000..72b6ed0
--- /dev/null
+++ b/ReportWindow6.xaml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ReportWindow6.xaml.cs b/ReportWindow6.xaml.cs
new file mode 100644
index 0000000..c470a3e
--- /dev/null
+++ b/ReportWindow6.xaml.cs
@@ -0,0 +1,413 @@
+using Microsoft.Win32;
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data.SQLite;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+
+ public class CO2ProcessRecordWithSelection : FlowPressureRecord
+ {
+ public bool IsSelected { get; set; }
+ }
+ public partial class ReportWindow6 : Window
+ {
+ private const int PageSize = 10;
+ private int currentPage = 1;
+ private int totalRecords = 0;
+ // 新增:保存当前查询条件,用于删除后刷新
+ private string _currentStartDate = null;
+ private string _currentEndDate = null;
+ // 新增:使用ObservableCollection支持数据绑定刷新
+ private ObservableCollection _records = new ObservableCollection();
+
+ public ReportWindow6()
+ {
+ InitializeComponent();
+ LoadData();
+ }
+
+ private void LoadData(string startDate = null, string endDate = null)
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ string query = "SELECT Id, Flow, Pressure, BeginCO2, EndCO2, CO2Added, RecordTime FROM CO2";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ query += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+ query += " LIMIT @offset, @limit";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ cmd.Parameters.AddWithValue("@startDate", startDate);
+ cmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+ cmd.Parameters.AddWithValue("@offset", (currentPage - 1) * PageSize);
+ cmd.Parameters.AddWithValue("@limit", PageSize);
+
+ _records.Clear(); // 清空现有数据
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ _records.Add(new CO2ProcessRecordWithSelection
+ {
+ Id = reader.GetInt32(0),
+ Flow = reader.GetDouble(1),
+ Pressure = reader.GetDouble(2),
+ BeginCO2 = reader.GetDouble(3),
+ EndCO2 = reader.GetDouble(4),
+ CO2Added = reader.GetDouble(5),
+ RecordTime = reader.GetDateTime(6)
+ });
+ }
+ }
+
+ DataGridReport.ItemsSource = _records;
+ }
+
+ // 获取总记录数,考虑当前查询条件
+ string countQuery = "SELECT COUNT(*) FROM CO2";
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countQuery += " WHERE RecordTime BETWEEN @startDate AND @endDate";
+ }
+
+ using (SQLiteCommand countCmd = new SQLiteCommand(countQuery, conn))
+ {
+ if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate))
+ {
+ countCmd.Parameters.AddWithValue("@startDate", startDate);
+ countCmd.Parameters.AddWithValue("@endDate", endDate);
+ }
+
+ totalRecords = Convert.ToInt32(countCmd.ExecuteScalar());
+ }
+
+ UpdatePageInfo();
+ }
+ }
+
+
+ private void QueryButton_Click(object sender, RoutedEventArgs e)
+ {
+ string? startDate = StartDatePicker.SelectedDate?.ToString("yyyy-MM-dd") ?? null;
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1); // 设置为23:59:59.9999999
+ }
+
+ string? endDateString = endDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? null;
+
+ currentPage = 1;
+ LoadData(startDate, endDateString);
+ }
+
+ private void UpdatePageInfo()
+ {
+ PageInfo.Text = $"第 {currentPage} 页 / 共 {Math.Ceiling((double)totalRecords / PageSize)} 页 {totalRecords}条记录";
+ }
+
+ private void PreviousPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage > 1)
+ {
+ currentPage--;
+ LoadData();
+ }
+ }
+
+ private void NextPage_Click(object sender, RoutedEventArgs e)
+ {
+ if (currentPage * PageSize < totalRecords)
+ {
+ currentPage++;
+ LoadData();
+ }
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
+ ImageBrush brush = new ImageBrush();
+ brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute));
+ this.Background = brush;
+ }
+
+ private void DeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is int id)
+ {
+ var result = MessageBox.Show($"确定要删除ID为 {id} 的记录吗?", "确认删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ DeleteRecordFromDb(id);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+ }
+
+ // 新增:批量删除功能
+ private void BatchDeleteButton_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedIds = _records.Where(r => r.IsSelected).Select(r => r.Id).ToList();
+ if (!selectedIds.Any())
+ {
+ MessageBox.Show("请先选择要删除的记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var result = MessageBox.Show($"确定要删除选中的 {selectedIds.Count} 条记录吗?", "确认批量删除",
+ MessageBoxButton.YesNo, MessageBoxImage.Warning);
+ if (result == MessageBoxResult.Yes)
+ {
+ BatchDeleteFromDb(selectedIds);
+ // 刷新当前页数据
+ LoadData(_currentStartDate, _currentEndDate);
+ }
+ }
+
+ // 新增:数据库单条删除操作
+ private void DeleteRecordFromDb(int id)
+ {
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ using (var cmd = new SQLiteCommand("DELETE FROM CO2 WHERE Id = @Id", conn))
+ {
+ cmd.Parameters.AddWithValue("@Id", id);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ // 新增:数据库批量删除操作
+ private void BatchDeleteFromDb(List ids)
+ {
+ if (!ids.Any()) return;
+
+ using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+
+ string idsParam = string.Join(",", ids.Select((_, i) => $"@Id{i}"));
+ using (var cmd = new SQLiteCommand($"DELETE FROM CO2 WHERE Id IN ({idsParam})", conn))
+ {
+ // 添加参数
+ for (int i = 0; i < ids.Count; i++)
+ {
+ cmd.Parameters.AddWithValue($"@Id{i}", ids[i]);
+ }
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ private void Export_Click(object sender, RoutedEventArgs e)
+ {
+
+ DateTime? startDate = null;
+ if (DateTime.TryParse(StartDatePicker.SelectedDate?.ToString(), out DateTime tempStartDate))
+ {
+ startDate = tempStartDate;
+ }
+
+ DateTime? endDate = EndDatePicker.SelectedDate;
+
+ if (endDate.HasValue)
+ {
+ endDate = endDate.Value.Date.AddDays(1).AddTicks(-1);
+ }
+
+ // 步骤2:读取CO2表数据
+ List co2Records = ReadCO2RecordsFromDatabase(startDate, endDate);
+ if (co2Records == null || !co2Records.Any())
+ {
+ MessageBox.Show("CO2表中无数据,无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ // 步骤3:导出Excel
+ bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
+ if (exportSuccess)
+ {
+ MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show("Excel导出失败,请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private List ReadCO2RecordsFromDatabase(DateTime? startDate, DateTime? endDate)
+ {
+ List records = new List();
+ try
+ {
+ using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString))
+ {
+ conn.Open();
+ // 构建查询语句,根据开始时间和结束时间进行筛选
+ string query = "SELECT Flow, Pressure, BeginCO2, EndCO2, CO2Added, RecordTime FROM CO2 WHERE 1=1";
+
+ if (startDate != null)
+ {
+ query += " AND RecordTime >= @StartDate";
+ }
+
+ if (endDate != null)
+ {
+ query += " AND RecordTime <= @EndDate";
+ }
+
+ query += " ORDER BY RecordTime";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, conn))
+ {
+ if (startDate != null)
+ {
+ cmd.Parameters.AddWithValue("@StartDate", startDate);
+ }
+
+ if (endDate != null)
+ {
+ cmd.Parameters.AddWithValue("@EndDate", endDate);
+ }
+
+ using (SQLiteDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ records.Add(new CO2Record
+ {
+ Flow = reader.GetDouble(0), // 二氧化碳浓度(%)
+ Pressure = reader.GetDouble(1), // 压力(pa)
+ BeginCO2 = reader.GetDouble(2), // 开始CO2浓度(%)
+ EndCO2 = reader.GetDouble(3), // 终CO2浓度(%)
+ CO2Added = reader.GetDouble(4), // CO2浓度相对增加(%)
+ RecordTime = reader.GetDateTime(5) // 时间
+ });
+ }
+ }
+ }
+ }
+ return records;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取CO2表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return null;
+ }
+ }
+
+
+ ///
+ /// 将CO2数据导出到Excel(WPF .NET 8 适配版)
+ ///
+ private bool ExportCO2RecordsToExcel(List records)
+ {
+ try
+ {
+ // WPF 原生保存对话框(替代 WinForms)
+
+ SaveFileDialog saveDialog = new SaveFileDialog
+ {
+ Filter = "Excel文件 (*.xlsx)|*.xlsx",
+ FileName = $"CO2过程记录导出_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
+ Title = "保存CO2记录"
+ };
+
+ // 用户取消选择则返回
+ bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
+
+
+ // EPPlus 许可设置(.NET 8 必须显式设置)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+
+ // 创建并写入Excel
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("CO2记录");
+
+ // 表头(对应DataGrid列)
+ worksheet.Cells[1, 1].Value = "二氧化碳浓度(%)";
+ worksheet.Cells[1, 2].Value = "压力(pa)";
+
+ worksheet.Cells[1, 3].Value = "时间";
+
+ // 表头样式(加粗、居中)
+ using (var headerRange = worksheet.Cells[1, 1, 1, 6])
+ {
+ headerRange.Style.Font.Bold = true;
+ headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
+ }
+
+ // 填充数据(从第2行开始)
+ for (int i = 0; i < records.Count; i++)
+ {
+ int row = i + 2;
+ var record = records[i];
+ worksheet.Cells[row, 1].Value = record.Flow;
+ worksheet.Cells[row, 2].Value = record.Pressure;
+ //worksheet.Cells[row, 1].Value = record.BeginCO2;
+ //worksheet.Cells[row, 2].Value = record.EndCO2;
+ //worksheet.Cells[row, 3].Value = record.CO2Added;
+ worksheet.Cells[row, 3].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ // 自动调整列宽
+ worksheet.Cells.AutoFitColumns();
+
+ // 保存文件
+ package.Save();
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ private void HomePage_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+ {
+ // 检查是否有数据
+ if (_records == null || !_records.Any())
+ return;
+
+ // 判断当前是否已经全选
+ bool allSelected = _records.All(r => r.IsSelected);
+
+ // 如果已经全选则取消全选,否则全选
+ foreach (var record in _records)
+ {
+ record.IsSelected = !allSelected;
+ }
+
+ // 刷新DataGrid以显示更新后的选中状态
+ DataGridReport.Items.Refresh();
+ }
+ }
+}
diff --git a/Resources/sleep.jpg b/Resources/sleep.jpg
new file mode 100644
index 0000000..1a72ca1
Binary files /dev/null and b/Resources/sleep.jpg differ
diff --git a/Resources/sleep2.jpg b/Resources/sleep2.jpg
new file mode 100644
index 0000000..94557f6
Binary files /dev/null and b/Resources/sleep2.jpg differ
diff --git a/Resources/sleep3.jpg b/Resources/sleep3.jpg
new file mode 100644
index 0000000..11693a2
Binary files /dev/null and b/Resources/sleep3.jpg differ
diff --git a/Resources/sleep4.jpg b/Resources/sleep4.jpg
new file mode 100644
index 0000000..3d2e451
Binary files /dev/null and b/Resources/sleep4.jpg differ
diff --git a/SerialPortHelper/ModbusHelper.cs b/SerialPortHelper/ModbusHelper.cs
new file mode 100644
index 0000000..25b5d50
--- /dev/null
+++ b/SerialPortHelper/ModbusHelper.cs
@@ -0,0 +1,106 @@
+public static class ModbusHelper
+{
+ // 生成写单个线圈(功能码05)的Modbus指令
+ public static string GenerateWriteSingleCoilCommand(byte deviceAddress, ushort coilAddress, bool value)
+ {
+ byte[] command = new byte[6];
+ command[0] = deviceAddress;
+ command[1] = 0x05; // 写单个线圈功能码
+ command[2] = (byte)(coilAddress >> 8);
+ command[3] = (byte)coilAddress;
+ command[4] = value ? (byte)0xFF : (byte)0x00;
+ command[5] = 0x00;
+
+ // 计算CRC校验
+ ushort crc = CalculateCRC(command, 6);
+ byte[] crcBytes = BitConverter.GetBytes(crc);
+
+ // 组合完整命令(包含CRC校验)
+ byte[] fullCommand = new byte[8];
+ Array.Copy(command, fullCommand, 6);
+ fullCommand[6] = crcBytes[0]; // CRC低字节在前
+ fullCommand[7] = crcBytes[1]; // CRC高字节在后
+
+ // 转换为十六进制字符串
+ return BitConverter.ToString(fullCommand).Replace("-", "");
+ }
+
+ // 验证Modbus响应是否有效
+ public static bool ValidateResponse(string command, string response)
+ {
+ if (string.IsNullOrEmpty(response) || response.Length < 8)
+ return false;
+
+ // 解析响应字符串为字节数组
+ byte[] responseBytes = StringToByteArray(response);
+
+ // 从原始命令中提取设备地址
+ byte deviceAddress = StringToByteArray(command.Substring(0, 2))[0];
+
+ // 验证设备地址
+ if (responseBytes[0] != deviceAddress)
+ return false;
+
+ // 验证功能码(响应中的功能码应与请求相同,若有异常则功能码最高位为1)
+ byte requestFunctionCode = StringToByteArray(command.Substring(2, 2))[0];
+ byte responseFunctionCode = responseBytes[1];
+
+ // 检查是否为异常响应
+ if ((responseFunctionCode & 0x80) != 0)
+ {
+ byte errorCode = responseBytes[2];
+ Console.WriteLine($"Modbus异常响应 - 功能码: {requestFunctionCode}, 错误码: {errorCode}");
+ return false;
+ }
+
+ // 验证功能码是否匹配
+ if (responseFunctionCode != requestFunctionCode)
+ return false;
+
+ // 验证CRC校验
+ ushort receivedCrc = (ushort)((responseBytes[responseBytes.Length - 2] << 8) | responseBytes[responseBytes.Length - 1]);
+ ushort calculatedCrc = CalculateCRC(responseBytes, responseBytes.Length - 2);
+
+ return receivedCrc == calculatedCrc;
+ }
+
+ // 计算Modbus RTU CRC校验
+ private static ushort CalculateCRC(byte[] data, int length)
+ {
+ ushort crc = 0xFFFF;
+
+ for (int i = 0; i < length; i++)
+ {
+ crc ^= data[i];
+
+ for (int j = 0; j < 8; j++)
+ {
+ if ((crc & 0x0001) != 0)
+ {
+ crc >>= 1;
+ crc ^= 0xA001;
+ }
+ else
+ {
+ crc >>= 1;
+ }
+ }
+ }
+
+ return crc;
+ }
+
+ // 将十六进制字符串转换为字节数组
+ private static byte[] StringToByteArray(string hex)
+ {
+ int length = hex.Length;
+ byte[] bytes = new byte[length / 2];
+
+ for (int i = 0; i < length; i += 2)
+ {
+ bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
+ }
+
+ return bytes;
+ }
+}
\ No newline at end of file
diff --git a/SerialPortHelper/RS485Communicator.cs b/SerialPortHelper/RS485Communicator.cs
new file mode 100644
index 0000000..194d307
--- /dev/null
+++ b/SerialPortHelper/RS485Communicator.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO.Ports;
+using System.Text;
+using System.Threading;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ public class RS485Communicator
+ {
+ private SerialPort _serialPort;
+ private bool _isListening;
+
+ public event Action DataReceived;
+
+ public RS485Communicator(string portName, int baudRate = 19200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
+ {
+ _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
+ {
+ ReadTimeout = 500,
+ WriteTimeout = 500
+ };
+
+ _serialPort.DataReceived += SerialPort_DataReceived;
+ }
+
+ public void Open()
+ {
+ if (!_serialPort.IsOpen)
+ {
+ _serialPort.Open();
+ _isListening = true;
+ }
+ }
+
+ public void Close()
+ {
+ if (_serialPort.IsOpen)
+ {
+ _isListening = false;
+ _serialPort.Close();
+ }
+ }
+
+ public void SendData(string data)
+ {
+ if (_serialPort.IsOpen)
+ {
+ byte[] bytesToSend = Encoding.ASCII.GetBytes(data);
+ _serialPort.Write(bytesToSend, 0, bytesToSend.Length);
+ }
+ else
+ {
+ throw new InvalidOperationException("Serial port is not open.");
+ }
+ }
+
+ private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
+ {
+ if (_isListening && _serialPort.IsOpen)
+ {
+ string receivedData = _serialPort.ReadLine();
+ DataReceived?.Invoke(receivedData);
+ }
+ }
+ }
+}
diff --git a/Window1.xaml b/Window1.xaml
new file mode 100644
index 0000000..8b339f1
--- /dev/null
+++ b/Window1.xaml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Window1.xaml.cs b/Window1.xaml.cs
new file mode 100644
index 0000000..089de9d
--- /dev/null
+++ b/Window1.xaml.cs
@@ -0,0 +1,890 @@
+using Microsoft.Win32;
+using Modbus.Device;
+using Modbus;
+using OfficeOpenXml;
+using Sunny.UI;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Configuration;
+using System.Data.SQLite;
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using 口罩泄露定制款;
+using static ShanghaiEnvironmentalTechnology.Window5;
+
+namespace ShanghaiEnvironmentalTechnology
+{
+ ///
+ /// 气流压力测试窗口(Modbus通信+数据记录)
+ ///
+ public partial class Window1 : Window, IDisposable
+ {
+ #region 寄存器/线圈地址定义(按功能分组)
+ // 压力相关地址
+ private readonly ushort _pressureSettingRegisterAddress = 0x0138; // 压力设置地址
+ private readonly ushort _pressureRegisterAddress = 1372; // 实时压力读取地址
+ private readonly ushort _calibrationCoilAddress = 72; // 校准线圈(M72)
+ private readonly ushort _settingRegisterAddress = 312; // 流量记录地址
+
+
+ // 流量相关地址
+ private readonly ushort _flowAddress = 0x0078; // 实时流量读取地址
+ private readonly ushort _flowRegisterAddress = 0x0170; // 流量记录地址
+ private readonly ushort _flow2Address = 368; // 实时流量读取地址
+
+ // 测试控制线圈
+ private readonly ushort _testStartAddress = 60; // 测试启动(M60)
+ private readonly ushort _testStatusAddress = 0x003C; // 测试状态读取(同启动地址)
+ private readonly ushort _testStopAddress = 62; // 测试停止(M62)
+ #endregion
+
+ #region 私有字段
+ private TcpClient _tcpClient;
+ private IModbusMaster _modbusMaster;
+ private System.Timers.Timer _pressureReadTimer; // 实时压力定时器
+ private System.Timers.Timer _settingReadTimer; // 设定实时压力定时器
+ private System.Timers.Timer _flowTimer; // 实时流量定时器
+
+ private System.Timers.Timer startTimer; // 启动状态实时定时器
+ #endregion
+ DataChange c = new DataChange();
+ Function fc;
+ public Window1()
+ {
+ InitializeComponent();
+ InitializeModbusTcp();
+ Loaded += Window_Loaded;
+ }
+
+ #region 初始化与资源释放
+ ///
+ /// 初始化Modbus连接和定时器
+ ///
+ private void InitializeModbusTcp()
+ {
+ try
+ {
+ string plcIp = ConfigurationManager.AppSettings["PLC1_IP"];
+ int plcPort = int.Parse(ConfigurationManager.AppSettings["PLC1_Port"]);
+
+ _tcpClient = new TcpClient(plcIp, plcPort);
+ _modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
+ _modbusMaster.Transport.ReadTimeout = 3000;
+ _modbusMaster.Transport.WriteTimeout = 3000;
+
+ // 初始化定时器(1秒间隔)
+ _pressureReadTimer = CreateTimer(1000, OnPressureTimerElapsed);
+ _settingReadTimer = CreateTimer(1000, OnSettingReadTimerTimerElapsed);
+ _flowTimer = CreateTimer(1000, OnFlowTimerElapsed);
+ startTimer = CreateTimer(1000, OnStartTimerElapsed);
+
+ fc = new Function(_modbusMaster);
+ // 初始化数据库
+ InitializeDatabase();
+ }
+ catch (Exception ex)
+ {
+ ShowError($"Modbus初始化失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 通用定时器创建方法
+ ///
+ private System.Timers.Timer CreateTimer(int intervalMs, ElapsedEventHandler elapsedAction)
+ {
+ var timer = new System.Timers.Timer(intervalMs)
+ {
+ AutoReset = true,
+ Enabled = true
+ };
+ timer.Elapsed += elapsedAction;
+ return timer;
+ }
+
+ ///
+ /// 释放资源(定时器、TCP连接)
+ ///
+ public void Dispose()
+ {
+ _pressureReadTimer?.Dispose();
+ _flowTimer?.Dispose();
+ _tcpClient?.Close();
+ _tcpClient?.Dispose();
+ _modbusMaster = null;
+ }
+
+ ///
+ /// 窗口关闭时释放资源
+ ///
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ Dispose();
+ }
+ #endregion
+
+ #region 定时器读取逻辑
+ ///
+ /// 实时压力读取定时器
+ ///
+ private void OnPressureTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ ReadAndUpdateRegister(
+ _pressureRegisterAddress, true,
+ value => UpdatePressureUI(value.ToString())
+ );
+ }
+
+ ///
+ /// 设定实时压力读取定时器
+ ///
+ private void OnSettingReadTimerTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ ReadAndUpdateRegister(
+ _settingRegisterAddress, true,
+ value => UpdateSettingUI(value.ToString())
+ );
+ }
+
+ ///
+ /// 实时流量读取定时器
+ ///
+ private void OnFlowTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ ReadAndUpdateRegister(
+ _flowAddress, true,
+ value => UpdateFlowPressureUI(value.ToString())
+ );
+ }
+ ///
+ /// 实时流量读取定时器(修正跨线程访问UI问题)
+ ///
+ private void OnStartTimerElapsed(object sender, ElapsedEventArgs e)
+ {
+ try
+ {
+ bool[] result = _modbusMaster?.ReadCoils(0x01, 61, 1);
+ bool isTestRunning = result != null && result.Length > 0 && result[0];
+
+ TestStartButton.Dispatcher.Invoke(() =>
+ {
+ if (isTestRunning)
+ {
+ TestStartButton.Content = "测试启动成功";
+ TestStartButton.Foreground = Brushes.LightGreen;
+ }
+ else
+ {
+ TestStartButton.Content = "测试启动";
+ TestStartButton.Foreground = Brushes.White;
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"读取线圈或更新UI失败:{ex.Message}");
+ }
+ }
+
+
+ ///
+ /// 通用寄存器读取并更新UI,支持16位整数和32位浮点数
+ ///
+ /// 起始地址
+ /// 是否为浮点型(占用2个寄存器)
+ /// 更新UI的回调函数
+ private void ReadAndUpdateRegister(ushort address, bool isFloat, Action