Files
FATrace/FATrace.App/frmMain.cs
2026-03-05 10:20:43 +08:00

1471 lines
54 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using FATrace.App.Model;
using FATrace.Com;
using FATrace.Model;
using NLog;
using System.Data;
using System.Globalization;
using System.Net.NetworkInformation;
using System.Text;
namespace FATrace.App
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
//禁止编译器对跨线程访问做检查
Control.CheckForIllegalCrossThreadCalls = false;
// 切换到“历史数据”页时,确保下拉框数据已绑定
TabControlMain.SelectedIndexChanged += (s, e) =>
{
if (TabControlMain.SelectedTab == tabPage1)
EnsureSearchRawNameDataBound();
};
// 窗口首次显示后再绑定一次,避免某些情况下 Load 阶段控件状态未准备好
this.Shown += (s, e) =>
{
EnsureSearchRawNameDataBound();
};
}
//日志的实例化
private static Logger logger = LogManager.GetCurrentClassLogger();
// TouchSocket 版称重客户端
private TScalTcp? _scaleTcp;
/// <summary>
/// 关闭确认标记:用于避免重复弹窗(如关闭过程触发多次 FormClosing
/// </summary>
private bool _closeConfirmed;
// 打印机连接参数(用于状态检测)
private string _printerIp = "192.0.1.21";
private int _printerPort = 9100;
// 状态缓存,避免频繁重复写日志
private bool? _lastScaleOk = null;
private bool? _lastServerOk = null;
private bool? _lastPrinterOk = null;
// 统一更新状态栏文本与颜色(线程安全)
private void UpdateStatusLabel(ToolStripStatusLabel label, string text, Color backColor, Color foreColor)
{
void Apply()
{
label.Text = text;
label.BackColor = backColor;
label.ForeColor = foreColor;
}
if (InvokeRequired) BeginInvoke((Action)Apply); else Apply();
}
private void SetScaleStatusOk(string text)
{
UpdateStatusLabel(tslWeightState, text, Color.Green, Color.White);
_lastScaleOk = true;
}
private void SetScaleStatusFail(string text)
{
UpdateStatusLabel(tslWeightState, text, Color.Tomato, Color.White);
_lastScaleOk = false;
}
private void SetServerStatusOk(string text)
{
UpdateStatusLabel(tslServerState, text, Color.Green, Color.White);
_lastServerOk = true;
}
private void SetServerStatusFail(string text)
{
UpdateStatusLabel(tslServerState, text, Color.Tomato, Color.White);
_lastServerOk = false;
}
private void SetPrinterStatusOk(string text)
{
UpdateStatusLabel(tslPrintState, text, Color.Green, Color.White);
_lastPrinterOk = true;
}
private void SetPrinterStatusFail(string text)
{
UpdateStatusLabel(tslPrintState, text, Color.Tomato, Color.White);
_lastPrinterOk = false;
}
// 使用 ICMP Ping 检测主机可达,避免占用打印机 9100 端口而影响正常打印
private async Task<bool> PingAsync(string ip, int timeoutMs)
{
try
{
using var ping = new Ping();
var reply = await ping.SendPingAsync(ip, timeoutMs);
return reply.Status == IPStatus.Success;
}
catch
{
return false;
}
}
private async Task CheckDbStatusAsync()
{
try
{
var ok = await Task.Run(() =>
{
try
{
// 简单心跳
var obj = FSqlContext.FDb.Ado.ExecuteScalar("SELECT 1");
if (obj == null) return false;
// 兼容不同数据库返回类型(可能是 long/decimal/string
var v = Convert.ToInt32(obj);
return v == 1;
}
catch
{
return false;
}
});
if (_lastServerOk != ok)
{
if (ok)
{
SetServerStatusOk("服务器正常");
logger.Info("数据库服务器心跳正常");
}
else
{
SetServerStatusFail("服务器异常");
logger.Error("数据库服务器心跳失败");
}
}
else
{
// 状态未变化,但仍在首次加载时给出视觉反馈
if (ok && _lastServerOk == null) SetServerStatusOk("服务器正常");
}
}
catch (Exception ex)
{
SetServerStatusFail($"服务器异常:{ex.Message}");
logger.Error(ex, "数据库服务器状态检测异常");
}
}
private async Task CheckPrinterStatusAsync()
{
try
{
// 使用 Ping 检测打印机在线状态,不占用打印端口
bool ok = await PingAsync(_printerIp, 2000);
if (_lastPrinterOk != ok)
{
if (ok)
{
SetPrinterStatusOk("打印机在线");
logger.Info($"打印机({_printerIp}:{_printerPort})在线");
}
else
{
SetPrinterStatusFail("打印机离线");
logger.Error($"打印机({_printerIp}:{_printerPort})离线");
}
}
else
{
if (ok && _lastPrinterOk == null) SetPrinterStatusOk("打印机在线");
}
}
catch (Exception ex)
{
SetPrinterStatusFail($"打印机异常:{ex.Message}");
logger.Error(ex, "打印机状态检测异常");
}
}
/// <summary>
/// 当前确认者用户名
/// </summary>
public string CurrentCheckUserNo { get; set; } = string.Empty;
/// <summary>
/// 当前操作者用户名
/// </summary>
public string CurrentOpUserNo { get; set; } = string.Empty;
public string CurrentOperationNoUserLevel { get; set; } = string.Empty;
/// <summary>
/// 称重上限
/// </summary>
private double WeightUp { get; set; }
/// <summary>
/// 称重下限
/// </summary>
private double WeightDown { get; set; }
/// <summary>
/// 计算扫描 Task
/// </summary>
private static Task CycleTask { get; set; }
/// <summary>
/// 日信息
/// 防止跨日的生产的产量的清零
/// </summary>
private DaySgl CurDaySgl { get; set; }
/// <summary>
/// 线程的启用
/// </summary>
private bool ThreadEnable { get; set; } = true;
private async void frmMain_Load(object sender, EventArgs e) // 将加载事件改为 async 以便等待连接
{
this.TabControlMain.SelectedIndex = 1;
// 初始化打印机对象
CurZebraPrint = new ZebraPrint(_printerIp, _printerPort);
// 下面初始化称重服务(请按需修改 IP 与端口为仪表的实际地址)
var scaleIp = "192.168.0.80"; // 仪表的 TCP Server IP示例值实际请替换
var scalePort = 10251; // 仪表的 TCP Server 端口(示例值,实际请替换)
if (double.TryParse(ConfigHelper.GetStringOrDefault("WeightUp", "10"), out var weightUp))
{
WeightUp = weightUp;
}
if (double.TryParse(ConfigHelper.GetStringOrDefault("WeightDown", "200"), out var weightDown))
{
WeightDown = weightDown;
}
txtWeightUp.Text = weightUp.ToString();
txtWeightDown.Text = weightDown.ToString();
//日产量初始获取信息
CurDayCount = GetDayCount();
ListRawCtrInfo = new List<RawCtrInfo>()
{
new RawCtrInfo(){
RawName="瑞士乳杆菌GCL1815",
RawCode="1121000265",
BtnControlName="btnRawName1",
RawSource=RawSource.Japan
},
new RawCtrInfo(){
RawName="抗性糊精",
RawCode="YG03031004",
BtnControlName="btnRawName3",
RawSource=RawSource.China
},
new RawCtrInfo(){
RawName="白砂糖",
RawCode="YG03010001",
BtnControlName="btnRawName2",
RawSource=RawSource.China
},
};
// 初始化历史查询下拉框数据
EnsureSearchRawNameDataBound();
CurDaySgl = new DaySgl();
CurDaySgl.DaySglEvent += DaySgl_DaySglEvent;
CycleScan();
try // 捕获连接过程中的异常,便于界面提示
{
// 使用 TouchSocket 版本的称重客户端
_scaleTcp = new TScalTcp(scaleIp, scalePort);
_scaleTcp.StableWeightReceived += ScaleTcp_StableWeightReceived; // 稳定值事件
_scaleTcp.CommunicationError += ScaleTcp_CommunicationError; // 通信异常事件
await _scaleTcp.StartAsync(); // 启动并连接
SetScaleStatusOk($"称重已连接 {scaleIp}:{scalePort}");
logger.Info($"称重连接成功:{scaleIp}:{scalePort}");
// 如需切回 SuperSocket 方案,请注释上方并取消下方注释
// _weightService = new TScalWeightServices(scaleIp, scalePort);
// _weightService.StableWeightReceived += WeightService_StableWeightReceived;
// _weightService.CommunicationError += WeightService_CommunicationError;
// await _weightService.StartAsync();
}
catch (Exception ex) // 连接失败时弹出提示
{
SetScaleStatusFail($"称重连接失败:{ex.Message}");
logger.Error(ex, "称重连接失败");
MessageBox.Show($"称重连接失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); // 显示错误
}
// 启动后台状态检测DB与打印机
_ = Task.Run(async () =>
{
await CheckDbStatusAsync();
await CheckPrinterStatusAsync();
});
}
/// <summary>
/// 跨日的生产的产量的清零
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DaySgl_DaySglEvent(object? sender, string e)
{
CurDayCount = GetDayCount();
}
/// <summary>
/// 周期扫描
/// </summary>
private void CycleScan()
{
CycleTask = Task.Run(async () =>
{
int loop = 0;
while (ThreadEnable)
{
await Task.Delay(3000);
// 称重连接状态轮询(防止某些厂商设备断线未触发回调)
bool scaleOk = _scaleTcp?.IsConnected ?? false;
if (_lastScaleOk != scaleOk)
{
if (scaleOk)
{
SetScaleStatusOk("称重已连接");
logger.Info("称重连接恢复");
}
else
{
SetScaleStatusFail("称重未连接");
logger.Error("称重连接断开");
}
}
CurDaySgl.CurDay = DateTime.Now.ToString("yyyy-MM-dd");
// 每30秒检测一次服务器与打印机
loop = (loop + 1) % 1000000;
if (loop % 10 == 0)
await CheckDbStatusAsync();
if (loop % 10 == 5)
await CheckPrinterStatusAsync();
}
});
}
private void btnTest_Click(object sender, EventArgs e)
{
//new ZebraPrint("192.168.0.40", 9100).PrintWeight("添加剂21", 80.8, "20251021", 3, "02", 2583);
}
///// <summary>
///// 当前选择的原料名称
///// </summary>
//private string CurSelectedRawProInput.RawName { get; set; }
/// <summary>
/// 当前重量数据
/// </summary>
private double CurWeight { get; set; } = 0;
///// <summary>
///// 当前剩余数据
///// </summary>
//private double CurRemainWeight { get; set; }
/// <summary>
/// 当前选中的生产原料数据
/// </summary>
private RawProInput CurSelectedRawProInput { get; set; }
/// <summary>
/// 当前打印的驱动数据
/// </summary>
private ZebraPrint CurZebraPrint { get; set; }
/// <summary>
/// 当天的产量信息
/// </summary>
private DayCount CurDayCount { get; set; }
/// <summary>
/// 原料控制集合
/// </summary>
private List<RawCtrInfo> ListRawCtrInfo { get; set; }
/// <summary>
/// 选中的原料控件信息
/// </summary>
private RawCtrInfo SelectedRawCtrInfo { get; set; }
#region
/// <summary>
/// 高亮当前选中的原料按钮,并还原其他按钮样式
/// </summary>
private void HighlightRawButton(Button active)
{
var all = new[] { btnRawName1, btnRawName2, btnRawName3 };
foreach (var b in all)
{
if (b == null) continue;
if (ReferenceEquals(b, active))
{
b.UseVisualStyleBackColor = false;
b.BackColor = Color.DeepSkyBlue;
b.ForeColor = Color.White;
}
else
{
b.UseVisualStyleBackColor = true;
b.BackColor = SystemColors.Control;
b.ForeColor = SystemColors.ControlText;
}
}
}
/// <summary>
/// 统一处理原料按钮点击的选择与高亮
/// </summary>
private void OnRawNameButtonClicked(Button btn)
{
//CurSelectedRawName = btn.Text?.Trim();
HighlightRawButton(btn);
foreach (var RawCtrInfo in ListRawCtrInfo)
{
if (RawCtrInfo.BtnControlName == btn.Name)
{
SelectedRawCtrInfo = RawCtrInfo;
break;
}
}
var RawInfoData = CheckRawInfo(SelectedRawCtrInfo.RawName!);
if (RawInfoData.result)
{
//只取最新的第一个原料信息
CurSelectedRawProInput = RawInfoData.data!;
txtWeight.Text = CurSelectedRawProInput.Weight.ToString();
txtBatch.Text = CurSelectedRawProInput.Batch!;
txtShelfLife.Text = CurSelectedRawProInput.ShelfLife.ToString();
txtRemainWeight.Text = CurSelectedRawProInput.RemainWeight.ToString();
Task.Run(() =>
{
lblRawBeforeInfo.BeginInvoke(new Action(() =>
{
lblRawBeforeInfo.Visible = true;
}));
Thread.Sleep(3000);
lblRawBeforeInfo.BeginInvoke(new Action(() =>
{
lblRawBeforeInfo.Visible = false;
}));
});
}
else
{
//可能第一次使用的时候,需要初始化数据,把控件的信息给选中的模型
CurSelectedRawProInput = new RawProInput();
CurSelectedRawProInput.RawSource = SelectedRawCtrInfo.RawSource;
CurSelectedRawProInput.RawName = SelectedRawCtrInfo.RawName!;
CurSelectedRawProInput.RawCode = SelectedRawCtrInfo.RawCode!;
//清空分拆的数据
ClearInput();
}
}
/// <summary>
/// 检查原料信息
/// </summary>
/// <returns></returns>
private (bool result, RawProInput data) CheckRawInfo(string RawName)
{
//理论上如果存在的话,只有一个信息,因为一个原料没有用完的情况下,是不可以用下一个的产品的,否则提示
var ListRawProInput = FSqlContext.FDb.Select<RawProInput>().Where(a => a.RawName == RawName && a.RawState == RawSplitState.Splitting).OrderByDescending(a => a.CreateTime).ToList();
if (ListRawProInput.Count() > 0)
{
return (true, ListRawProInput.FirstOrDefault()!);
}
else
{
//不存在的话,说明是新的原料,可以直接使用
return (false, new RawProInput());
}
}
private void btnRawName1_Click(object sender, EventArgs e)
{
if (sender is Button btn)
{
OnRawNameButtonClicked(btn);
}
}
private void btnRawName3_Click(object sender, EventArgs e)
{
if (sender is Button btn)
{
OnRawNameButtonClicked(btn);
}
}
private void btnRawName2_Click(object sender, EventArgs e)
{
if (sender is Button btn)
{
OnRawNameButtonClicked(btn);
}
}
private void btnRawNameCommon_Click(object? sender, EventArgs e)
{
if (sender is Button btn)
{
OnRawNameButtonClicked(btn);
}
}
#endregion
/// <summary>
/// 确认输入数据
/// 大包装的产线数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnTrueProInput_Click(object sender, EventArgs e)
{
try
{
// 1) 校验已选择的原料名称
if (CurSelectedRawProInput == null || string.IsNullOrWhiteSpace(CurSelectedRawProInput.RawName))
{
MessageBox.Show("请先在上方选择原料名称。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 2) 读取并校验输入框
var weightText = (txtWeight.Text ?? string.Empty).Trim();
var batchText = (txtBatch.Text ?? string.Empty).Trim();
var shelfLifeText = (txtShelfLife.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(weightText) ||
!double.TryParse(weightText.Replace('', '.').Replace('。', '.'), out var weight) || weight <= 0)
{
MessageBox.Show("请输入有效的入库重量Kg必须为正数。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtWeight.Focus();
txtWeight.SelectAll();
return;
}
if (string.IsNullOrWhiteSpace(batchText))
{
MessageBox.Show("请输入批号。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtBatch.Focus();
return;
}
if (batchText.Length > 20)
{
MessageBox.Show("批号长度不能超过20个字符。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtBatch.Focus();
return;
}
if (string.IsNullOrWhiteSpace(shelfLifeText) ||
!int.TryParse(shelfLifeText.Replace('', '.').Replace('。', '.'), out var shelfLife) || shelfLife <= 0)
{
MessageBox.Show("请输入有效的保质期(月),必须为正数。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtShelfLife.Focus();
txtShelfLife.SelectAll();
return;
}
// 3) 组装实体
var entity = new RawProInput
{
RawName = CurSelectedRawProInput!.RawName,
RawCode = CurSelectedRawProInput.RawCode,
Weight = Math.Round(weight, 2), // 保留3位小数避免浮点抖动
Batch = batchText,
ShelfLife = shelfLife,
RawSource = CurSelectedRawProInput.RawSource,
RemainWeight = Math.Round(weight, 2),
RawState = RawSplitState.Splitting
};
// 4) 写入数据库FreeSql
var affRowsData = FSqlContext.FDb.Insert(entity).ExecuteInserted();
if (affRowsData.Count <= 0)
{
MessageBox.Show("保存失败:未能写入数据库。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
else
{
//插入成功后,更新当前选中的产品
CurSelectedRawProInput = affRowsData.FirstOrDefault()!;
// 5) 成功提示并清空输入
MessageBox.Show("保存成功。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
MessageBox.Show($"保存失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 清零当前的原料
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnClearRaw_Click(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show("您确定清零当前的原料?", "Confirmation",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.No)
{
return;
}
if (CurSelectedRawProInput == null)
{
MessageBox.Show("请选中原料后再操作。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
var UpdatedData = FSqlContext.FDb.Update<RawProInput>()
.Set(a => a.RawState, RawSplitState.SplitComplete)
.Where(a => a.Id == CurSelectedRawProInput.Id)
.ExecuteUpdated();
if (UpdatedData.Count() > 0)
{
ClearInput();
}
}
/// <summary>
/// 称重打印数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnWeightPrint_Click(object sender, EventArgs e)
{
if (CurSelectedRawProInput == null)
{
MessageBox.Show("请先选择要称重的产品", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (string.IsNullOrEmpty(CurSelectedRawProInput.RawName) || string.IsNullOrEmpty(CurSelectedRawProInput.Batch) || string.IsNullOrEmpty(CurSelectedRawProInput.RawCode))
{
MessageBox.Show("选择的产品信息无数据,请信息输入后再操作", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (WeightDown > 0 && WeightUp > 0)
{
if (CurWeight < WeightDown || CurWeight > WeightUp)
{
DialogResult resultRangeCheck = frmMessage.ShowConfirm($"检测到当前重量 {CurWeight:0.###}g 不在配置范围 [{WeightDown:0.###}g, {WeightUp:0.###}g] 内,确定要【打印】操作吗?", "确认操作", this);
if (resultRangeCheck == DialogResult.Cancel)
{
return;
}
else
{
logger.Info("你打印了一个不在配置范围内的重量!!!");
}
}
}
//确认数据
// 显示消息框,并等待用户响应
DialogResult result = frmMessage.ShowConfirm("确定要【打印】操作吗?", "确认操作", this);
if (result == DialogResult.Cancel)
{
return;
}
//if (CurWeight < 2.0)
//{
// //确认数据
// // 显示消息框,并等待用户响应
// DialogResult resultWeightCheck = frmMessage.ShowConfirm("检测到当前的重量小于2g确定要【打印】操作吗", "确认操作", this);
// if (resultWeightCheck == DialogResult.Cancel)
// {
// return;
// }
//}
//新的剩余重量 Kg
var NewRemainWeight = CurSelectedRawProInput.RemainWeight - CurWeight / 1000.0;
//当前产品的剩余重量
txtRemainWeight.Text = NewRemainWeight.ToString();
//二维码
var Code = GenCode(CurSelectedRawProInput.RawCode!, CurWeight, CurSelectedRawProInput.Batch!, CurSelectedRawProInput.ShelfLife, CurSelectedRawProInput.RawSource, CurDayCount.Count);
// 执行打印
try
{
UpdateStatusLabel(tslPrintState, "正在打印...", Color.Goldenrod, Color.White);
CurZebraPrint.PrintWeight(Code, CurSelectedRawProInput.RawName!,
CurWeight,
CurSelectedRawProInput.Batch!,
CurSelectedRawProInput.ShelfLife
);
SetPrinterStatusOk("打印成功");
logger.Info($"打印成功:{CurSelectedRawProInput.RawName} {CurWeight}g 批号{CurSelectedRawProInput.Batch}");
}
catch (Exception ex)
{
SetPrinterStatusFail($"打印失败:{ex.Message}");
logger.Error(ex, "打印失败");
MessageBox.Show($"打印失败:{ex.Message}", "打印机错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//内包条码更新
txtCode.Text = Code;
// 写入称重使用记录
var Result = FSqlContext.FDb.Insert<RawProUse>(new RawProUse()
{
Batch = CurSelectedRawProInput.Batch,
InBagCode = Code,
BoxCode = Code + ",A",
OpUser = CurrentOpUserNo,
CheckUser = CurrentCheckUserNo,
RawCode = CurSelectedRawProInput.RawCode,
ShelfLife = CurSelectedRawProInput.ShelfLife,
RawName = CurSelectedRawProInput.RawName,
RemainWeight = NewRemainWeight,
Weight = CurWeight,
DeliveryDate = DateTime.Now.ToString("yyyy-MM-dd"),
WeightTime = DateTime.Now,
StockWeight = CurSelectedRawProInput.Weight,
}).ExecuteAffrows();
if (Result > 0)
{
//结束后这个重量
CurSelectedRawProInput.RemainWeight = NewRemainWeight;
txtRemainWeight.Text = CurSelectedRawProInput.RemainWeight.ToString();
//称重检查如果剩余重量为0进行提示当剩余的重量小于称重的重量进行提示
if (CurSelectedRawProInput.RemainWeight < CurWeight / 1000.0)
{
//剩余的重量小于称重的重量,进行提示,强制进行清零
Task.Run(() =>
{
lblRawUseStateTip.BeginInvoke(new Action(() =>
{
lblRawUseStateTip.Visible = true;
}));
Thread.Sleep(3000);
lblRawUseStateTip.BeginInvoke(new Action(() =>
{
lblRawUseStateTip.Visible = false;
}));
});
//更新数据,完成
FSqlContext.FDb.Update<RawProInput>()
.Set(a => a.RemainWeight, NewRemainWeight)
.Set(a => a.RawState, RawSplitState.SplitComplete)
.Where(a => a.Id == CurSelectedRawProInput.Id)
.ExecuteAffrows();
//当前的产品信息清零
CurSelectedRawProInput = null;
//清空分拆的数据
ClearInput();
//恢复btnRawName12,3的默认样式
SetDefaultStyle(panel1);
}
else
{
//当前分拆的产品的剩余重量更新数据
FSqlContext.FDb.Update<RawProInput>()
.Set(a => a.RemainWeight, NewRemainWeight)
.Where(a => a.Id == CurSelectedRawProInput.Id)
.ExecuteAffrows();
}
// 产量 +1
CurDayCount.Count = CurDayCount.Count + 1;
UpdateDayCount(CurDayCount);
}
}
/// <summary>
/// 格式化重量显示自动适配整数部分位数2-4位小数部分固定2位
/// </summary>
/// <param name="weight">原始重量值</param>
/// /// <returns>格式化后的重量字符串09.50, 81.10, 100.00, 1000.00</returns>
private string FormatWeight(double weight)
{
// 根据重量值大小决定格式
if (weight >= 1000)
return weight.ToString("0000.00"); // 四位整数1000.00-9999.99
else if (weight >= 100)
return weight.ToString("000.00"); // 三位整数100.00-999.99
else
return weight.ToString("00.00"); // 两位整数00.00-99.99
}
/// <summary>
/// 获取当前的日产量信息
/// </summary>
/// <returns></returns>
private DayCount GetDayCount()
{
var CurDayCountInfo = FSqlContext.FDb.Select<DayCount>().Where(a => a.DayInfo == DateTime.Now.ToString("yyyy-MM-dd")).First();
if (CurDayCountInfo == null)
{
logger.Info($"日产量信息不存在,新建日产量信息");
//当日的日产量信息不存在,第一次的话就新建信息
var ReturnData = FSqlContext.FDb.Insert<DayCount>(new DayCount()
{
Count = 1,
DayInfo = DateTime.Now.ToString("yyyy-MM-dd"),
}).ExecuteInserted();
return ReturnData.FirstOrDefault()!;
}
else
{
//否则的话,获取当前的日产量信息
return CurDayCountInfo;
}
}
/// <summary>
/// 更新日产量信息
/// </summary>
private bool UpdateDayCount(DayCount dayCount)
{
var Data = FSqlContext.FDb.Update<DayCount>()
.Set(a => a.Count, dayCount.Count)
.Set(a => a.UpdateTime, DateTime.Now)
.Where(a => a.Id == dayCount.Id)
.ExecuteAffrows();
if (Data > 0)
{
return true;
}
return false;
}
/// <summary>
/// 清空输入
/// </summary>
private void ClearInput()
{
txtWeight.Text = "";
txtBatch.Text = "";
txtShelfLife.Text = "";
txtRemainWeight.Text = "";
}
/// <summary>
/// 恢复panel1内按钮默认样式
/// </summary>
/// <param name="btn"></param>
private void SetDefaultStyle(Panel panel)
{
foreach (Control control in panel.Controls)
{
if (control is Button btn)
{
btn.BackColor = Color.Transparent;
btn.ForeColor = Color.Black;
}
}
}
/// <summary>
/// 生成条码信息
/// </summary>
/// <returns></returns>
public string GenCode(string RawCode, double Weight, string Batch, int ShelfLife, RawSource Source, int DayCount)
{
//二维码数据
var Code = new StringBuilder();
Code.Append(RawCode);
Code.Append(',');
Code.Append(Batch);
Code.Append(',');
Code.Append(FormatWeight(Weight).ToString().Replace(".", ""));
Code.Append(',');
Code.Append(ShelfLife.ToString());
Code.Append(',');
Code.Append(GetSourceInf(Source));
Code.Append(',');
Code.Append(DayCount.ToString());
return Code.ToString();
}
/// <summary>
/// 获取枚举的信息
/// </summary>
/// <returns></returns>
public string GetSourceInf(RawSource rawSource)
{
switch (rawSource)
{
case RawSource.China:
return "01";
case RawSource.Japan:
return "02";
default:
return "01";
}
}
// TouchSocket 版本稳定数据事件处理
private void ScaleTcp_StableWeightReceived(object? sender, TScalTcp.StableWeightEventArgs e)
{
void UpdateUi()
{
CurWeight = (double)e.Value;
txtRtWeight.Text = e.Value.ToString("0.###");
// 数据收发正常
if (_lastScaleOk != true)
{
SetScaleStatusOk("称重通讯正常");
}
}
if (InvokeRequired)
BeginInvoke((Action)UpdateUi);
else
UpdateUi();
}
// TouchSocket 版本通信错误处理
private void ScaleTcp_CommunicationError(object? sender, string message)
{
void ShowError()
{
MessageBox.Show(message, "称重通信错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
// 更新状态与日志
SetScaleStatusFail(message);
logger.Error($"称重通信错误:{message}");
if (InvokeRequired)
BeginInvoke((Action)ShowError);
else
ShowError();
}
/// <param name="e"></param>
private void btnLogin_Click(object sender, EventArgs e)
{
try
{
if (string.IsNullOrEmpty(txtCheckUserName.Text))
{
MessageBox.Show("请输入确认者用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(txtOpName.Text))
{
MessageBox.Show("请输入操作者用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(txtPassword.Text))
{
MessageBox.Show("请输入密码", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var ListUser = FSqlContext.FDb.Select<TbWeightUser>()
.Where(a => a.CheckName == txtCheckUserName.Text.Trim() && a.OpName == txtOpName.Text.Trim())
.ToList();
if (ListUser != null && ListUser.Count() > 0)
{
if (ListUser.FirstOrDefault().Password == txtPassword.Text.Trim())
{
MessageBox.Show("登录成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
Main_PopUserNameEvent(txtCheckUserName.Text.Trim(), txtOpName.Text.Trim(), "称重用户");
this.TabControlMain.SelectedIndex = 0;
txtCheckUserName.Text = "";
txtOpName.Text = "";
txtPassword.Text = "";
//PopUserNameEvent(txtUserName.Text.Trim());
//this.Close();
}
else
{
MessageBox.Show("密码错误!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
else
{
MessageBox.Show("当前用户不存在!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
}
}
/// <summary>
/// 用户登录的事件发布方法
/// </summary>
/// <param name="UserName"></param>
private void Main_PopUserNameEvent(string CheckUserName, string OpUserName, string UserLevel)
{
try
{
CurrentCheckUserNo = CheckUserName;
CurrentOpUserNo = OpUserName;
CurrentOperationNoUserLevel = UserLevel;
Invoke(new Action(() =>
{
tslCurrentUser.Text = "确认者用户:" + CurrentCheckUserNo;
tslCurrentUser.BackColor = Color.Green;
tslCurrentUser.ForeColor = Color.White;
}));
}
catch (Exception ex)
{
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
}
}
#region
private void bntFrmLogin_Click(object sender, EventArgs e)
{
TabControlMain.SelectedIndex = 1;
}
private void btnSysConfig_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(CurrentCheckUserNo))
{
}
}
private void btnExit_Click(object sender, EventArgs e)
{
try
{
if (DialogResult.OK == MessageBox.Show("你确定要关闭程序系统吗?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning))
{
ThreadEnable = false;
// 断开连接
//_MelsecMcNet?.ConnectClose();
// 断开连接
this.Close();
System.Environment.Exit(System.Environment.ExitCode);
//Application.ExitThread();
//Application.Exit();
}
else
{
}
}
catch (Exception ex)
{
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
//ScanSerialPort.Close();
//HandSerialPort.Close();
//_MelsecMcNet?.ConnectClose();
//insertLogToDBDelegate.BeginInvoke(1, "UpdateUIMethod异常", ex.Message.ToString() + ex.StackTrace.Substring(ex.StackTrace.Length - 40, 40), null, null);
}
}
private void btnMain_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(CurrentCheckUserNo))
{
TabControlMain.SelectedIndex = 0;
}
else
{
MessageBox.Show("请登录后再操作!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void btnHistoryData_Click(object sender, EventArgs e)
{
try
{
TabControlMain.SelectedIndex = 2; // 切换到历史数据页
EnsureSearchRawNameDataBound(); // 确保下拉框有数据
}
catch { }
}
#endregion
#region
private void btnSearchHistoryData_Click(object sender, EventArgs e)
{
try
{
// 初始化下拉框数据源(仅在首次/为空时绑定)
EnsureSearchRawNameDataBound();
// 时间范围:按日期选择,自动扩大为整日区间
var startDate = dtpSearchStartTime.Value.Date; // 00:00:00
var endDate = dtpSearchEndTime.Value.Date.AddDays(1).AddTicks(-1); // 23:59:59.9999999
if (startDate > endDate)
{
MessageBox.Show("开始时间不能晚于结束时间。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 原料名称/编码关键字:允许为空(为空则不过滤)。
var rawKeyword = (cbxSearchRawName.Text ?? string.Empty).Trim();
// 批号:精确匹配
var batchKeyword = (txtSearchBatch.Text ?? string.Empty).Trim();
// 构建 FreeSql 查询
var select = FSqlContext.FDb.Select<RawProUse>()
.Where(a => a.WeightTime >= startDate && a.WeightTime <= endDate);
if (!string.IsNullOrWhiteSpace(rawKeyword))
// 名称与编码均支持模糊匹配
select = select.Where(a => (a.RawName != null && a.RawName.Contains(rawKeyword)) || (a.RawCode != null && a.RawCode.Contains(rawKeyword)));
if (!string.IsNullOrWhiteSpace(batchKeyword))
select = select.Where(a => a.Batch.Contains(batchKeyword));
var result = select.OrderByDescending(a => a.WeightTime).ToList();
// 绑定到 DataGridView
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = result;
ApplyChineseColumnHeaders();
}
catch (Exception ex)
{
MessageBox.Show($"查询失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 确保搜索条件中的“原料名称”下拉框已从 ListRawCtrInfo 绑定数据。
/// </summary>
private void EnsureSearchRawNameDataBound()
{
// 优先使用界面配置的原料集合;若为空则回退到数据库历史记录中的原料名称
string[] names = Array.Empty<string>();
if (ListRawCtrInfo != null && ListRawCtrInfo.Count > 0)
{
names = ListRawCtrInfo
.Where(x => !string.IsNullOrWhiteSpace(x.RawName))
.Select(x => x.RawName!.Trim())
.Distinct()
.ToArray();
}
else
{
try
{
names = FSqlContext.FDb.Select<RawProUse>()
.Where(a => a.RawName != null && a.RawName != "")
.OrderBy(a => a.RawName)
.ToList(a => a.RawName!)
.Where(s => !string.IsNullOrWhiteSpace(s))
.Distinct()
.ToArray();
}
catch { /* 忽略绑定期的数据库异常,保持界面可用 */ }
}
cbxSearchRawName.BeginUpdate();
try
{
cbxSearchRawName.DataSource = null;
cbxSearchRawName.Items.Clear();
if (names.Length > 0)
cbxSearchRawName.Items.AddRange(names);
// 允许直接输入(支持编码模糊输入),并打开联想补全
cbxSearchRawName.DropDownStyle = ComboBoxStyle.DropDown;
cbxSearchRawName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
// 使用自定义自动完成源,加入“名称”和“编码”,便于输入编码模糊匹配
var ac = new AutoCompleteStringCollection();
try
{
if (ListRawCtrInfo != null)
{
var codes = ListRawCtrInfo
.Where(x => !string.IsNullOrWhiteSpace(x.RawCode))
.Select(x => x.RawCode!.Trim());
var texts = names.Concat(codes).Distinct().ToArray();
ac.AddRange(texts);
}
else
{
ac.AddRange(names);
}
}
catch { ac.AddRange(names); }
cbxSearchRawName.AutoCompleteCustomSource = ac;
cbxSearchRawName.AutoCompleteSource = AutoCompleteSource.CustomSource;
cbxSearchRawName.SelectedIndex = -1; // 默认不选中
}
finally
{
cbxSearchRawName.EndUpdate();
}
}
/// <summary>
/// 将 DataGridView 列头设置为中文显示。
/// </summary>
private void ApplyChineseColumnHeaders()
{
// 隐藏行头(左侧灰条)
dataGridView1.RowHeadersVisible = false;
void SetHeader(string name, string text)
{
var col = dataGridView1.Columns[name];
if (col != null) col.HeaderText = text;
}
// 移除编号列
var idCol = dataGridView1.Columns["Id"]; if (idCol != null) dataGridView1.Columns.Remove(idCol);
SetHeader("RawCode", "原料编号");
SetHeader("RawName", "原料名称");
SetHeader("InBagCode", "内袋二维码");
SetHeader("BoxCode", "外箱二维码");
SetHeader("Batch", "原料批号");
SetHeader("ShelfLife", "保质期");
SetHeader("Weight", "称重重量(g)");
SetHeader("RemainWeight", "剩余重量(Kg)");
SetHeader("StockWeight", "入库总重量(Kg)");
SetHeader("WeightTime", "称重时间");
SetHeader("OpUser", "操作员");
SetHeader("CheckUser", "确认者");
SetHeader("OutTime", "出库时间");
SetHeader("CreateTime", "创建时间");
}
#endregion
private void frmMain_FormClosed(object sender, FormClosedEventArgs e)
{
//CurZebraPrint.Close();
if (_scaleTcp != null)
{
_scaleTcp!.StopAsync();
}
}
/// <summary>
/// 重新打印之前最新的一个
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnReprint_Click(object sender, EventArgs e)
{
try
{
if (CurSelectedRawProInput == null)
{
MessageBox.Show("请先选择要称重的产品", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var LastData = FSqlContext.FDb.Select<RawProUse>().OrderByDescending(a => a.WeightTime).First();
if (LastData == null)
{
MessageBox.Show("没有找到最新的消息", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 显示消息框,并等待用户响应
DialogResult result = frmMessage.ShowConfirm($"确定要【重复打印】{LastData.InBagCode} 条码数据吗?", "确认操作", this);
if (result == DialogResult.Cancel)
{
return;
}
// 执行打印
try
{
UpdateStatusLabel(tslPrintState, "正在打印...", Color.Goldenrod, Color.White);
CurZebraPrint.PrintWeight(LastData.InBagCode!, CurSelectedRawProInput.RawName!,
CurWeight,
CurSelectedRawProInput.Batch!,
CurSelectedRawProInput.ShelfLife
);
SetPrinterStatusOk("打印成功");
logger.Info($"打印成功:{CurSelectedRawProInput.RawName} {CurWeight}g 批号{CurSelectedRawProInput.Batch}");
}
catch (Exception ex)
{
SetPrinterStatusFail($"打印失败:{ex.Message}");
logger.Error(ex, "打印失败");
MessageBox.Show($"打印失败:{ex.Message}", "打印机错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
catch (Exception ex)
{
logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
/// <summary>
/// 保存配置到Config并使用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnConfigSave_Click(object sender, EventArgs e)
{
try
{
var downText = (txtWeightDown.Text ?? string.Empty).Trim();
var upText = (txtWeightUp.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(downText) ||
!double.TryParse(downText.Replace('', '.').Replace('。', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var down) || down <= 0)
{
MessageBox.Show("请输入有效的下限(g),必须为正数。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtWeightDown.Focus();
txtWeightDown.SelectAll();
return;
}
if (string.IsNullOrWhiteSpace(upText) ||
!double.TryParse(upText.Replace('', '.').Replace('。', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var up) || up <= 0)
{
MessageBox.Show("请输入有效的上限(g),必须为正数。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtWeightUp.Focus();
txtWeightUp.SelectAll();
return;
}
if (down >= up)
{
MessageBox.Show("下限(g)必须小于上限(g)。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtWeightDown.Focus();
txtWeightDown.SelectAll();
return;
}
var downStr = down.ToString("0.###", CultureInfo.InvariantCulture);
var upStr = up.ToString("0.###", CultureInfo.InvariantCulture);
ConfigHelper.SetValue("WeightDown", downStr);
ConfigHelper.SetValue("WeightUp", upStr);
WeightDown = down;
WeightUp = up;
txtWeightDown.Text = downStr;
txtWeightUp.Text = upStr;
logger.Info($"称重配置已更新WeightDown={downStr}g, WeightUp={upStr}g");
MessageBox.Show("配置已保存并立即生效。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
logger.Error(ex, "保存称重配置失败");
MessageBox.Show($"保存配置失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (!_closeConfirmed && e.CloseReason == CloseReason.UserClosing)
{
try
{
var result = MessageBox.Show(
"确认退出系统?",
"退出确认",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2);
if (result != DialogResult.Yes)
{
e.Cancel = true;
logger.Info("已取消关闭");
return;
}
_closeConfirmed = true;
logger.Info("用户确认关闭,开始退出");
}
catch (Exception ex)
{
// 弹窗异常时,为保证可关闭,默认继续退出,但记录日志
logger.Error(ex, "关闭确认弹窗异常");
_closeConfirmed = true;
}
}
}
}
}