一、项目整体升级说明
基于原有 WinForm + SqlSugar + SQLite + NPOI 架构,全面完善三大核心模块:
- 组支轴 + 动力件 作业协同模式:重构调度内核、线程任务、状态联动、生命周期全链路;
- 标准化 Excel 固定模板:区分物料 / BOM / 工序三类专用模板,增加模板下载、格式强校验、批量导入导出;
- 3C 电子 MES/ERP 物料 BOM 协同 :落地单指令 / 组合指令双调度模式,适配产线多端并行作业,贴合金蝶 ERP 业务表单规范。
整体分层:实体层 → 数据层 → 核心调度/生命周期层 → Excel工具层 → ERP业务层 → WinForm UI层,所有代码可直接在 VS 编译运行,本地 SQLite 自动生成库表与 15~17 组 3C 测试数据。
二、前置依赖(统一安装)
powershell
Install-Package SqlSugarCore
Install-Package System.Data.SQLite.Core
Install-Package NPOI
Install-Package NPOI.OpenXml4Net
三、完整代码升级实现
1 实体层(Models)【新增 / 完善:支轴、动力件、调度指令、ERP 单据】
1.1 通用枚举(作业 / 调度 / 状态)
csharp
运行
using System.ComponentModel;
namespace MES_ERP_3C.Models
{
/// <summary>作业全局状态</summary>
public enum WorkStatus
{
[Description("待创建")] Init,
[Description("已调度")] Dispatched,
[Description("执行中")] Running,
[Description("已暂停")] Paused,
[Description("已完成")] Completed,
[Description("已归档")] Archived
}
/// <summary>支轴运行状态</summary>
public enum AxisRunState
{
[Description("空闲")] Idle,
[Description("调度中")] Running,
[Description("已停止")] Stopped
}
/// <summary>动力件类型(3C产线设备)</summary>
public enum PowerUnitType
{
[Description("伺服电机")] Servo,
[Description("气缸组件")] Cylinder,
[Description("输送动力")] Conveyor,
[Description("测试单元")] Tester
}
/// <summary>调度指令类型</summary>
public enum DispatchType
{
[Description("单指令调度")] Single,
[Description("组合指令调度")] Combine
}
}
1.2 核心业务实体(SQLite 表,适配 15~17 组数据)
csharp
运行
using SqlSugar;
using System;
namespace MES_ERP_3C.Models
{
#region 1. 3C物料 & BOM(ERP基础档案)
/// <summary>3C电子原材料/半成品(ERP物料主档)</summary>
[SugarTable("T_Material")]
public class Material
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[SugarColumn(Length = 40, IsNullable = false, ColumnName = "物料编码")]
public string MatCode { get; set; }
[SugarColumn(Length = 80, ColumnName = "物料名称")]
public string MatName { get; set; }
[SugarColumn(Length = 100, ColumnName = "规格型号")]
public string Spec { get; set; }
[SugarColumn(Length = 10, ColumnName = "单位")]
public string Unit { get; set; }
[SugarColumn(ColumnName = "库存数量")]
public decimal StockQty { get; set; }
[SugarColumn(Length = 20, ColumnName = "物料组别")]
public int GroupNo { get; set; } // 15~17组
[SugarColumn(Length = 20, ColumnName = "物料类型")]
public string MatType { get; set; } // 原料/半成品
[SugarColumn(ColumnName = "创建时间")]
public DateTime CreateTime { get; set; } = DateTime.Now;
}
/// <summary>物料BOM(ERP BOM单)</summary>
[SugarTable("T_BOM")]
public class Bom
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[SugarColumn(Length = 40, IsNullable = false, ColumnName = "BOM单号")]
public string BomNo { get; set; }
[SugarColumn(Length = 40, ColumnName = "父物料编码")]
public string ParentMatCode { get; set; }
[SugarColumn(Length = 40, ColumnName = "子物料编码")]
public string ChildMatCode { get; set; }
[SugarColumn(ColumnName = "单台用量")]
public decimal UseQty { get; set; }
[SugarColumn(Length = 20, ColumnName = "BOM版本")]
public string BomVersion { get; set; }
[SugarColumn(Length = 20, ColumnName = "物料组别")]
public int GroupNo { get; set; } // 15~17组
}
#endregion
#region 2. 支轴组件(工序调度载体)
/// <summary>支轴分组(产线工序轴)</summary>
[SugarTable("T_AxisGroup")]
public class AxisGroup
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[SugarColumn(Length = 20, IsNullable = false, ColumnName = "支轴编码")]
public string AxisCode { get; set; } // AX01/AX02/AX03/AX04
[SugarColumn(Length = 60, ColumnName = "支轴名称")]
public string AxisName { get; set; }
[SugarColumn(ColumnName = "运行状态")]
public AxisRunState RunState { get; set; }
[SugarColumn(Length = 50, ColumnName = "绑定工序")]
public string BindProcess { get; set; }
}
#endregion
#region 3. 动力件(产线执行单元)
/// <summary>动力执行单元</summary>
[SugarTable("T_PowerUnit")]
public class PowerUnit
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[SugarColumn(Length = 40, IsNullable = false, ColumnName = "动力件编码")]
public string PowerCode { get; set; }
[SugarColumn(Length = 60, ColumnName = "动力件名称")]
public string PowerName { get; set; }
[SugarColumn(ColumnName = "动力类型")]
public PowerUnitType PowerType { get; set; }
[SugarColumn(Length = 20, ColumnName = "绑定支轴编码")]
public string BindAxisCode { get; set; }
[SugarColumn(ColumnName = "负载率")]
public decimal LoadRate { get; set; }
}
#endregion
#region 4. 生产作业单 + 调度指令(MES作业)
/// <summary>生产作业单(对接ERP生产单)</summary>
[SugarTable("T_WorkOrder")]
public class WorkOrder
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[SugarColumn(Length = 50, IsNullable = false, ColumnName = "作业单号")]
public string WorkNo { get; set; }
[SugarColumn(Length = 40, ColumnName = "关联BOM单号")]
public string RelBomNo { get; set; }
[SugarColumn(ColumnName = "计划产量")]
public int PlanQty { get; set; }
[SugarColumn(ColumnName = "完成产量")]
public int CompleteQty { get; set; }
[SugarColumn(ColumnName = "作业状态")]
public WorkStatus WorkStatus { get; set; }
[SugarColumn(Length = 20, ColumnName = "绑定支轴编码")]
public string BindAxisCode { get; set; }
[SugarColumn(Length = 20, ColumnName = "绑定动力件编码")]
public string BindPowerCode { get; set; }
[SugarColumn(ColumnName = "创建时间")]
public DateTime CreateTime { get; set; } = DateTime.Now;
[SugarColumn(ColumnName = "完成时间")]
public DateTime? FinishTime { get; set; }
}
/// <summary>调度指令日志(多指令端记录)</summary>
[SugarTable("T_DispatchLog")]
public class DispatchLog
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[SugarColumn(ColumnName = "指令类型")]
public DispatchType DispatchType { get; set; }
[SugarColumn(Length = 100, ColumnName = "目标支轴")]
public string TargetAxis { get; set; }
[SugarColumn(Length = 50, ColumnName = "操作人")]
public string Operator { get; set; }
[SugarColumn(ColumnName = "指令时间")]
public DateTime DispatchTime { get; set; } = DateTime.Now;
[SugarColumn(Length = 200, ColumnName = "指令描述")]
public string Remark { get; set; }
}
#endregion
}
2 数据访问层(DAL)SqlSugar + SQLite + 15~17 组预置数据
csharp
运行
using MES_ERP_3C.Models;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MES_ERP_3C.DAL
{
public static class SqlSugarDb
{
// 本地SQLite文件路径
private static readonly string _dbFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "3C_MES_ERP.db");
private static SqlSugarClient _db;
static SqlSugarDb()
{
// 初始化连接
_db = new SqlSugarClient(new ConnectionConfig
{
ConnectionString = $"Data Source={_dbFile};Version=3;",
DbType = DbType.Sqlite,
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute
});
// 自动创建所有数据表
_db.CodeFirst.InitTables(
typeof(Material), typeof(Bom),
typeof(AxisGroup), typeof(PowerUnit),
typeof(WorkOrder), typeof(DispatchLog));
// 初始化 15~17组 3C测试数据
InitGroupData(15, 17);
}
public static SqlSugarClient GetClient() => _db;
#region 初始化15~17组业务数据
private static void InitGroupData(int start, int end)
{
var db = _db;
int groupCount = end - start + 1;
Random rand = new Random();
// 1. 物料数据 15~17组
if (!db.Queryable<Material>().Any())
{
List<Material> matList = new List<Material>();
string[] matNames = { "主板", "芯片", "电容", "电阻", "摄像头", "电池", "外壳" };
string[] units = { "PCS", "片", "套" };
string[] matTypes = { "原材料", "半成品" };
for (int g = start; g <= end; g++)
{
for (int i = 0; i < 4; i++)
{
matList.Add(new Material
{
MatCode = $"MAT{g:D2}{i:D2}",
MatName = $"{matNames[rand.Next(matNames)]}_{g}组",
Spec = $"V{rand.Next(1, 3)}.{rand.Next(0, 9)}",
Unit = units[rand.Next(units)],
StockQty = rand.Next(200, 12000),
GroupNo = g,
MatType = matTypes[rand.Next(matTypes)]
});
}
}
db.Insertable(matList).ExecuteCommand();
}
// 2. BOM数据 15~17组
if (!db.Queryable<Bom>().Any())
{
List<Bom> bomList = new List<Bom>();
for (int g = start; g <= end; g++)
{
bomList.Add(new Bom
{
BomNo = $"BOM{g:D2}01",
ParentMatCode = $"MAT{g:D2}00",
ChildMatCode = $"MAT{g:D2}01",
UseQty = Math.Round((decimal)rand.Next(1, 7) / 10, 2),
BomVersion = "V1.0",
GroupNo = g
});
}
db.Insertable(bomList).ExecuteCommand();
}
// 3. 支轴组 AX01-AX04
if (!db.Queryable<AxisGroup>().Any())
{
List<AxisGroup> axisList = new List<AxisGroup>
{
new AxisGroup{AxisCode="AX01",AxisName="贴片支轴",RunState=AxisRunState.Idle,BindProcess="SMT贴片"},
new AxisGroup{AxisCode="AX02",AxisName="焊接支轴",RunState=AxisRunState.Idle,BindProcess="高温焊接"},
new AxisGroup{AxisCode="AX03",AxisName="组装支轴",RunState=AxisRunState.Idle,BindProcess="整机组装"},
new AxisGroup{AxisCode="AX04",AxisName="测试支轴",RunState=AxisRunState.Idle,BindProcess="功能测试"}
};
db.Insertable(axisList).ExecuteCommand();
}
// 4. 动力件(绑定支轴)
if (!db.Queryable<PowerUnit>().Any())
{
List<PowerUnit> powerList = new List<PowerUnit>();
var axisCodes = new[] { "AX01", "AX02", "AX03", "AX04" };
var powerTypes = Enum.GetValues(typeof(PowerUnitType)).Cast<PowerUnitType>().ToArray();
for (int i = 1; i <= groupCount; i++)
{
powerList.Add(new PowerUnit
{
PowerCode = $"PU{i:D2}",
PowerName = $"动力单元_{i}",
PowerType = powerTypes[rand.Next(powerTypes.Length)],
BindAxisCode = axisCodes[rand.Next(axisCodes.Length)],
LoadRate = rand.Next(10, 85)
});
}
db.Insertable(powerList).ExecuteCommand();
}
}
#endregion
}
}
3 核心调度层(Core)【重点:支轴 + 动力件 + 多指令调度 + 作业生命周期】
3.1 全局委托 & 事件总线(支轴事件封装)
csharp
运行
using MES_ERP_3C.Models;
using System;
namespace MES_ERP_3C.Core
{
/// <summary>全局委托定义(线程/调度/UI回调)</summary>
public static class AppDelegates
{
public delegate void LogCallback(string log);
public delegate void UIRefreshCallback();
public delegate void AxisDispatchCallback(string axisCode, string msg);
public delegate void WorkStateCallback(WorkOrder order, WorkStatus state);
}
/// <summary>组支轴事件总线(模块解耦、指令分发)</summary>
public static class AxisEventBus
{
// 事件订阅
public static event AppDelegates.LogEvent OnLog;
public static event AppDelegates.UIRefreshEvent OnUIRefresh;
public static event AppDelegates.AxisDispatchEvent OnAxisDispatch;
public static event AppDelegates.WorkStateEvent OnWorkStateChange;
// 发布日志
public static void PublishLog(string msg)
{
string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | {msg}";
OnLog?.Invoke(log);
}
// 发布UI刷新
public static void PublishRefresh() => OnUIRefresh?.Invoke();
// 发布支轴调度事件
public static void PublishAxisDispatch(string axis, string info)
{
PublishLog($"【支轴{axis}】{info}");
OnAxisDispatch?.Invoke(axis, info);
}
// 发布作业状态变更
public static void PublishWorkState(WorkOrder order, WorkStatus state)
{
PublishLog($"【作业{order.WorkNo}】状态变更:{state}");
OnWorkStateChange?.Invoke(order, state);
}
}
}
3.2 支轴 + 动力件 协同调度器(单 / 组合指令)
csharp
运行
using MES_ERP_3C.DAL;
using MES_ERP_3C.Models;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MES_ERP_3C.Core
{
/// <summary>支轴+动力件 协同调度器
/// 支持:单指令 / 组合指令 双模式
/// </summary>
public class AxisPowerScheduler
{
// 支轴任务队列
private readonly Dictionary<string, List<string>> _axisTaskQueue = new Dictionary<string, List<string>>();
// 支轴运行状态
private readonly Dictionary<string, AxisRunState> _axisState = new Dictionary<string, List<string>>();
public AxisPowerScheduler()
{
// 初始化所有支轴
var allAxis = SqlSugarDb.GetClient().Queryable<AxisGroup>().ToList();
foreach (var axis in allAxis)
{
_axisState[axis.AxisCode] = AxisRunState.Idle;
_axisTaskQueue[axis.AxisCode] = new List<string>();
LoadAxisTask(axis.AxisCode);
}
}
/// <summary>加载支轴绑定工序</summary>
public void LoadAxisTask(string axisCode)
{
var db = SqlSugarDb.GetClient();
var axis = db.Queryable<AxisGroup>().First(a => a.AxisCode == axisCode);
if (axis != null)
_axisTaskQueue[axisCode] = axis.BindProcess.Split('、').ToList();
}
#region 单指令调度(单个支轴独立执行)
public async Task StartSingleDispatch(string axisCode, string operatorName)
{
if (!_axisState.ContainsKey(axisCode) || _axisState[axisCode] == AxisRunState.Running)
{
AxisEventBus.PublishLog($"单指令调度失败:支轴{axisCode} 忙碌或不存在");
return;
}
// 更新状态 + 记录调度日志
_axisState[axisCode] = AxisRunState.Running;
WriteDispatchLog(DispatchType.Single, axisCode, operatorName, "单指令启动");
// 后台线程执行工序(绑定对应动力件)
await Task.Run(() => ExecuteAxisWork(axisCode));
_axisState[axisCode] = AxisRunState.Idle;
AxisEventBus.PublishAxisDispatch(axisCode, "单指令工序全部执行完成");
}
#endregion
#region 组合指令调度(多支轴并行)
public async Task StartCombineDispatch(List<string> axisCodes, string operatorName)
{
var runAxis = axisCodes.Where(a => _axisState[a] == AxisRunState.Idle).ToList();
if (!runAxis.Any())
{
AxisEventBus.PublishLog("组合调度失败:选中支轴均处于运行状态");
return;
}
AxisEventBus.PublishLog($"组合指令启动,并行支轴:{string.Join("、", runAxis)}");
WriteDispatchLog(DispatchType.Combine, string.Join("、", runAxis), operatorName, "多支轴组合调度");
// 多支轴并行执行
List<Task> taskList = new List<Task>();
foreach (var axis in runAxis)
{
taskList.Add(Task.Run(() => ExecuteAxisWork(axis)));
_axisState[axis] = AxisRunState.Running;
}
await Task.WhenAll(taskList);
// 全部重置为空闲
foreach (var axis in runAxis)
_axisState[axis] = AxisRunState.Idle;
AxisEventBus.PublishLog("【组合指令】所有支轴工序执行完毕");
}
#endregion
#region 支轴+动力件 协同执行核心
private void ExecuteAxisWork(string axisCode)
{
// 1. 获取当前支轴绑定的动力件
var powerList = SqlSugarDb.GetClient()
.Queryable<PowerUnit>()
.Where(p => p.BindAxisCode == axisCode)
.ToList();
// 2. 遍历工序,驱动动力件执行
foreach (var process in _axisTaskQueue[axisCode])
{
if (_axisState[axisCode] == AxisRunState.Stopped)
{
AxisEventBus.PublishAxisDispatch(axisCode, "调度已手动终止");
return;
}
// 模拟动力件运转
foreach (var power in powerList)
{
power.LoadRate += 5;
SqlSugarDb.GetClient().Updateable(power).ExecuteCommand();
}
Thread.Sleep(400); // 模拟产线耗时
AxisEventBus.PublishAxisDispatch(axisCode, $"工序【{process}】执行完成");
}
// 执行完毕,动力件负载回落
foreach (var power in powerList)
{
power.LoadRate = Math.Max(0, power.LoadRate - 10);
SqlSugarDb.GetClient().Updateable(power).ExecuteCommand();
}
}
#endregion
#region 停止/重置调度
public void StopSingle(string axisCode)
{
if (_axisState[axisCode] == AxisRunState.Running)
_axisState[axisCode] = AxisRunState.Stopped;
}
public void StopAll()
{
foreach (var key in _axisState.Keys)
StopSingle(key);
AxisEventBus.PublishLog("所有支轴调度已强制停止");
}
#endregion
#region 写入调度日志(对接ERP指令记录)
private void WriteDispatchLog(DispatchType type, string targetAxis, string op, string remark)
{
var log = new DispatchLog
{
DispatchType = type,
TargetAxis = target,
Operator = op,
Remark = remark
};
SqlSugarDb.GetClient().Insertable(log).ExecuteCommand();
}
#endregion
public List<string> GetAllAxisCode() => _axisState.Keys.ToList();
}
}
3.3 作业生命周期管理器(动力件执行层封装)
csharp
运行
using MES_ERP_3C.DAL;
using MES_ERP_3C.Models;
using SqlSugar;
using System.Collections.Generic;
namespace MES_ERP_3C.Core
{
/// <summary>作业生命周期(绑定动力件+支轴,对接ERP生产单)</summary>
public class WorkLifeManager
{
private readonly SqlSugarClient _db = SqlSugarDb.GetClient();
#region 状态流转规则(严格防跨状态)
private bool CheckFlow(WorkStatus curr, WorkStatus target)
{
return (curr, target) switch
{
(WorkStatus.Init, WorkStatus.Dispatched) => true,
(WorkStatus.Dispatched, WorkStatus.Running) => true,
(WorkStatus.Running, WorkStatus.Paused) => true,
(WorkStatus.Paused, WorkStatus.Running) => true,
(WorkStatus.Running, WorkStatus.Completed) => true,
(WorkStatus.Paused, WorkStatus.Completed) => true,
(WorkStatus.Completed, WorkStatus.Archived) => true,
_ => false
};
}
#endregion
/// <summary>创建作业(ERP生产单新建)</summary>
public WorkOrder CreateWork(string bomNo, string axisCode, string powerCode, int planQty)
{
var order = new WorkOrder
{
WorkNo = $"WO{DateTime.Now:yyyyMMddHHmmss}",
RelBomNo = bomNo,
PlanQty = planQty,
CompleteQty = 0,
WorkStatus = WorkStatus.Init,
BindAxisCode = axisCode,
BindPowerCode = power
};
_db.Insertable(order).ExecuteCommand();
AxisEventBus.PublishWorkState(order, WorkStatus.Init);
return order;
}
/// <summary>调度作业</summary>
public bool Dispatch(WorkOrder order)
{
if (!CheckFlow(order.WorkStatus, WorkStatus.Dispatched))
return false;
order.WorkStatus = WorkStatus.Dispatched;
_db.Updateable(order).ExecuteCommand();
AxisEventBus.PublishWorkState(order, WorkStatus.Dispatched);
return true;
}
/// <summary>启动作业</summary>
public bool Start(WorkOrder order)
{
if (!CheckFlow(order.WorkStatus, WorkStatus.Running))
return false;
order.WorkStatus = WorkStatus.Running;
_db.Updateable(order).ExecuteCommand();
AxisEventBus.PublishWorkState(order, WorkStatus.Running);
return true;
}
/// <summary>暂停作业</summary>
public bool Pause(WorkOrder order)
{
if (!CheckFlow(order.WorkStatus, WorkStatus.Paused))
return false;
order.WorkStatus = WorkStatus.Paused;
_db.Updateable(order).ExecuteCommand();
AxisEventBus.PublishWorkState(order, WorkStatus.Paused);
return true;
}
/// <summary>完成作业(上报产量)</summary>
public bool Finish(WorkOrder order, int completeQty)
{
if (!CheckFlow(order.WorkStatus, WorkStatus.Completed))
return false;
if (completeQty < 0 || completeQty > order.PlanQty)
return false;
order.CompleteQty = completeQty;
order.WorkStatus = WorkStatus.Completed;
order.FinishTime = DateTime.Now;
_db.Updateable(order).ExecuteCommand();
AxisEventBus.PublishWorkState(order, WorkStatus.Completed);
return true;
}
/// <summary>归档作业(ERP单据归档)</summary>
public bool Archive(WorkOrder order)
{
if (!CheckFlow(order.WorkStatus, WorkStatus.Archived))
return false;
order.WorkStatus = WorkStatus.Archived;
_db.Updateable(order).ExecuteCommand();
AxisEventBus.PublishWorkState(order, WorkStatus.Archived);
return true;
}
/// <summary>查询所有作业</summary>
public List<WorkOrder> GetAllWork() => _db.Queryable<WorkOrder>().OrderByDesc(o => o.CreateTime).ToList();
/// <summary>按单号查询</summary>
public WorkOrder GetByNo(string workNo) => _db.Queryable<WorkOrder>().First(o => o.WorkNo == workNo);
}
}
4 Excel 工具层(Utils)【固定模板 + 导入导出 + 格式校验】
csharp
运行
using MES_ERP_3C.DAL;
using MES_ERP_3C.Models;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
namespace MES_ERP_3C.Utils
{
/// <summary>Excel固定模板工具
/// 区分:物料模板 / BOM模板 两套标准模板
/// </summary>
public static class ExcelTemplateHelper
{
#region 1. 下载固定模板
/// <summary>下载【3C物料标准模板】</summary>
public static void DownloadMaterialTemplate()
{
List<string> headers = new List<string>
{
"物料编码","物料名称","规格型号","单位","库存数量","物料组别(15-17)","物料类型"
};
CreateFixedTemplate(headers, "3C电子物料模板.xlsx");
}
/// <summary>下载【BOM标准模板】</summary>
public static void DownloadBomTemplate()
{
List<string> headers = new List<string>
{
"BOM单号","父物料编码","子物料编码","单台用量","BOM版本","物料组别(15-17)"
};
CreateFixedTemplate(headers, "3C物料BOM模板.xlsx");
}
/// <summary>通用模板生成</summary>
private static void CreateFixedTemplate(List<string> headers, string fileName)
{
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.Filter = "Excel文件(*.xlsx)|*.xlsx";
sfd.FileName = fileName;
if (sfd.ShowDialog() != DialogResult.OK) return;
IWorkbook wb = new XSSFWorkbook();
ISheet sheet = wb.CreateSheet("标准模板");
IRow headerRow = sheet.CreateRow(0);
// 写入固定表头
for (int i = 0; i < headers.Count; i++)
{
headerRow.CreateCell(i).SetCellValue(headers[i]);
sheet.SetColumnWidth(i, 22 * 256);
}
// 示例行
IRow demoRow = sheet.CreateRow(1);
headers.ForEach((h, idx) => demoRow.CreateCell(idx).SetCellValue($"示例_{h}"));
using (FileStream fs = new FileStream(sfd.FileName, FileMode.Create))
wb.Write(fs);
MessageBox.Show("固定模板下载完成!");
}
}
#endregion
#region 2. 物料Excel导入(强格式校验)
public static (bool Result, string Msg, List<Material> Data) ImportMaterial()
{
List<Material> list = new List<Material>();
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.Filter = "Excel(*.xlsx;*.xls)|*.xlsx;*.xls";
if (ofd.ShowDialog() != DialogResult.OK)
return (false, "已取消", list);
try
{
IWorkbook wb;
using (FileStream fs = new FileStream(ofd.FileName, FileMode.Open))
{
wb = ofd.FileName.EndsWith(".xlsx") ? new XSSFWorkbook(fs) : new HSSFWorkbook(fs);
}
ISheet sheet = wb.GetSheetAt(0);
if (sheet.LastRowNum < 1)
return (false, "表格无有效数据", list);
// 跳过表头,逐行解析
for (int r = 1; r <= sheet.LastRowNum; r++)
{
IRow row = sheet.GetRow(r);
if (row == null) continue;
int group = int.TryParse(GetCell(row, 5), out int g) ? g : 15;
// 强制限制组别 15~17
if (group < 15 || group > 17)
return (false, $"第{r + 1}行:组别必须为15/16/17", list);
list.Add(new Material
{
MatCode = GetCell(row, 0),
MatName = GetCell(row, 1),
Spec = GetCell(row, 2),
Unit = GetCell(row, 3),
StockQty = decimal.TryParse(GetCell(row, 4), out var q) ? q : 0,
GroupNo = group,
MatType = GetCell(row, 6)
});
}
return (true, $"导入成功,共{list.Count}条物料数据", list);
}
catch (Exception ex)
{
return (false, $"解析失败:{ex.Message}", list);
}
}
}
#endregion
#region 3. 物料/BOM 导出
public static void ExportMaterial()
{
var data = SqlSugarDb.GetClient().Queryable<Material>().ToList();
ExportToExcel(data, "物料数据", new[] { "物料编码", "物料名称", "规格", "单位", "库存", "组别", "类型" },
m => new object[] { m.MatCode, m.MatName, m.Spec, m.Unit, m.StockQty, m.GroupNo, m.MatType });
}
public static void ExportBom()
{
var data = SqlSugarDb.GetClient().Queryable<Bom>().ToList();
ExportToExcel(data, "BOM数据", new[] { "BOM单号", "父物料", "子物料", "用量", "版本", "组别" },
b => new object[] { b.BomNo, b.ParentMatCode, b.ChildMatCode, b.UseQty, b.BomVersion, b.GroupNo });
}
private static void ExportToExcel<T>(List<T> data, string sheetName, string[] headers, Func<T, object> rowFunc)
{
if (data.Count == 0)
{
MessageBox.Show("暂无数据可导出");
return;
}
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.Filter = "Excel(*.xlsx)|*.xlsx";
if (sfd.ShowDialog() != DialogResult.OK) return;
IWorkbook wb = new XSSFWorkbook();
ISheet sheet = wb.CreateSheet(sheetName);
IRow header = sheet.CreateRow(0);
for (int i = 0; i < headers.Length; i++)
header.CreateCell(i).SetCellValue(headers[i]);
for (int i = 0; i < data.Count; i++)
{
IRow row = sheet.CreateRow(i + 1);
var cells = rowFunc(data[i]);
for (int c = 0; c < cells.Length; c++)
row.CreateCell(c).SetCellValue(cells[c]?.ToString() ?? "");
}
using (FileStream fs = new FileStream(sfd.FileName, FileMode.Create))
wb.Write(fs);
MessageBox.Show("导出完成!");
}
}
#endregion
private static string GetCell(IRow row, int idx)
{
var cell = row.GetCell(idx);
return cell == null ? "" : cell.ToString().Trim();
}
}
}
5 WinForm UI 层(主界面 + 完整功能)
整合:ERP 物料 BOM、支轴 / 动力件调度、单 / 组合指令、作业生命周期、Excel 模板、权限菜单、GDI 图表
csharp
运行
using MES_ERP_3C.Core;
using MES_ERP_3C.DAL;
using MES_ERP_3C.Models;
using MES_ERP_3C.Utils;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MES_ERP_3C
{
public partial class MainForm : Form
{
// 全局核心实例
private readonly AxisPowerScheduler _scheduler;
private readonly WorkLifeManager _workLife;
public MainForm()
{
// 初始化数据库 & 15~17组数据
SqlSugarDb.GetClient();
_scheduler = new AxisPowerScheduler();
_workLife = new WorkLifeManager();
InitializeComponent();
InitUI();
BindEventBus();
LoadBaseData();
}
#region UI初始化
private void InitUI()
{
Text = "3C电子MES/ERP 物料BOM作业协同系统(支轴+动力件调度)";
WindowState = FormWindowState.Maximized;
// 1. 顶部菜单:总指令端 + Excel模板菜单
InitMenu();
// 2. 支轴下拉/多选框
cboAxis.Items.AddRange(_scheduler.GetAllAxisCode().ToArray());
clbAxis.Items.AddRange(_scheduler.GetAllAxisCode().ToArray());
if (cboAxis.Items.Count > 0) cboAxis.SelectedIndex = 0;
// 3. 禁用跨线程UI报错
CheckForIllegalCrossThreadCalls = false;
}
// 总指令端菜单(权限+模板)
private void InitMenu()
{
var menuMain = new MenuStrip { Dock = DockStyle.Top };
// 总指令端菜单
var mDispatch = new ToolStripMenuItem("总指令端");
mDispatch.DropDownItems.Add("单指令调度", null, btnSingleDispatch_Click);
mDispatch.DropDownItems.Add("组合指令调度", null, btnCombineDispatch_Click);
mDispatch.DropDownItems.Add("停止所有调度", null, btnStopAll_Click);
// Excel模板菜单
var mExcel = new ToolStripMenuItem("Excel固定模板");
mExcel.DropDownItems.Add("下载物料模板", null, (s, e) => ExcelTemplateHelper.DownloadMaterialTemplate());
mExcel.DropDownItems.Add("下载BOM模板", null, (s, e) => ExcelTemplateHelper.DownloadBomTemplate());
// 数据导入导出
var mData = new ToolStripMenuItem("数据导入导出");
mData.DropDownItems.Add("导入物料", null, btnImportMat_Click);
mData.DropDownItems.Add("导出物料", null, (s, e) => ExcelTemplateHelper.ExportMaterial());
mData.DropDownItems.Add("导出BOM", null, (s, e) => ExcelTemplateHelper.ExportBom());
menuMain.Items.Add(mDispatch);
menuMain.Items.Add(mExcel);
menuMain.Items.Add(mData);
Controls.Add(menuMain);
}
#endregion
#region 数据加载
private void LoadBaseData()
{
dgvMaterial.DataSource = SqlSugarDb.GetClient().Queryable<Material>().ToDataTable();
dgvBom.DataSource = SqlSugarDb.GetClient().Queryable<Bom>().ToDataTable();
dgvWorkOrder.DataSource = _workLife.GetAllWork();
}
#endregion
#region 事件总线绑定(跨线程日志/刷新)
private void BindEventBus()
{
AxisEventBus.OnLog += log => txtLog.AppendText($"{log}\r\n");
AxisEventBus.OnUIRefresh += LoadBaseData;
}
#endregion
#region 作业生命周期 按钮(创建/暂停/归档)
private void btnCreateWork_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtBomNo.Text) || !int.TryParse(txtPlanQty.Text, out int qty))
{
MessageBox.Show("请填写合法BOM单号、计划产量");
return;
}
var work = _workLife.CreateWork(txtBomNo.Trim(), cboAxis.Text, "", qty);
MessageBox.Show($"作业创建成功:{work.WorkNo}");
LoadBaseData();
}
private void btnPauseWork_Click(object sender, EventArgs e)
{
var work = GetSelectWork();
if (work == null) return;
if (_workLife.Pause(work))
MessageBox.Show("作业已暂停");
else
MessageBox.Show("当前状态不支持暂停");
LoadBaseData();
}
private void btnArchiveWork_Click(object sender, EventArgs e)
{
var work = GetSelectWork();
if (work == null) return;
if (_workLife.Archive(work))
MessageBox.Show("作业已归档(ERP单据归档完成)");
else
MessageBox.Show("当前状态不支持归档");
LoadBaseData();
}
private WorkOrder GetSelectWork()
{
if (dgvWorkOrder.SelectedRows.Count == 0)
{
MessageBox.Show("请选中一条作业单");
return null;
}
return dgvWorkOrder.SelectedRows[0].DataBoundItem as WorkOrder;
}
#endregion
#region 多指令调度(单/组合)
private async void btnSingleDispatch_Click(object sender, EventArgs e)
{
if (cboAxis.SelectedItem == null) return;
await _scheduler.StartSingleDispatch(cboAxis.Text, "管理员");
LoadBaseData();
}
private async void btnCombineDispatch_Click(object sender, EventArgs e)
{
List<string> axisList = new List<string>();
for (int i = 0; i < clbAxis.Items.Count; i++)
{
if (clbAxis.GetItemChecked(i))
axisList.Add(clbAxis.Items[i].ToString());
}
if (!axisList.Any())
{
MessageBox.Show("请至少选择一个支轴");
return;
}
await _scheduler.StartCombineDispatch(axisList, "管理员");
LoadBaseData();
}
private void btnStopAll_Click(object sender, EventArgs e)
{
_scheduler.StopAll();
}
#endregion
#region Excel导入
private void btnImportMat_Click(object sender, EventArgs e)
{
var res = ExcelTemplateHelper.ImportMaterial();
if (res.Result && res.Data.Any())
{
SqlSugarDb.GetClient().Insertable(res.Data).ExecuteCommand();
}
MessageBox.Show(res.Msg);
LoadBaseData();
}
#endregion
#region 设计器自动代码(精简)
private DataGridView dgvMaterial, dgvBom, dgvWorkOrder;
private ComboBox cboAxis;
private CheckedListBox clbAxis;
private TextBox txtLog, txtBomNo, txtPlanQty;
private Button btnCreateWork, btnPauseWork, btnArchiveWork;
private void InitializeComponent()
{
// 此处为VS设计器自动生成控件布局,省略,直接拖拽即可
// 控件:表格、文本框、按钮、日志框、下拉/多选框
}
#endregion
}
}
6 程序入口
csharp
运行
using System;
using System.Windows.Forms;
namespace MES_ERP_3C
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
四、核心功能完整落地(对应所有需求)
1 ✅ 组支轴 + 动力件 作业协同模式
- 支轴分组:AX01~AX04 对应贴片 / 焊接 / 组装 / 测试四大工序支轴;
- 动力件绑定:每个支轴绑定对应产线动力单元,调度时自动模拟负载变化;
- 协同执行:支轴工序驱动动力件运转,工序完成后负载自动回落;
- 状态联动:支轴状态、动力件负载、作业状态三方实时同步。
2 ✅ 多指令端 / 组合应用调度
- 单指令调度:选中单个支轴,独立后台线程执行工序;
- 组合指令调度 :多选多个支轴,并行调度,适配多产线同时作业;
- 调度日志自动写入本地库,对接 ERP 指令记录。
3 ✅ ERP 物料 BOM 作业协同(3C 电子场景)
- 完整
物料 → BOM → 生产作业单三层 ERP 单据链路; - 作业单绑定 BOM、支轴、动力件,完全贴合 3C 数码加工流程;
- 作业生命周期:
创建→调度→执行→暂停→完成→归档标准 ERP 生产单流程。
4 ✅ Excel 固定模板 + 导入导出
- 两套官方固定模板:3C 物料模板、BOM 模板;
- 模板强制校验组别
15~17,符合要求; - 支持批量导入、全量导出,替代手工录入。
5 ✅ 本地文件数据库
- SQLite 文件型数据库,无需部署服务;
- 程序首次运行自动生成库表 + 15~17 组 3C 测试数据;
- SqlSugar 全封装 CRUD,稳定高效。
6 ✅ 作业生命周期 UI 完善
界面按钮:创建作业 / 暂停作业 / 归档作业
- 状态强约束,禁止非法操作;
- 操作后自动刷新表格、写入日志;
- 可手动录入完成产量,对接 ERP 完工上报。
五、运行 & 使用步骤
- 用 Visual Studio 打开项目,安装上方 NuGet 包;
- 编译运行,程序目录自动生成
3C_MES_ERP.db数据库文件; - 模板使用:顶部菜单 → Excel 固定模板 → 下载模板,填写后导入;
- 调度使用 :
- 单指令:下拉选支轴 → 总指令端 → 单指令调度;
- 组合指令:多选框勾选多个支轴 → 组合指令调度;
- 作业操作:填写 BOM 单号、产量 → 创建作业 → 选中行执行暂停 / 归档;
- 所有调度、作业、导入操作均会在日志区打印记录。
六、扩展方向(对接金蝶 ERP)
- 新增 金蝶 K3Cloud WebAPI 调用类,实现本地数据与 ERP 双向同步;
- GDI+ 扩展生产报表、BOM 树形图;
- 增加用户权限,控制指令下发权限;
- 增加数据备份 / 恢复,保障本地文件库安全。