using YiDa_WinForm.Model; using Newtonsoft.Json; using NPOI.SS.UserModel; using System; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Timers; using System.Windows.Forms; using YiDa_WinForm.Service; using YiDa_WinForm.Config; using YiDa_WinForm.Service.Mqtt; namespace YiDa_WinForm { public partial class MainForm : Form { // 依赖注入 private readonly MqttClientService _mqttService; private readonly YiDaUploadService _uploadService; private readonly ButtonOperationService _buttonService; // 配方配置 private DataTable _formula; // 配方缓存 private DataTable _formulaDic; private List _formulaList1; // 设备1选择的配方 private List _formulaList2; // 设备2选择的配方 private System.Timers.Timer _timer; // 日志文件相关路径 private readonly string _logDirectory = "log"; private string _currentLogFile; private DateTime _lastLogDate = DateTime.MinValue; // ======================================== 初始化相关方法 ======================================== /// /// 构造器 /// public MainForm() { // 初始化设计器方法 InitializeComponent(); _mqttService = new MqttClientService(); _buttonService = new ButtonOperationService(); _uploadService = new YiDaUploadService(); _mqttService.MessageReceived += OnMqttMessage; _formula = new DataTable(); _formulaDic = new DataTable(); buttonDisconnect.Enabled = false; // 初始化日志目录 InitializeLogDirectory(); } /// /// 初始化日志目录 /// private void InitializeLogDirectory() { if (!Directory.Exists(_logDirectory)) { Directory.CreateDirectory(_logDirectory); } UpdateLogFile(); } /// /// 更新当前日志文件(每日一个新文件) /// private void UpdateLogFile() { DateTime today = DateTime.Now.Date; if (today != _lastLogDate) { _lastLogDate = today; _currentLogFile = Path.Combine(_logDirectory, $"log_{today:yyyyMMdd}.txt"); } } /// /// 加载MainForm信息 /// /// /// private async void Form1_Load(object sender, EventArgs e) { try { MakePanelRound(panelLed); await LoadFormula(); await LoadMqttDic(); timer1.Enabled = true; } catch (Exception ex) { AppendLog($"初始化失败:{ex.Message}"); } } /// /// 将指定 Panel 变成圆形 /// /// private static void MakePanelRound(Panel panel) { int size = Math.Min(panel.Width, panel.Height); panel.Size = new Size(size, size); GraphicsPath path = new GraphicsPath(); path.AddEllipse(0, 0, panel.Width, panel.Height); panel.Region = new Region(path); } /// /// 加载配方 /// private async Task LoadFormula() { // 从数据库查询配方信息 DataTable dt = await _buttonService.QueryFormulaAsync(); if (dt != null && dt.Rows.Count > 0) { // 新增列用于拼接 part_number|part_name if (!dt.Columns.Contains("part_number_name")) dt.Columns.Add("part_number_name", typeof(string)); foreach (DataRow row in dt.Rows) { row["part_number_name"] = row["part_number"].ToString() + "|" + row["part_name"].ToString(); } // 使用 LINQ 去重,按 part_number + part_name var uniqueParts = dt.AsEnumerable() .GroupBy(r => r["part_number_name"].ToString()) .Select(g => g.First()) .ToList(); // 新建 DataTable 用于绑定下拉框 DataTable dtUnique = dt.Clone(); // 保留原有列 foreach (var row in uniqueParts) { dtUnique.ImportRow(row); } // 新建空白行 DataRow drNew = dtUnique.NewRow(); drNew["id"] = -1; drNew["part_number_name"] = ""; // 空行值 dtUnique.Rows.InsertAt(drNew, 0); _formula = dt; // 原始配方表(多行) bsPF.DataSource = dtUnique; bsPF2.DataSource = dtUnique; comboBox1.DisplayMember = "part_number_name"; // 显示“零件号|零件名” comboBox1.ValueMember = "part_number_name"; comboBox2.DisplayMember = "part_number_name"; comboBox2.ValueMember = "part_number_name"; // 自定义显示格式:显示“零件号 - 零件名” comboBox1.Format += (s, e) => { if (e.ListItem is DataRowView drv) { e.Value = string.IsNullOrEmpty(drv["part_number_name"].ToString()) ? "" : drv["part_number"].ToString() + " - " + drv["part_name"].ToString(); } }; comboBox2.Format += (s, e) => { if (e.ListItem is DataRowView drv) { e.Value = string.IsNullOrEmpty(drv["part_number_name"].ToString()) ? "" : drv["part_number"].ToString() + " - " + drv["part_name"].ToString(); } }; } } /// /// 加载注塑机MQTT字典 /// private async Task LoadMqttDic() { DataTable dt = await _buttonService.InitMqttDic(); if (dt != null) { _formulaDic = dt; } } // ======================================== 按钮相关方法 ======================================== /// /// 网关连接按钮 /// /// /// private async void buttonConnect_Click(object sender, EventArgs e) { try { AppendLog("正在连接MQTT服务器..."); await _mqttService.MqttClientStartAsync(); AppendLog("连接成功!"); panelLed.BackColor = Color.Green; toolStripStatusLabel1.Text = "已连接"; buttonConnect.Enabled = false; buttonDisconnect.Enabled = true; } catch (Exception ex) { panelLed.BackColor = Color.Red; AppendLog($"连接失败:{ex.Message}"); toolStripStatusLabel1.Text = "连接失败"; } } /// /// 网关断开按钮 /// /// /// private async void buttonDisconnect_Click(object sender, EventArgs e) { try { await _mqttService.MqttClientStopAsync(); AppendLog("已断开连接"); panelLed.BackColor = Color.Red; toolStripStatusLabel1.Text = "未连接"; buttonConnect.Enabled = true; buttonDisconnect.Enabled = false; } catch (Exception ex) { AppendLog($"无法断开连接:{ex.Message}"); } } /// /// 关闭MainForm按钮 /// /// /// private async void MainFormClosing(object sender, FormClosingEventArgs e) { try { // 停止定时器 if (_timer != null) { _timer.Enabled = false; _timer.Dispose(); } await _mqttService.MqttClientStopAsync(); } catch (Exception ex) { AppendLog($"关闭窗口失败:{ex.Message}"); } } /// /// 配方导入按钮 /// /// /// private async void button1_Click(object sender, EventArgs e) { try { //弹出文件选择窗口 OpenFileDialog openFile = new OpenFileDialog(); openFile.Filter = "Excel文件|*.xlsx;*.xls"; if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; DataTable excelDt = GetExcel(filePath); if (excelDt != null && excelDt.Rows.Count > 0) { await _buttonService.SaveFormulaByExcel(excelDt); await LoadFormula(); MessageBox.Show("配方信息已导入!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("不允许导入空表!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } } catch (Exception ex) { AppendLog($"配方导入失败:{ex.Message}"); MessageBox.Show($"配方导入失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 上传宜搭按钮 /// /// /// private async void button2_Click(object sender, EventArgs e) { try { // 如果 MQTT 没连接,就不允许上传 if (toolStripStatusLabel1.Text != "已连接") { MessageBox.Show("请先连接MQTT网关!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } if (string.IsNullOrEmpty(comboBox1.Text) && string.IsNullOrEmpty(comboBox2.Text)) { //如果两个下拉框都还没数据,说明没导入过配方,直接中止。 MessageBox.Show("请先导入配方信息并选择配方!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } this.button2.Enabled = false; // 停止旧定时器(避免重复) if (_timer != null) { _timer.Enabled = false; _timer.Dispose(); } // 重新获取配方(每次点击都刷新) RefreshSelectedFormula(); double seconds; if (!double.TryParse(txtLUploadPL.Text, out seconds) || seconds <= 0) { seconds = 10; //控制上传间隔,默认10秒。 } // 立即上传一次 await MqttYiDaUpload(); // 创建新定时器(.NET 4.8 兼容写法) _timer = new System.Timers.Timer(seconds * 1000); _timer.Elapsed += Timer_Elapsed; _timer.AutoReset = true; // 是否重复 _timer.Enabled = true; // 开始计时 AppendLog($"自动上传已启动,间隔:{seconds}秒"); MessageBox.Show($"自动上传已启动,间隔:{seconds}秒", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { this.button2.Enabled = true; AppendLog($"上传宜搭失败:{ex.Message}"); MessageBox.Show($"上传宜搭失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 定时器触发事件 /// /// /// private async void Timer_Elapsed(object sender, ElapsedEventArgs e) { // 每次定时器触发都重新获取最新配方 RefreshSelectedFormula(); await MqttYiDaUpload(); } /// /// 刷新选中的配方(每次上传前重新获取) /// private void RefreshSelectedFormula() { // 设备1选择配方 string selected1 = this.comboBox1.SelectedValue?.ToString(); if (string.IsNullOrEmpty(selected1) || selected1 == "-1") { _formulaList1 = null; } else { string[] arr1 = selected1.Split('|'); if (arr1.Length == 2) { string partNumber1 = arr1[0]; string partName1 = arr1[1]; _formulaList1 = _formula.AsEnumerable() .Where(r => r.Field("part_number") == partNumber1 && r.Field("part_name") == partName1) .ToList(); } else { _formulaList1 = null; } } // 设备2选择配方 string selected2 = this.comboBox2.SelectedValue?.ToString(); if (string.IsNullOrEmpty(selected2) || selected2 == "-1") { _formulaList2 = null; } else { string[] arr2 = selected2.Split('|'); if (arr2.Length == 2) { string partNumber2 = arr2[0]; string partName2 = arr2[1]; _formulaList2 = _formula.AsEnumerable() .Where(r => r.Field("part_number") == partNumber2 && r.Field("part_name") == partName2) .ToList(); } else { _formulaList2 = null; } } } /// /// 刷新配方 /// /// /// private async void btnRefresh_Click(object sender, EventArgs e) { try { await LoadFormula(); await LoadMqttDic(); MessageBox.Show("配方已刷新!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { AppendLog($"刷新配方失败:{ex.Message}"); MessageBox.Show($"刷新配方失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 中止上传按钮 /// /// /// private void button3_Click(object sender, EventArgs e) { this.button2.Enabled = true; if (this._timer != null) { this._timer.Enabled = false; this._timer.Dispose(); this._timer = null; AppendLog("已停止自动上传数据"); MessageBox.Show("已停止自动上传", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { AppendLog("无运行中的上传定时器"); MessageBox.Show("当前无自动上传任务", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information); } } /// /// 报废上传凭证按钮 /// /// /// private async void button4_Click(object sender, EventArgs e) { try { OpenFileDialog openFile = new OpenFileDialog(); if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (filePath != null) { // 获取钉钉 token string token = _uploadService.GetDingDingToken(); if (string.IsNullOrEmpty(token)) { MessageBox.Show("获取 token 失败,请检查 AppKey/AppSecret"); return; } // 上传数据库数据到宜搭 await _uploadService.UploadScrapCertificate(token, filePath); MessageBox.Show("报废证据上传已上传!"); } else { MessageBox.Show("不允许上传空文件!"); } } } catch (Exception ex) { AppendLog($"报废证据上传失败:{ex.Message}"); } } private void button5_Click(object sender, EventArgs e) { throw new System.NotImplementedException(); } /// /// 刷新秒数 /// /// /// private void txtLUploadPL_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Back) { e.Handled = false; return; } if (char.IsDigit(e.KeyChar)) { e.Handled = false; } else if (e.KeyChar == '.' && !txtLUploadPL.Text.Contains(".")) { e.Handled = false; } else { e.Handled = true; } } /// /// 更新时间 /// /// /// private void timer1_Tick(object sender, EventArgs e) { // 每秒更新一次时间显示 this.label4.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); } // ======================================== 工具方法 ======================================== /// /// 获取Excel到Datatable中(.NET 4.8 兼容版) /// /// Excel路径 /// 返回DataTable数据 private static DataTable GetExcel(string filePath) { IWorkbook iwkX = null; FileStream fs = null; try { fs = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); iwkX = WorkbookFactory.Create(fs); //sheet DataTable dt = new DataTable(); for (int h = 0; h < iwkX.NumberOfSheets; h++) { ISheet sheet = iwkX.GetSheetAt(h); var rows = sheet.GetRowEnumerator(); bool isMove = rows.MoveNext(); //循环sheet if (isMove) { var Cols = (IRow)rows.Current; dt.TableName = sheet.SheetName; for (int i = 0; i < Cols.LastCellNum; i++) { string str = Cols.GetCell(i)?.ToString() ?? $"列{i + 1}"; dt.Columns.Add(str); } while (rows.MoveNext()) { var row = (IRow)rows.Current; var dr = dt.NewRow(); for (int i = 0; i < row.LastCellNum; i++) { var cell = row.GetCell(i); if (cell == null) { dr[i] = ""; } else { // 处理不同单元格类型 switch (cell.CellType) { case CellType.Numeric: if (DateUtil.IsCellDateFormatted(cell)) { dr[i] = cell.DateCellValue.ToString(); } else { dr[i] = cell.NumericCellValue.ToString(); } break; case CellType.String: dr[i] = cell.StringCellValue; break; case CellType.Boolean: dr[i] = cell.BooleanCellValue.ToString(); break; default: dr[i] = cell.ToString(); break; } } } dt.Rows.Add(dr); } } } return dt; } finally { // .NET 4.8 手动释放资源 if (fs != null) { fs.Close(); fs.Dispose(); } if (iwkX != null) { iwkX = null; } } } /// /// 上传宜搭方法 /// private async Task MqttYiDaUpload() { try { //获取最新的2台设备数据,各取最新1条。 DataTable dtMqtt = await _buttonService.GetLatestMqttDataAsync(); if (dtMqtt == null || dtMqtt.Rows.Count == 0)//有设备数据才会执行 { AppendLog("未查询到MQTT设备数据,跳过本次上传"); return; } AppendLog($"本次读取到{dtMqtt.Rows.Count}条MQTT设备数据"); foreach (DataRow dr in dtMqtt.Rows)//遍历dtMqtt中数据,一行代表一个设备的数据,一共2行 { string deviceCode = dr["device_code"].ToString(); AppendLog($"开始处理设备[{deviceCode}]数据"); List drPeiFangList = null; string site = null; if (deviceCode == "device1") { drPeiFangList = _formulaList1; site = "1号设备"; } else if (deviceCode == "device2") { drPeiFangList = _formulaList2; site = "2号设备"; } if (drPeiFangList == null || drPeiFangList.Count == 0) { AppendLog($"设备[{deviceCode}]未选择配方,跳过"); continue; } //取出一条字段 MESSAGE string strMessage = dr["receive_data"].ToString(); if (string.IsNullOrEmpty(strMessage)) { AppendLog($"设备[{deviceCode}]无有效receive_data,跳过"); continue; } //反序列化设备收到的数据,取出里面的参数(@params)。根据MqttDataModel 的定义。 SQLDataModel data = null; try { data = JsonConvert.DeserializeObject(strMessage); } catch (Exception ex) { AppendLog($"设备[{deviceCode}]数据反序列化失败:{ex.Message}"); continue; } //如果反序列化失败或 params 为空,就跳过。 if (data == null || data.@params == null) { AppendLog($"设备[{deviceCode}]反序列化后无params数据,跳过"); continue; } //获取 @params 的实际类型。 Type paramsType = data.@params.GetType(); //properties—>paramsType 的所有字段信息 PropertyInfo[] properties = paramsType.GetProperties(BindingFlags.Public | BindingFlags.Instance); List mqttLists = new List(); List yidaLists = new List(); //遍历参数构造数据项 foreach (PropertyInfo prop in properties) { //parameter_name,参数名,比如 "Value01" string paramName = prop.Name; //parameter_value,参数值,比如 24.6 object value = prop.GetValue(data.@params); // 跳过空值(null 或 "") if (value == null || string.IsNullOrWhiteSpace(value.ToString())) { continue; } //查映射表 //如果找到了映射,就替换 name 为中文显示名, //dtMqttDic在Form1_Load方法中已经被赋值,是字典表中的数据。 DataRow[] drs = _formulaDic.Select("param_code = '" + paramName + "'"); if (drs != null && drs.Length > 0) { paramName = drs[0]["param_name"].ToString(); } DataRow drMatch = drPeiFangList.FirstOrDefault(r => r.Field("parameter_name") == paramName); if (drMatch == null) { AppendLog($"设备[{deviceCode}]配方中未找到参数[{paramName}],跳过"); continue; // 没找到匹配行就跳过 } //构造 MqttModel(用于存数据库) MqttModel mqttNew = new MqttModel(); //把当前参数数据与选中的配方信息(drPeiFang)融合,形成一个完整记录。 mqttNew.SupplierCode = drMatch["supplier_code"].ToString();//供应商代码 mqttNew.SupplierName = drMatch["supplier_name"].ToString();//供应商名称 mqttNew.VehicleModel = drMatch["vehicle_model"].ToString();//车型 mqttNew.PartNumber = drMatch["part_number"].ToString();//零件号 mqttNew.PartName = drMatch["part_name"].ToString();//零件名 mqttNew.ParameterName = paramName;//parameter_name,参数名 mqttNew.ParameterValue = value == null ? "" : value.ToString();//parameter_value,参数值 mqttNew.ToleranceLower = drMatch["tolerance_lower"].ToString();//下公差 mqttNew.ToleranceUpper = drMatch["tolerance_upper"].ToString();//上公差 mqttNew.LeaderPart = drMatch["leader_part"].ToString();//零件负责人 mqttNew.WorkStation = site;//工位 mqttNew.LeaderOutProtection = drMatch["leader_out_protection"].ToString();//外保负责人 //构造 YiDaModel(用于宜搭上传) //把当前参数数据与选中的配方信息(drPeiFang)融合,形成一个完整记录。 YiDaModel yidaNew = new YiDaModel(); yidaNew.textField_mha98neu = drMatch["supplier_code"].ToString();//供应商代码 yidaNew.textField_mha98nev = drMatch["supplier_name"].ToString();//供应商名称 yidaNew.textField_mha98new = drMatch["vehicle_model"].ToString();//车型 yidaNew.textField_mha98nex = drMatch["part_number"].ToString();//零件号 yidaNew.textField_mha98ney = drMatch["part_name"].ToString();//零件名 yidaNew.textField_mha98nf1 = paramName;//parameter_name,参数名 yidaNew.textField_mhx44i2i = value == null ? "" : value.ToString();//parameter_value,参数值 yidaNew.textField_mhx44i2j = drMatch["tolerance_lower"].ToString();//下公差 yidaNew.textField_mhx44i2k = drMatch["tolerance_upper"].ToString();//上公差 yidaNew.textField_mha98nf7 = drMatch["leader_part"].ToString();//零件负责人 if (paramName == "报废图片路径" && _buttonService._formulaValue == "报废图片路径") { yidaNew.imageField_mii5s85z = _uploadService._reformationPicture; //整改图片 } yidaNew.textField_mha98nf0 = site; //工位 yidaNew.textField_mha98nfh = drMatch["leader_out_protection"].ToString(); //外保负责人 // 添加时间戳 yidaNew.textField_mhlvt8ht = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds); //新增合格判断逻辑代码 if (paramName == "报警信息") { if (string.Equals(value?.ToString(), "无错误", StringComparison.Ordinal)) { mqttNew.IsQualification = "合格"; yidaNew.textField_mha98nf5 = "合格"; } else { mqttNew.IsQualification = "不合格"; yidaNew.textField_mha98nf5 = "不合格"; } } else if (paramName == "开模总数实时" || paramName == "托模次数" || paramName == "报废图片路径") { mqttNew.IsQualification = "合格"; yidaNew.textField_mha98nf5 = "合格"; } else { // 用参数值(value)比较,而非参数名(name) // 安全转换参数值、下公差、上公差为float if (float.TryParse(value?.ToString(), out float paramValue) && float.TryParse(drMatch["tolerance_lower"].ToString(), out float toleranceLower) && float.TryParse(drMatch["tolerance_upper"].ToString(), out float toleranceUpper)) { // 数值在公差范围内则合格 if (paramValue >= toleranceLower && paramValue <= toleranceUpper) { mqttNew.IsQualification = "合格"; yidaNew.textField_mha98nf5 = "合格"; } else { mqttNew.IsQualification = "不合格"; yidaNew.textField_mha98nf5 = "不合格"; } } else { // 转换失败(如参数值非数值、公差为空),判定为不合格或跳过 mqttNew.IsQualification = "不合格"; yidaNew.textField_mha98nf5 = "不合格"; // 输出日志排查转换失败的字段 this.AppendLog($"数值转换失败:设备[{deviceCode}],参数名={paramName},参数值={value},下公差={drMatch["tolerance_lower"]},上公差={drMatch["tolerance_upper"]}"); } } mqttLists.Add(mqttNew); yidaLists.Add(yidaNew); } int len = Math.Min(mqttLists.Count, yidaLists.Count); if (len > 0) { List mqttLists1 = mqttLists.Take(len).ToList(); List yidaLists1 = yidaLists.Take(len).ToList(); string token = _uploadService.GetDingDingToken(); if (string.IsNullOrEmpty(token)) { AppendLog("获取 token 失败,请检查 AppKey/AppSecret"); // .NET 4.8 跨线程弹窗 this.Invoke(new Action(() => { MessageBox.Show("获取 token 失败,请检查 AppKey/AppSecret", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); })); return; } // 跨线程调用UI相关方法 this.Invoke(new Action(() => { _uploadService.UploadDatabaseDataToYiDaWithLogging(token, yidaLists1, mqttLists, this); })); AppendLog($"设备[{deviceCode}]成功上传{len}条数据到宜搭"); } else { AppendLog($"设备[{deviceCode}]无有效参数数据可上传"); } } } catch (Exception ex) { AppendLog($"MqttYiDaUpload执行失败:{ex.Message}"); // 跨线程显示错误提示(.NET 4.8 兼容写法) this.Invoke(new Action(() => { MessageBox.Show($"上传失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); })); } } private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { } // ======================================== 日志相关方法 ======================================== /// /// 接收 MQTT 消息后,将消息日志显示到 UI 控件上(.NET 4.8 兼容版) /// /// private void OnMqttMessage(string msg) { if (this.InvokeRequired) { this.Invoke(new Action(OnMqttMessage), msg); return; } AppendLog($"收到消息: {msg}"); } /// /// 写入日志到文本框并保存到文件(.NET 4.8 兼容版) /// public void AppendLog(string message) { try { if (this.InvokeRequired) { this.Invoke(new Action(AppendLog), message); return; } // 格式化带时间戳的日志 string timeStamped = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}\r\n"; // 保存到日志文件 SaveToLogFile(timeStamped); // 添加到文本框并限制行数 textBoxLog.AppendText(timeStamped); LimitLogLines(500); } catch (Exception ex) { // 静默处理日志写入错误,避免影响主流程 Console.WriteLine($"日志处理错误: {ex.Message}"); } } /// /// 将日志保存到文件(.NET 4.8 兼容版) /// private void SaveToLogFile(string logContent) { try { UpdateLogFile(); // 确保使用当天的日志文件 // 追加写入日志,使用UTF8编码避免中文乱码(.NET 4.8 显式指定编码) File.AppendAllText(_currentLogFile, logContent, System.Text.Encoding.UTF8); } catch (Exception ex) { // 文件写入错误处理 Console.WriteLine($"日志文件写入错误: {ex.Message}"); } } /// /// 限制日志文本框的最大行数(.NET 4.8 兼容版) /// private void LimitLogLines(int maxLines) { // 按换行符分割所有行 string[] lines = textBoxLog.Text.Split(new[] { "\r\n" }, StringSplitOptions.None); // 如果超过最大行数,只保留最后maxLines行 if (lines.Length > maxLines) { // 计算需要保留的起始索引 int startIndex = lines.Length - maxLines; string[] newLines = new string[maxLines]; Array.Copy(lines, startIndex, newLines, 0, maxLines); // 重新设置文本框内容并保持滚动到最底部 textBoxLog.Text = string.Join("\r\n", newLines); // 滚动到最新内容 textBoxLog.SelectionStart = textBoxLog.TextLength; textBoxLog.ScrollToCaret(); } else { // 未超过限制时也滚动到最底部 textBoxLog.SelectionStart = textBoxLog.TextLength; textBoxLog.ScrollToCaret(); } } private void button5_Click_1(object sender, EventArgs e) { throw new System.NotImplementedException(); } private void txtLUploadPL_TextChanged(object sender, EventArgs e) { throw new System.NotImplementedException(); } private void panel1_Paint(object sender, PaintEventArgs e) { throw new System.NotImplementedException(); } } }