3C 数码电子产品 MES/ERP BOM 协同工作台完整组件代码
项目简述
适配 ** 数码小物件(耳机 / 手环 / 充电器 / PCB 小件 / 阻容件)**MES 车间 BOM 协同;数据分组规范:
- 10 组:产品、半成品、原料物料库数据
- 11 组:产线、工站、工序工艺路径基础数据
- 12 组:BOM 明细、生产执行任务、物料需求汇总业务数据 技术:
WinForm+.NET4.8+SqlSugar+SQLite本地文件库+GDI原生绘图+事件总线+Task异步线程架构:顶部菜单联合控制端 + Tab 多主题工作台、分执行端工位模块、BOM 汇总工作台、标准化 GDI 复合图表(柱状 + 仪表盘一体控件)
一、NuGet 安装
powershell
Install-Package SqlSugarCore
Install-Package System.Data.SQLite
二、实体 Model(分 10/11/12 三组数据表)
csharp
运行
using SqlSugar;
using System;
namespace MES3C_Digital
{
#region 【10组 物料主数据:成品/半成品/原材料】
[SugarTable("T10_Product")]
public class T10_Product//成品
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string ProdCode { get; set; }
public string ProdName { get; set; }
public string Spec { get; set; }
}
[SugarTable("T10_Semi")]
public class T10_Semi//半成品
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string SemiCode { get; set; }
public string SemiName { get; set; }
public int ParentProdId { get; set; }//归属成品
}
[SugarTable("T10_Material")]
public class T10_Material//3C小物料:阻容、芯片、连接器
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string MatCode { get; set; }
public string MatName { get; set; }
public string Spec { get; set; }
public int StockQty { get; set; }//现有库存
}
#endregion
#region 【11组 产线工站工序基础数据】
[SugarTable("T11_Line")]
public class T11_Line
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string LineCode { get; set; }
public string LineName { get; set; }
}
[SugarTable("T11_Station")]
public class T11_Station
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string StaCode { get; set; }
public string StaName { get; set; }
public int LineId { get; set; }//归属产线
}
[SugarTable("T11_Process")]
public class T11_Process//工序路径
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string ProcName { get; set; }
public int StationId { get; set; }
public int SortNo { get; set; }//工序排序
}
#endregion
#region 【12组 BOM+生产任务+需求汇总业务数据】
[SugarTable("T12_BomItem")]
public class T12_BomItem//BOM用料明细
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string TargetCode { get; set; }//成品/半成品编码
public string MatCode { get; set; }//原料编码
public decimal UnitQty { get; set; }//单台用量
public decimal LossRate { get; set; }//损耗系数
}
[SugarTable("T12_ProdTask")]
public class T12_ProdTask//产线执行任务【分执行端数据源】
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string TaskNo { get; set; }
public int StationId { get; set; }
public string TargetCode { get; set; }//投产产品
public string Status { get; set; }//待投产/生产中/已完工
public int PlanQty { get; set; }//计划投产数量
}
[SugarTable("T12_Demand")]
public class T12_Demand//BOM需求汇总
{
[SugarColumn(IsPrimaryKey = true,IsIdentity = true)]
public int Id { get; set; }
public string MatName { get; set; }
public decimal NeedTotal { get; set; }//总需求
public decimal Stock { get; set; }//现有库存
public decimal ShortQty { get; set; }//缺料数量
}
#endregion
//图表传输DTO
public class BomChartDto
{
public string MatName { get; set; }
public decimal Need { get; set; }
public decimal Stock { get; set; }
}
}
三、SqlSugar 本地数据库帮助类(自动建库 + 初始化 10/11/12 组测试数据)
csharp
运行
using SqlSugar;
using System;
using System.Collections.Generic;
using System.IO;
namespace MES3C_Digital
{
public static class DbHelper
{
private static readonly string DbFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MES3C_Digital.db");
public static SqlSugarClient Db { get; private set; }
public static void InitDb()
{
Db = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = $"Data Source={DbFile};Version=3;",
DbType = DbType.Sqlite,
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute
});
//自动建全表
Db.CodeFirst.InitTables(
typeof(T10_Product),typeof(T10_Semi),typeof(T10_Material),
typeof(T11_Line),typeof(T11_Station),typeof(T11_Process),
typeof(T12_BomItem),typeof(T12_ProdTask),typeof(T12_Demand));
InitGroup10();
InitGroup11();
InitGroup12();
}
//初始化10组数据
static void InitGroup10()
{
if(Db.Queryable<T10_Material>().Any()) return;
var prod = new List<T10_Product>()
{
new(){ProdCode="P001",ProdName="TWS蓝牙耳机",Spec="标准版"},
new(){ProdCode="P002",ProdName="智能手环",Spec="运动款"}
};
var semi = new List<T10_Semi>()
{
new(){SemiCode="SEMI001",SemiName="蓝牙主控模组",ParentProdId=1},
new(){SemiCode="SEMI002",SemiName="手环传感模组",ParentProdId=2}
};
var mat = new List<T10_Material>()
{
new(){MatCode="R0402",MatName="0402贴片电阻",Spec="10K",StockQty=55000},
new(){MatCode="C0402",MatName="0402贴片电容",Spec="10UF",StockQty=32000},
new(){MatCode="IC_BLE",MatName="BLE蓝牙芯片",Spec="5.3",StockQty=28000}
};
Db.Insertable(prod).ExecuteCommand();
Db.Insertable(semi).ExecuteCommand();
Db.Insertable(mat).ExecuteCommand();
}
//初始化11组产线工站工序
static void InitGroup11()
{
if(Db.Queryable<T11_Line>().Any()) return;
var line = new List<T11_Line>() { new(){LineCode="L11",LineName="数码组装11号线"},new(){LineCode="L12",LineName="数码组装12号线"} };
var sta = new List<T11_Station>()
{
new(){StaCode="SMT01",StaName="SMT贴片工站",LineId=1},
new(){StaCode="ASB01",StaName="半成品组装工站",LineId=1},
new(){StaCode="TEST01",StaName="功能测试工站",LineId=2}
};
var proc = new List<T11_Process>()
{
new(){ProcName="SMT贴片",StationId=1,SortNo=1},
new(){ProcName="元器件组装",StationId=2,SortNo=2},
new(){ProcName="整机测试",StationId=3,SortNo=3}
};
Db.Insertable(line).ExecuteCommand();
Db.Insertable(sta).ExecuteCommand();
Db.Insertable(proc).ExecuteCommand();
}
//初始化12组BOM、生产任务
static void InitGroup12()
{
if(Db.Queryable<T12_BomItem>().Any()) return;
var bom = new List<T12_BomItem>()
{
new(){TargetCode="SEMI001",MatCode="R0402",UnitQty=6, LossRate=0.02m},
new(){TargetCode="SEMI001",MatCode="C0402",UnitQty=4, LossRate=0.02m},
new(){TargetCode="SEMI001",MatCode="IC_BLE",UnitQty=1, LossRate=0.015m}
};
var task = new List<T12_ProdTask>()
{
new(){TaskNo="T1201",StationId=1,TargetCode="P001",Status="待投产",PlanQty=12000},
new(){TaskNo="T1202",StationId=3,TargetCode="P002",Status="生产中",PlanQty=8000}
};
Db.Insertable(bom).ExecuteCommand();
Db.Insertable(task).ExecuteCommand();
}
/// <summary>按投产数量自动核算BOM需求(核心业务)</summary>
public static List<BomChartDto> CalcBomNeed(string targetCode,int planQty)
{
var bomList = Db.Queryable<T12_BomItem>().Where(x=>x.TargetCode==targetCode).ToList();
var matList = Db.Queryable<T10_Material>().ToList();
List<BomChartDto> res = new List<BomChartDto>();
foreach(var item in bomList)
{
var mat = matList.Find(x=>x.MatCode==item.MatCode);
decimal need = item.UnitQty*planQty*(1+item.LossRate);
res.Add(new BomChartDto()
{
MatName = mat.MatName,
Need = need,
Stock = mat.StockQty
});
}
return res;
}
/// <summary>更新工位任务状态(分执行端)</summary>
public static bool UpdateTaskStatus(int tid,string stat)
{
return Db.Updateable<T12_ProdTask>().Set(x=>x.Status,stat).Where(x=>x.Id==tid).ExecuteCommand()>0;
}
}
}
四、组支轴全局事件总线(模块解耦、跨工作台通信)
csharp
运行
using System;
namespace MES3C_Digital
{
public static class MesEventBus
{
//日志输出
public delegate void LogMsg(string msg);
public static event LogMsg OnLog;
public static void FireLog(string msg)=>OnLog?.Invoke($"{DateTime.Now:HH:mm:ss} | {msg}");
//全局UI刷新
public delegate void UiRefresh();
public static event UiRefresh OnUiRefresh;
public static void FireRefresh()=>OnUiRefresh?.Invoke();
}
}
五、异步任务管理器(Task 多线程后台计算 BOM,不阻塞 UI)
csharp
运行
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MES3C_Digital
{
public class BomTaskRunner
{
//异步核算BOM需求
public async Task<List<BomChartDto>> RunCalcAsync(string targetCode,int planQty)
{
return await Task.Run(()=>
{
var data = DbHelper.CalcBomNeed(targetCode,planQty);
MesEventBus.FireLog($"BOM需求核算完成:{targetCode},投产:{planQty}");
return data;
});
}
//异步变更产线任务状态
public async Task ChangeTaskStateAsync(int taskId,string status)
{
await Task.Run(()=>
{
DbHelper.UpdateTaskStatus(taskId,status);
MesEventBus.FireLog($"任务ID:{taskId}→{status}");
MesEventBus.FireRefresh();
});
}
}
}
六、标准化 GDI 复合控件【柱状图 + 缺料仪表盘一体】(可复用标准控件)
csharp
运行
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace MES3C_Digital
{
public class UcBomChart : Control
{
private List<BomChartDto> _source = new List<BomChartDto>();
public List<BomChartDto> DataSource { set { _source = value;Invalidate(); } }
private readonly Font _fontNorm = new Font("微软雅黑",9);
private readonly Font _fontTitle = new Font("微软雅黑",12,FontStyle.Bold);
public UcBomChart()
{
DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.UserPaint,true);
BackColor = Color.White;
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(BackColor);
if(_source.Count==0)
{
g.DrawString("暂无BOM需求数据",_fontTitle,Brushes.Gray,Width/2-80,Height/2);
return;
}
//左70%柱状,右30%仪表盘
Rectangle barArea = new Rectangle(30,40,(int)(Width*0.67),Height-90);
Rectangle gaugeArea = new Rectangle((int)(Width*0.7),40,(int)(Width*0.27),Height-90);
DrawBar(g,barArea);
DrawGauge(g,gaugeArea);
DrawTitle(g);
}
//双列柱状:需求/库存
void DrawBar(Graphics g,Rectangle rect)
{
int pad = 45;
int barW =32;
int gap=35;
decimal maxVal=0;
foreach(var d in _source) maxVal=Math.Max(maxVal,Math.Max(d.Need,d.Stock));
float scale = (rect.Height-pad)/(float)maxVal;
//坐标轴
g.DrawLine(Pens.Black,rect.Left+pad,rect.Bottom-pad,rect.Right-15,rect.Bottom-pad);
g.DrawLine(Pens.Black,rect.Left+pad,rect.Top+15,rect.Left+pad,rect.Bottom-pad);
int xStart = rect.Left+pad+10;
for(int i=0;i<_source.Count;i++)
{
var item = _source[i];
float hNeed = (float)(item.Need*scale);
float hStk = (float)(item.Stock*scale);
//需求蓝柱
using(var b1=new SolidBrush(Color.FromArgb(40,130,255)))
{
g.FillRectangle(b1,xStart,rect.Bottom-pad-hNeed,barW,hNeed);
g.DrawRectangle(Pens.DarkBlue,xStart,rect.Bottom-pad-hNeed,barW,hNeed);
}
//库存绿柱
using(var b2=new SolidBrush(Color.FromArgb(50,180,90)))
{
g.FillRectangle(b2,xStart+barW+6,rect.Bottom-pad-hStk,barW,hStk);
g.DrawRectangle(Pens.DarkGreen,xStart+barW+6,rect.Bottom-pad-hStk,barW,hStk);
}
g.DrawString(item.Need.ToString("N0"),_fontNorm,Brushes.Black,xStart,rect.Bottom-pad-hNeed-18);
g.DrawString(item.Stock.ToString("N0"),_fontNorm,Brushes.Black,xStart+barW+6,rect.Bottom-pad-hStk-18);
g.DrawString(item.MatName,_fontNorm,Brushes.Black,xStart,rect.Bottom-pad+5);
xStart += barW*2+gap;
}
//图例
g.FillRectangle(Brushes.RoyalBlue,rect.Right-110,rect.Top+10,14,14);
g.DrawString("总需求",_fontNorm,Brushes.Black,rect.Right-90,rect.Top+8);
g.FillRectangle(Brushes.MediumSeaGreen,rect.Right-110,rect.Top+32,14,14);
g.DrawString("现有库存",_fontNorm,Brushes.Black,rect.Right-90,rect.Top+30);
}
//缺料率半圆仪表盘
void DrawGauge(Graphics g,Rectangle rect)
{
decimal avgShortRate=0;
foreach(var d in _source)
{
decimal shortQty=Math.Max(0,d.Need-d.Stock);
avgShortRate += d.Need==0?0:shortQty/d.Need;
}
avgShortRate /= _source.Count;
int cx = rect.X+rect.Width/2;
int cy = rect.Y+rect.Height/2;
int r = Math.Min(rect.Width,rect.Height)/2-12;
g.DrawArc(new Pen(Color.LightGray,22),cx-r,cy-r,r*2,r*2,135,270);
Pen progressPen = new Pen(avgShortRate>0.3m?Color.OrangeRed:Color.LimeGreen,22);
g.DrawArc(progressPen,cx-r+8,cy-r+8,(r-8)*2,(r-8)*2,135,(float)(270*avgShortRate));
progressPen.Dispose();
var str = $"平均缺料:{avgShortRate:P1}";
var sz = g.MeasureString(str,_fontTitle);
g.DrawString(str,_fontTitle,Brushes.DarkRed,cx-sz.Width/2,cy+20);
}
void DrawTitle(Graphics g)
{
string t = "3C数码BOM物料需求&库存汇总报表";
var s = g.MeasureString(t,_fontTitle);
g.DrawString(t,_fontTitle,Brushes.Black,(Width-s.Width)/2,6);
}
protected override void Dispose(bool disposing)
{
if(disposing) { _fontNorm.Dispose();_fontTitle.Dispose(); }
base.Dispose(disposing);
}
}
}
七、四大主题工作台(UserControl)+ 主窗体(顶部菜单联合控制)
csharp
运行
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
namespace MES3C_Digital
{
public partial class FrmMain : Form
{
private readonly BomTaskRunner _runner = new BomTaskRunner();
private readonly UcBomChart _chart = new UcBomChart();
private readonly TabControl _tabMain = new TabControl();
private TextBox _txtLog;
public FrmMain()
{
DbHelper.InitDb();
InitTopMenu();
InitAllWorkBench();
BindGlobalEvent();
Text = "3C数码MES/ERP BOM协同系统 |10/11/12分组数据库";
WindowState = FormWindowState.Maximized;
}
#region 顶部联合控制菜单
void InitTopMenu()
{
MenuStrip ms = new MenuStrip();
var mTask = new ToolStripMenuItem("线边分执行端");
var mBom = new ToolStripMenuItem("BOM需求汇总工作台");
var mData = new ToolStripMenuItem("10/11/12数据维护");
var mLog = new ToolStripMenuItem("系统日志");
mTask.Click += (s, e)=>_tabMain.SelectedIndex=0;
mBom.Click += (s, e)=>_tabMain.SelectedIndex=1;
mData.Click += (s, e)=>_tabMain.SelectedIndex=2;
mLog.Click += (s, e)=>_tabMain.SelectedIndex=3;
ms.Items.AddRange(new[] {mTask,mBom,mData,mLog});
Controls.Add(ms);
}
#endregion
#region 四大主题工作台
void InitAllWorkBench()
{
_tabMain.Dock = DockStyle.Fill;
Controls.Add(_tabMain);
//Tab1:【组动力件分执行端】产线任务工作台
var tabExec = new TabPage("线边分执行端");
DataGridView dgvTask = new DataGridView{Dock=DockStyle.Fill};
Panel pTop = new Panel{Dock=DockStyle.Top,Height=60};
Button btnStart = new Button{Text="投产开工",Left=15,Top=12,Width=110};
Button btnFinish = new Button{Text="完工入库",Left=135,Top=12,Width=110};
pTop.Controls.AddRange(new[]{btnStart,btnFinish});
tabExec.Controls.Add(dgvTask);
tabExec.Controls.Add(pTop);
dgvTask.DataSource = DbHelper.Db.Queryable<T12_ProdTask>().ToDataTable();
//Tab2:【BOM需求汇总工作台】核心图表工作台
var tabBomSum = new TabPage("BOM需求汇总工作台");
Panel pOpt = new Panel{Dock=DockStyle.Top,Height=60};
ComboBox cboPro = new ComboBox{Left=12,Top=15,Width=210};
TextBox txtQty = new TextBox{Left=230,Top=15,Width=90,Text="12000"};
Button btnCalc = new Button{Text="一键核算BOM需求",Left=330,Top=12,Width=130};
cboPro.Items.AddRange(new[]{"SEMI001-蓝牙主控模组","SEMI002-手环传感模组"});
cboPro.SelectedIndex=0;
pOpt.Controls.AddRange(new[]{cboPro,txtQty,btnCalc});
_chart.Dock = DockStyle.Fill;
tabBomSum.Controls.Add(pOpt);
tabBomSum.Controls.Add(_chart);
//Tab3:10/11/12分组数据维护
var tabData = new TabPage("10/11/12后台数据维护");
DataGridView dgvData = new DataGridView{Dock=DockStyle.Fill};
ComboBox cboGroup = new ComboBox{Dock=DockStyle.Top};
cboGroup.Items.AddRange(new[]{"10组-物料库","11组-产线工站","12组-BOM&任务"});
cboGroup.SelectedIndex=0;
tabData.Controls.Add(dgvData);
tabData.Controls.Add(cboGroup);
cboGroup.SelectedIndexChanged+=(s,e)=>
{
if(cboGroup.Text.Contains("10组")) dgvData.DataSource=DbHelper.Db.Queryable<T10_Material>().ToDataTable();
else if(cboGroup.Text.Contains("11组")) dgvData.DataSource=DbHelper.Db.Queryable<T11_Station>().ToDataTable();
else dgvData.DataSource=DbHelper.Db.Queryable<T12_BomItem>().ToDataTable();
};
//Tab4:系统日志台
var tabLog = new TabPage("系统日志");
_txtLog = new TextBox{Dock=DockStyle.Fill,Multiline=true,ReadOnly=true,BackColor=Color.Black,ForeColor=Color.Lime};
tabLog.Controls.Add(_txtLog);
_tabMain.TabPages.AddRange(new[]{tabExec,tabBomSum,tabData,tabLog});
//绑定按钮事件
btnCalc.Click+=async(s,e)=>
{
string code = cboPro.Text.Split('-')[0];
var res = await _runner.RunCalcAsync(code,int.Parse(txtQty.Text));
_chart.DataSource = res;
};
btnStart.Click +=(s,e)=>
{
if(dgvTask.CurrentRow==null) {MessageBox.Show("选中任务行");return;}
int tid = Convert.ToInt32(dgvTask.CurrentRow["Id"].Value);
_ = _runner.ChangeTaskStateAsync(tid,"生产中");
};
btnFinish.Click +=(s,e)=>
{
if(dgvTask.CurrentRow==null) return;
int tid = Convert.ToInt32(dgvTask.CurrentRow["Id"].Value);
_ = _runner.ChangeTaskStateAsync(tid,"已完工");
};
//UI刷新后重载表格
MesEventBus.OnUiRefresh+=()=>
{
Invoke(new Action(()=>
{
dgvTask.DataSource=DbHelper.Db.Queryable<T12_ProdTask>().ToDataTable();
}));
};
}
#endregion
//绑定全局事件(日志输出)
void BindGlobalEvent()
{
MesEventBus.OnLog+=(msg)=>Invoke(new Action(()=>_txtLog.AppendText(msg+Environment.NewLine)));
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
}
}
八、整体功能对标需求清单
表格
| 需求项 | 实现说明 |
|---|---|
| 3C 数码小件 BOM 协同 | 耳机 / 手环 / 阻容 / 芯片全品类 BOM 自动拆料算需求,含损耗 |
| 10/11/12 分组数据库 | 10 物料、11 产线工站工序、12BOM 任务需求,SqlSugar 自动建库 |
| 联合控制端 + 主题工作台 | 顶部菜单一键切换 4 大 Tab 工作台 |
| 组支轴事件总线 | 全局事件解耦,日志 / UI 刷新统一分发 |
| 任务线程 BOM 组装计算 | Task 异步后台核算,不卡死 UI |
| 组动力件分执行端 | 独立工作台:开工 / 完工修改生产任务状态 |
| BOM 汇总主题工作台 | 选半成品 + 填投产数→自动算需求→GDI 图表展示 |
| 标准丰富 GDI 控件 | 双柱状(需求 / 库存)+ 半圆缺料仪表盘、抗锯齿、图例、数值标注 |
| 本地文件数据库 | SQLite 单文件,无需安装数据库服务 |
九、运行步骤
- 新建.NET Framework4.8 Winform 项目
- 安装 SqlSugarCore+System.Data.SQLite
- 全量粘贴代码,命名统一命名空间
MES3C_Digital - 直接运行,自动在 exe 同目录生成
MES3C_Digital.db本地数据库,预置 10/11/12 组初始化业务数据