以下是基于 MES/ERP 工序 BOM 协同场景的完整 WinForm 模块化实现代码,涵盖工序路径、BOM 物料、主子调度台、GDI 图表报表、本地 SQLite 数据维护等核心功能,同时强化 GDI 图表的标准化与丰富性,支持 8-9 组后台数据的本地维护编辑:




一、核心基础层:LocalDB(增强数据维护能力)
csharp
运行
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Windows.Forms;
public static class LocalDB
{
private static string dbFile = Path.Combine(Application.StartupPath, "MESData.db");
public static string Conn => $"Data Source={dbFile};Version=3;";
#region 初始化(含基础表+8-9组测试数据)
public static void Init()
{
if (!File.Exists(dbFile)) SQLiteConnection.CreateFile(dbFile);
using (var conn = new SQLiteConnection(Conn))
{
conn.Open();
// 核心表结构(工序、路径、BOM、任务、物料、产线、资源、人员、设备 共9组核心数据)
new SQLiteCommand($@"
CREATE TABLE IF NOT EXISTS WorkStation(Id INTEGER PRIMARY KEY,Name TEXT,Role TEXT,Remark TEXT);
CREATE TABLE IF NOT EXISTS ProcessRoute(Id INTEGER PRIMARY KEY,Name TEXT,StationId INT,Sort INT,IsEnabled BIT);
CREATE TABLE IF NOT EXISTS BOM(Id INTEGER PRIMARY KEY,ProductCode TEXT,Material TEXT,Spec TEXT,UseQty DECIMAL,Loss DECIMAL,Unit TEXT);
CREATE TABLE IF NOT EXISTS WorkTask(Id INTEGER PRIMARY KEY,TaskNo TEXT,StationId INT,StationName TEXT,Status TEXT,PlanQty INT,ActualQty INT,CreateTime DATETIME);
CREATE TABLE IF NOT EXISTS Material(Id INTEGER PRIMARY KEY,Code TEXT,Name TEXT,StockQty INT,Unit TEXT,Supplier TEXT);
CREATE TABLE IF NOT EXISTS ProductionLine(Id INTEGER PRIMARY KEY,LineCode TEXT,LineName TEXT,StationIds TEXT,Manager TEXT);
CREATE TABLE IF NOT EXISTS Resource(Id INTEGER PRIMARY KEY,ResType TEXT,ResName TEXT,StationId INT,Qty INT,Status TEXT);
CREATE TABLE IF NOT EXISTS Employee(Id INTEGER PRIMARY KEY,EmpNo TEXT,Name TEXT,StationId INT,Role TEXT,Phone TEXT);
CREATE TABLE IF NOT EXISTS Equipment(Id INTEGER PRIMARY KEY,EqCode TEXT,Name TEXT,StationId INT,Status TEXT,MaintainDate DATETIME);
", conn).ExecuteNonQuery();
}
InitSampleData(); // 初始化9组测试数据
}
private static void InitSampleData()
{
// 清空旧数据
Execute("DELETE FROM WorkStation");
Execute("DELETE FROM ProcessRoute");
Execute("DELETE FROM BOM");
Execute("DELETE FROM WorkTask");
Execute("DELETE FROM Material");
Execute("DELETE FROM ProductionLine");
Execute("DELETE FROM Resource");
Execute("DELETE FROM Employee");
Execute("DELETE FROM Equipment");
// 1. 工站数据
Execute("INSERT INTO WorkStation VALUES(1,'SMT工站','操作员','表面贴装')");
Execute("INSERT INTO WorkStation VALUES(2,'QC工站','质检员','质量检测')");
Execute("INSERT INTO WorkStation VALUES(3,'包装工站','包装员','成品包装')");
Execute("INSERT INTO WorkStation VALUES(4,'测试工站','测试员','功能测试')");
Execute("INSERT INTO WorkStation VALUES(5,'插件工站','操作员','元件插件')");
Execute("INSERT INTO WorkStation VALUES(6,'焊工站','技术员','焊接加工')");
// 2. 工序路径
Execute("INSERT INTO ProcessRoute VALUES(1,'SMT贴片',1,1,1)");
Execute("INSERT INTO ProcessRoute VALUES(2,'元件插件',5,2,1)");
Execute("INSERT INTO ProcessRoute VALUES(3,'焊接',6,3,1)");
Execute("INSERT INTO ProcessRoute VALUES(4,'QC检测',2,4,1)");
Execute("INSERT INTO ProcessRoute VALUES(5,'功能测试',4,5,1)");
Execute("INSERT INTO ProcessRoute VALUES(6,'成品包装',3,6,1)");
// 3. BOM物料
Execute("INSERT INTO BOM VALUES(1,'PROD001','PCB主板','FR-4',1,0.03,'片')");
Execute("INSERT INTO BOM VALUES(2,'PROD001','主控芯片','STM32F103',2,0.02,'颗')");
Execute("INSERT INTO BOM VALUES(3,'PROD001','电容','0805 100uF',5,0.01,'个')");
Execute("INSERT INTO BOM VALUES(4,'PROD002','外壳','ABS材质',1,0.02,'套')");
Execute("INSERT INTO BOM VALUES(5,'PROD002','显示屏','2.4寸TFT',1,0.01,'片')");
Execute("INSERT INTO BOM VALUES(6,'PROD002','电池','3.7V 2000mAh',1,0.005,'节')");
// 4. 工作任务
Execute("INSERT INTO WorkTask VALUES(1,'T001',1,'SMT工站','待执行',100,0,'2026-01-01 08:00:00')");
Execute("INSERT INTO WorkTask VALUES(2,'T002',5,'插件工站','执行中',150,80,'2026-01-01 09:00:00')");
Execute("INSERT INTO WorkTask VALUES(3,'T003',6,'焊工站','待执行',200,0,'2026-01-01 10:00:00')");
Execute("INSERT INTO WorkTask VALUES(4,'T004',2,'QC工站','已完成',180,180,'2026-01-01 11:00:00')");
Execute("INSERT INTO WorkTask VALUES(5,'T005',4,'测试工站','执行中',120,90,'2026-01-01 14:00:00')");
Execute("INSERT INTO WorkTask VALUES(6,'T006',3,'包装工站','已完成',200,200,'2026-01-01 15:00:00')");
// 5. 物料库存
Execute("INSERT INTO Material VALUES(1,'MAT001','PCB主板',500,'片','深圳XX电子')");
Execute("INSERT INTO Material VALUES(2,'MAT002','主控芯片',1000,'颗','上海XX半导体')");
Execute("INSERT INTO Material VALUES(3,'MAT003','电容',5000,'个','东莞XX电子')");
Execute("INSERT INTO Material VALUES(4,'MAT004','外壳',300,'套','惠州XX塑胶')");
Execute("INSERT INTO Material VALUES(5,'MAT005','显示屏',200,'片','广州XX显示')");
// 6. 产线数据
Execute("INSERT INTO ProductionLine VALUES(1,'LINE001','主线1','1,5,6,2,4,3','张三')");
Execute("INSERT INTO ProductionLine VALUES(2,'LINE002','主线2','1,5,2,4,3','李四')");
// 7. 资源数据
Execute("INSERT INTO Resource VALUES(1,'治具','SMT吸嘴',1,50,'正常')");
Execute("INSERT INTO Resource VALUES(2,'工具','电烙铁',6,20,'正常')");
Execute("INSERT INTO Resource VALUES(3,'治具','测试夹具',4,15,'待维修')");
// 8. 员工数据
Execute("INSERT INTO Employee VALUES(1,'EMP001','张三',1,'操作员','13800138000')");
Execute("INSERT INTO Employee VALUES(2,'EMP002','李四',2,'质检员','13900139000')");
Execute("INSERT INTO Employee VALUES(3,'EMP003','王五',6,'技术员','13700137000')");
// 9. 设备数据
Execute("INSERT INTO Equipment VALUES(1,'EQ001','SMT贴片机',1,'运行中','2026-02-01')");
Execute("INSERT INTO Equipment VALUES(2,'EQ002','AOI检测仪',2,'待机','2026-01-15')");
Execute("INSERT INTO Equipment VALUES(3,'EQ003','焊接机',6,'维护中','2026-01-20')");
}
#endregion
#region 通用数据库操作(查询/增删改)
public static DataTable Query(string sql)
{
var dt = new DataTable();
using (var adp = new SQLiteDataAdapter(sql, Conn)) adp.Fill(dt);
return dt;
}
public static int Execute(string sql)
{
using (var conn = new SQLiteConnection(Conn))
{
conn.Open();
return new SQLiteCommand(sql, conn).ExecuteNonQuery();
}
}
// 新增数据(通用方法)
public static int Insert(string table, params (string col, object val)[] columns)
{
var cols = string.Join(",", columns.Select(c => c.col));
var vals = string.Join(",", columns.Select(c => $"'{c.val}'"));
return Execute($"INSERT INTO {table} ({cols}) VALUES ({vals})");
}
// 修改数据(通用方法)
public static int Update(string table, int id, params (string col, object val)[] columns)
{
var setStr = string.Join(",", columns.Select(c => $"{c.col}='{c.val}'"));
return Execute($"UPDATE {table} SET {setStr} WHERE Id={id}");
}
// 删除数据(通用方法)
public static int Delete(string table, int id)
{
return Execute($"DELETE FROM {table} WHERE Id={id}");
}
#endregion
}
二、可视化层:增强版 GdiChart(标准化 + 丰富性)
csharp
运行
using System;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class GdiChart : Panel
{
// 可配置属性
public DataTable DataSource { get; set; }
public string XAxisName { get; set; } = "工站/物料";
public string YAxisName { get; set; } = "数量";
public Color[] BarColors { get; set; } = { Brushes.CornflowerBlue.Color, Brushes.LightSalmon.Color, Brushes.LightGreen.Color, Brushes.Orange.Color, Brushes.Purple.Color, Brushes.Teal.Color };
public Font TitleFont { get; set; } = new Font("微软雅黑", 12, FontStyle.Bold);
public Font AxisFont { get; set; } = new Font("微软雅黑", 9);
public Font LabelFont { get; set; } = new Font("Consolas", 10);
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (DataSource == null || DataSource.Rows.Count == 0) return;
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias; // 抗锯齿
g.Clear(BackColor);
// 1. 绘制标题
var titleSize = g.MeasureString($"{XAxisName}-{YAxisName}统计", TitleFont);
g.DrawString($"{XAxisName}-{YAxisName}统计", TitleFont, Brushes.Black, (ClientSize.Width - titleSize.Width) / 2, 10);
// 2. 定义绘图区域(留边距)
int marginLeft = 80, marginRight = 20, marginTop = 50, marginBottom = 60;
int drawWidth = ClientSize.Width - marginLeft - marginRight;
int drawHeight = ClientSize.Height - marginTop - marginBottom;
// 3. 计算数据极值
int maxVal = 0;
foreach (DataRow row in DataSource.Rows)
{
if (int.TryParse(row[1].ToString(), out int val) && val > maxVal) maxVal = val;
}
maxVal = maxVal == 0 ? 100 : maxVal; // 避免除零
// 4. 绘制Y轴(含刻度+标注)
int yTickCount = 5; // Y轴刻度数
int yTickStep = maxVal / yTickCount;
for (int i = 0; i <= yTickCount; i++)
{
int y = marginTop + drawHeight - (i * drawHeight / yTickCount);
string tickText = (i * yTickStep).ToString();
var tickSize = g.MeasureString(tickText, AxisFont);
// 刻度线
g.DrawLine(Pens.Gray, marginLeft - 5, y, marginLeft, y);
// 刻度值
g.DrawString(tickText, AxisFont, Brushes.Gray, marginLeft - 5 - tickSize.Width, y - tickSize.Height / 2);
}
// Y轴名称
g.RotateTransform(-90);
g.DrawString(YAxisName, AxisFont, Brushes.Black, -ClientSize.Height / 2, 20);
g.ResetTransform();
// 5. 绘制X轴(含刻度+标注)
int barCount = DataSource.Rows.Count;
int barWidth = Math.Min(70, drawWidth / (barCount * 2)); // 自适应柱宽
int barSpacing = drawWidth / (barCount + 1); // 柱间距
for (int i = 0; i < barCount; i++)
{
int x = marginLeft + (i + 1) * barSpacing - barWidth / 2;
string xLabel = DataSource.Rows[i][0].ToString();
var labelSize = g.MeasureString(xLabel, AxisFont);
// 刻度线
g.DrawLine(Pens.Gray, x + barWidth / 2, ClientSize.Height - marginBottom + 5, x + barWidth / 2, ClientSize.Height - marginBottom);
// X轴标签(自动换行/截断)
string showLabel = xLabel.Length > 6 ? $"{xLabel.Substring(0, 6)}..." : xLabel;
g.DrawString(showLabel, AxisFont, Brushes.DarkRed, x - labelSize.Width / 2, ClientSize.Height - marginBottom + 10);
}
// X轴名称
var xAxisSize = g.MeasureString(XAxisName, AxisFont);
g.DrawString(XAxisName, AxisFont, Brushes.Black, (ClientSize.Width - xAxisSize.Width) / 2, ClientSize.Height - marginBottom + 30);
// 6. 绘制柱状图(多颜色+边框+数值标注)
for (int i = 0; i < barCount; i++)
{
int val = int.Parse(DataSource.Rows[i][1].ToString());
int barHeight = val * drawHeight / maxVal;
int x = marginLeft + (i + 1) * barSpacing - barWidth / 2;
int y = marginTop + drawHeight - barHeight;
// 柱体填充(循环颜色)
using (var brush = new SolidBrush(BarColors[i % BarColors.Length]))
{
g.FillRectangle(brush, x, y, barWidth, barHeight);
}
// 柱体边框
g.DrawRectangle(Pens.Black, x, y, barWidth, barHeight);
// 柱顶数值标注
string valText = val.ToString();
var valSize = g.MeasureString(valText, LabelFont);
g.DrawString(valText, LabelFont, Brushes.Black, x + barWidth / 2 - valSize.Width / 2, y - valSize.Height - 5);
}
// 7. 绘制图例
int legendX = ClientSize.Width - marginRight - 150;
int legendY = marginTop;
g.DrawRectangle(Pens.Gray, legendX, legendY, 140, barCount * 20);
g.DrawString("图例", AxisFont, Brushes.Black, legendX + 5, legendY);
for (int i = 0; i < Math.Min(barCount, 6); i++)
{
string itemText = DataSource.Rows[i][0].ToString();
itemText = itemText.Length > 8 ? $"{itemText.Substring(0, 8)}..." : itemText;
g.FillRectangle(new SolidBrush(BarColors[i]), legendX + 10, legendY + 20 + i * 20, 15, 15);
g.DrawString(itemText, AxisFont, Brushes.Black, legendX + 30, legendY + 20 + i * 20);
}
// 日志输出
DashboardView._logBox?.AppendText($"{DateTime.Now:HH:mm:ss} 柱状图绘制完成:{barCount}个维度,最大值{maxVal} \r\n");
}
// 重载刷新方法
public new void Refresh()
{
Invalidate();
base.Refresh();
}
// 导出图表为图片
public void ExportToImage(string path)
{
using (var bmp = new Bitmap(ClientSize.Width, ClientSize.Height))
{
using (var g = Graphics.FromImage(bmp))
{
OnPaint(new PaintEventArgs(g, new Rectangle(0, 0, bmp.Width, bmp.Height)));
}
bmp.Save(path, System.Drawing.Imaging.ImageFormat.Png);
DashboardView._logBox?.AppendText($"{DateTime.Now:HH:mm:ss} 图表已导出至:{path} \r\n");
}
}
}
三、业务视图层:工序路径管理组件
csharp
运行
using System;
using System.Data;
using System.Windows.Forms;
public static class ProcessRouteView
{
public static TabPage Create()
{
var page = new TabPage("工序路径管理");
var split = new SplitContainer { Dock = DockStyle.Fill, Orientation = Orientation.Horizontal, SplitterDistance = 80 };
// 顶部操作区
var pnlTop = new Panel { Dock = DockStyle.Fill };
var btnAdd = new Button { Text = "新增工序", Dock = DockStyle.Left, Width = 100 };
var btnEdit = new Button { Text = "编辑工序", Dock = DockStyle.Left, Width = 100 };
var btnDel = new Button { Text = "删除工序", Dock = DockStyle.Left, Width = 100 };
var btnRefresh = new Button { Text = "刷新", Dock = DockStyle.Left, Width = 80 };
pnlTop.Controls.Add(btnAdd);
pnlTop.Controls.Add(btnEdit);
pnlTop.Controls.Add(btnDel);
pnlTop.Controls.Add(btnRefresh);
split.Panel1.Controls.Add(pnlTop);
// 底部数据展示区
var dgv = new DataGridView { Dock = DockStyle.Fill, AutoGenerateColumns = false };
// 配置列
dgv.Columns.AddRange(new[]
{
new DataGridViewTextBoxColumn { Name = "Id", HeaderText = "ID", DataPropertyName = "Id", Width = 60 },
new DataGridViewTextBoxColumn { Name = "Name", HeaderText = "工序名称", DataPropertyName = "Name", Width = 150 },
new DataGridViewTextBoxColumn { Name = "StationId", HeaderText = "工站ID", DataPropertyName = "StationId", Width = 80 },
new DataGridViewTextBoxColumn { Name = "Sort", HeaderText = "排序", DataPropertyName = "Sort", Width = 80 },
new DataGridViewCheckBoxColumn { Name = "IsEnabled", HeaderText = "启用", DataPropertyName = "IsEnabled", Width = 80 }
});
split.Panel2.Controls.Add(dgv);
page.Controls.Add(split);
// 加载数据
Action loadData = () => dgv.DataSource = LocalDB.Query("SELECT * FROM ProcessRoute ORDER BY Sort");
loadData();
// 新增工序
btnAdd.Click += (s, e) =>
{
var frm = new ProcessRouteEditForm(null);
if (frm.ShowDialog() == DialogResult.OK)
{
loadData();
DashboardView._logBox.AppendText($"{DateTime.Now:HH:mm:ss} 新增工序路径完成 \r\n");
}
};
// 编辑工序
btnEdit.Click += (s, e) =>
{
if (dgv.CurrentRow == null) return;
int id = int.Parse(dgv.CurrentRow.Cells["Id"].Value.ToString());
var row = LocalDB.Query($"SELECT * FROM ProcessRoute WHERE Id={id}").Rows[0];
var frm = new ProcessRouteEditForm(row);
if (frm.ShowDialog() == DialogResult.OK)
{
loadData();
DashboardView._logBox.AppendText($"{DateTime.Now:HH:mm:ss} 编辑工序路径(ID:{id})完成 \r\n");
}
};
// 删除工序
btnDel.Click += (s, e) =>
{
if (dgv.CurrentRow == null) return;
int id = int.Parse(dgv.CurrentRow.Cells["Id"].Value.ToString());
if (MessageBox.Show($"确认删除ID为{id}的工序路径?", "提示", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
LocalDB.Delete("ProcessRoute", id);
loadData();
DashboardView._logBox.AppendText($"{DateTime.Now:HH:mm:ss} 删除工序路径(ID:{id})完成 \r\n");
}
};
// 刷新
btnRefresh.Click += (s, e) => loadData();
return page;
}
// 工序路径编辑窗体
public class ProcessRouteEditForm : Form
{
private readonly DataRow _row;
private TextBox txtName = new TextBox { Dock = DockStyle.Top, PlaceholderText = "工序名称" };
private TextBox txtStationId = new TextBox { Dock = DockStyle.Top, PlaceholderText = "工站ID" };
private TextBox txtSort = new TextBox { Dock = DockStyle.Top, PlaceholderText = "排序号" };
private CheckBox chkEnabled = new CheckBox { Text = "启用", Dock = DockStyle.Top };
private Button btnSave = new Button { Text = "保存", Dock = DockStyle.Bottom };
private Button btnCancel = new Button { Text = "取消", Dock = DockStyle.Bottom };
public ProcessRouteEditForm(DataRow row)
{
_row = row;
Text = row == null ? "新增工序路径" : "编辑工序路径";
Size = new Size(300, 250);
StartPosition = FormStartPosition.CenterParent;
// 布局
var pnlMain = new Panel { Dock = DockStyle.Fill, Padding = new Padding(10) };
pnlMain.Controls.Add(txtName);
pnlMain.Controls.Add(txtStationId);
pnlMain.Controls.Add(txtSort);
pnlMain.Controls.Add(chkEnabled);
var pnlBtn = new Panel { Dock = DockStyle.Bottom, Height = 40 };
pnlBtn.Controls.Add(btnSave);
pnlBtn.Controls.Add(btnCancel);
Controls.Add(pnlMain);
Controls.Add(pnlBtn);
// 加载已有数据
if (row != null)
{
txtName.Text = row["Name"].ToString();
txtStationId.Text = row["StationId"].ToString();
txtSort.Text = row["Sort"].ToString();
chkEnabled.Checked = bool.Parse(row["IsEnabled"].ToString());
}
// 保存
btnSave.Click += (s, e) =>
{
if (string.IsNullOrWhiteSpace(txtName.Text))
{
MessageBox.Show("请输入工序名称");
return;
}
if (!int.TryParse(txtStationId.Text, out int stationId))
{
MessageBox.Show("工站ID必须为数字");
return;
}
if (!int.TryParse(txtSort.Text, out int sort))
{
MessageBox.Show("排序号必须为数字");
return;
}
if (_row == null)
{
// 新增
LocalDB.Insert("ProcessRoute",
("Name", txtName.Text),
("StationId", stationId),
("Sort", sort),
("IsEnabled", chkEnabled.Checked ? 1 : 0));
}
else
{
// 编辑
int id = int.Parse(_row["Id"].ToString());
LocalDB.Update("ProcessRoute", id,
("Name", txtName.Text),
("StationId", stationId),
("Sort", sort),
("IsEnabled", chkEnabled.Checked ? 1 : 0));
}
DialogResult = DialogResult.OK;
Close();
};
// 取消
btnCancel.Click += (s, e) => Close();
}
}
}
四、业务视图层:BOM 物料维护组件(增强版)
csharp
运行
using System;
using System.Data;
using System.Windows.Forms;
public static class BomView
{
public static TabPage Create()
{
var page = new TabPage("BOM物料调配");
var split = new SplitContainer { Dock = DockStyle.Fill, Orientation = Orientation.Horizontal, SplitterDistance = 100 };
// 顶部操作区
var pnlTop = new Panel { Dock = DockStyle.Fill };
var cboProduct = new ComboBox { Dock = DockStyle.Left, Width = 150, DropDownStyle = ComboBoxStyle.DropDownList };
var btnCalc = new Button { Text = "计算产量需求", Dock = DockStyle.Left, Width = 120 };
var txtQty = new TextBox { Dock = DockStyle.Left, Width = 100, PlaceholderText = "输入产量(如1000)" };
var btnEditBom = new Button { Text = "维护BOM数据", Dock = DockStyle.Left, Width = 120 };
var btnExport = new Button { Text = "导出BOM", Dock = DockStyle.Left, Width = 100 };
pnlTop.Controls.Add(cboProduct);
pnlTop.Controls.Add(txtQty);
pnlTop.Controls.Add(btnCalc);
pnlTop.Controls.Add(btnEditBom);
pnlTop.Controls.Add(btnExport);
split.Panel1.Controls.Add(pnlTop);
// 底部数据展示区
var dgv = new DataGridView { Dock = DockStyle.Fill, AutoGenerateColumns = false };
dgv.Columns.AddRange(new[]
{
new DataGridViewTextBoxColumn { Name = "Id", HeaderText = "ID", DataPropertyName = "Id", Width = 60 },
new DataGridViewTextBoxColumn { Name = "ProductCode", HeaderText = "产品编码", DataPropertyName = "ProductCode", Width = 100 },
new DataGridViewTextBoxColumn { Name = "Material", HeaderText = "物料名称", DataPropertyName = "Material", Width = 150 },
new DataGridViewTextBoxColumn { Name = "Spec", HeaderText = "规格", DataPropertyName = "Spec", Width = 120 },
new DataGridViewTextBoxColumn { Name = "UseQty", HeaderText = "单用量", DataPropertyName = "UseQty", Width = 80 },
new DataGridViewTextBoxColumn { Name = "Loss", HeaderText = "损耗率", DataPropertyName = "Loss", Width = 80 },
new DataGridViewTextBoxColumn { Name = "Unit", HeaderText = "单位", DataPropertyName = "Unit", Width = 60 },
new DataGridViewTextBoxColumn { Name = "TotalNeed", HeaderText = "总需求", DataPropertyName = "TotalNeed", Width = 100, DefaultCellStyle = { ForeColor = Color.Red } }
});
split.Panel2.Controls.Add(dgv);
page.Controls.Add(split);
// 加载产品编码
var productDt = LocalDB.Query("SELECT DISTINCT ProductCode FROM BOM");
cboProduct.Items.AddRange(productDt.Rows.Cast<DataRow>().Select(r => r["ProductCode"].ToString()).ToArray());
if (cboProduct.Items.Count > 0) cboProduct.SelectedIndex = 0;
// 计算产量需求
btnCalc.Click += (s, e) =>
{
if (cboProduct.SelectedItem == null || string.IsNullOrWhiteSpace(txtQty.Text))
{
MessageBox.Show("请选择产品并输入产量");
return;
}
if (!int.TryParse(txtQty.Text, out int qty))
{
MessageBox.Show("产量必须为数字");
return;
}
var product = cboProduct.SelectedItem.ToString();
var dt = LocalDB.Query($"SELECT * FROM BOM WHERE ProductCode='{product}'");
if (!dt.Columns.Contains("TotalNeed"))
{
dt.Columns.Add("TotalNeed", typeof(decimal));
}
foreach (DataRow r in dt.Rows)
{
decimal useQty = decimal.Parse(r["UseQty"].ToString());
decimal loss = decimal.Parse(r["Loss"].ToString());
decimal total = BomComponent.Calc(qty, useQty, loss);
r["TotalNeed"] = total;
}
dgv.DataSource = dt;
DashboardView._logBox.AppendText($"{DateTime.Now:HH:mm:ss} 产品[{product}]产量[{qty}]的BOM需求计算完成 \r\n");
};
// 维护BOM数据
btnEditBom.Click += (s, e) =>
{
var frm = new BomEditForm();
frm.ShowDialog();
// 重新加载产品编码
cboProduct.Items.Clear();
var newProductDt = LocalDB.Query("SELECT DISTINCT ProductCode FROM BOM");
cboProduct.Items.AddRange(newProductDt.Rows.Cast<DataRow>().Select(r => r["ProductCode"].ToString()).ToArray());
if (cboProduct.Items.Count > 0) cboProduct.SelectedIndex = 0;
};
// 导出BOM
btnExport.Click += (s, e) =>
{
if (dgv.DataSource == null) return;
var dt = (DataTable)dgv.DataSource;
using (var sfd = new SaveFileDialog { Filter = "Excel文件|*.xlsx|CSV文件|*.csv", Title = "导出BOM数据" })
{
if (sfd.ShowDialog() == DialogResult.OK)
{
// 简化版导出(实际可使用NPOI/EPPlus)
if (sfd.FilterIndex == 2)
{
var lines = new[] { string.Join(",", dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName)) }
.Concat(dt.Rows.Cast<DataRow>().Select(r => string.Join(",", r.ItemArray)));
System.IO.File.WriteAllLines(sfd.FileName, lines);
}
DashboardView._logBox.AppendText($"{DateTime.Now:HH:mm:ss} BOM数据已导出至:{sfd.FileName} \r\n");
}
}
};
return page;
}
// BOM编辑窗体
public class BomEditForm : Form
{
private DataGridView dgv = new DataGridView { Dock = DockStyle.Fill, AutoGenerateColumns = false };
private Button btnAdd = new Button { Text = "新增", Dock = DockStyle.Bottom, Width = 80 };
private Button btnDel = new Button { Text = "删除", Dock = DockStyle.Bottom, Width = 80 };
private Button btnSave = new Button { Text = "保存", Dock = DockStyle.Bottom, Width = 80 };
public BomEditForm()
{
Text = "BOM物料维护";
Size = new Size(800, 400);
StartPosition = FormStartPosition.CenterParent;
// 配置列
dgv.Columns.AddRange(new[]
{
new DataGridViewTextBoxColumn { Name = "Id", HeaderText = "ID", DataPropertyName = "Id", Width = 60 },
new DataGridViewTextBoxColumn { Name = "ProductCode", HeaderText = "产品编码", DataPropertyName = "ProductCode", Width = 100 },
new DataGridViewTextBoxColumn { Name = "Material", HeaderText = "物料名称", DataPropertyName = "Material", Width = 150 },
new DataGridViewTextBoxColumn { Name = "Spec", HeaderText = "规格", DataPropertyName = "Spec", Width = 120 },
new DataGridViewTextBoxColumn { Name = "UseQty", HeaderText = "单用量", DataPropertyName = "UseQty", Width = 80 },
new DataGridViewTextBoxColumn { Name = "Loss", HeaderText = "损耗率", DataPropertyName = "Loss", Width = 80 },
new DataGridViewTextBoxColumn { Name = "Unit", HeaderText = "单位", DataPropertyName = "Unit", Width = 60 }
});
dgv.DataSource = LocalDB.Query("SELECT * FROM BOM");
// 布局
var pnlBtn = new Panel { Dock = DockStyle.Bottom, Height = 40 };
pnlBtn.Controls.Add(btnAdd);
pnlBtn.Controls.Add(btnDel);
pnlBtn.Controls.Add(btnSave);
Controls.Add(dgv);
Controls.Add(pnlBtn);
// 新增行
btnAdd.Click += (s, e) =>
{
var dt = (DataTable)dgv.DataSource;
var newRow = dt.NewRow();
newRow["UseQty"] = 1;
newRow["Loss"] = 0.01;
dt.Rows.Add(newRow);
dgv.CurrentCell = dgv.Rows[dgv.Rows.Count - 1].Cells["ProductCode"];
};
// 删除行
btnDel.Click += (s, e) =>
{
if (dgv.CurrentRow == null || dgv.CurrentRow.IsNewRow) return;
int id = int.TryParse(dgv.CurrentRow.Cells["Id"].Value?.ToString(), out int i) ? i : 0;
if (id > 0) LocalDB.Delete("BOM", id);
dgv.Rows.Remove(dgv.CurrentRow);
};
// 保存修改
btnSave.Click += (s, e) =>
{
var dt = (DataTable)dgv.DataSource;
foreach (DataRow row in dt.Rows)
{
if (row.RowState == DataRowState.Added)
{
LocalDB.Insert("BOM",
("ProductCode", row["ProductCode"]),
("Material", row["Material"]),
("Spec", row["Spec"]),
("UseQty", row["UseQty"]),
("Loss", row["Loss"]),
("Unit", row["Unit"]));
}
else if (row.RowState == DataRowState.Modified)
{
int id = int.Parse(row["Id"].ToString());
LocalDB.Update("BOM", id,
("ProductCode", row["ProductCode"]),
("Material", row["Material"]),
("Spec", row["Spec"]),
("UseQty", row["UseQty"]),
("Loss", row["Loss"]),
("Unit", row["Unit"]));
}
}
MessageBox.Show("保存成功");
Close();
};
}
}
}
五、核心整合层:MainForm(主窗体,组装所有模块)
csharp
运行
using System;
using System.Drawing;
using System.Windows.Forms;
public partial class MainForm : Form
{
private readonly GdiChart _chart = new GdiChart
{
XAxisName = "工站",
YAxisName = "计划产量",
BackColor = Color.White,
BarColors = new[] { Color.CornflowerBlue, Color.LightSalmon, Color.LightGreen, Color.Orange, Color.Purple, Color.Teal }
};
public MainForm()
{
LocalDB.Init(); // 初始化数据库+测试数据
InitializeComponent();
InitializeLayout();
BindEvents();
Text = "MES/ERP 工序BOM协同系统";
WindowState = FormWindowState.Maximized;
Icon = SystemIcons.Application;
}
private void InitializeComponent()
{
this.SuspendLayout();
this.Name = "MainForm";
this.Text = "MES/ERP 工序BOM协同系统";
this.Size = new Size(1200, 800);
this.ResumeLayout(false);
}
private void InitializeLayout()
{
var tabControl = new TabControl { Dock = DockStyle.Fill, Font = new Font("微软雅黑", 10) };
// 组装所有功能标签页
tabControl.TabPages.Add(TaskNodeView.Create()); // 分节点执行端
tabControl.TabPages.Add(BomView.Create()); // BOM物料调配
tabControl.TabPages.Add(ProcessRouteView.Create()); // 工序路径管理
tabControl.TabPages.Add(ChartView.Create(_chart)); // GDI图表报表
tabControl.TabPages.Add(MasterSlaveView.Create()); // 主子调度台
tabControl.TabPages.Add(DashboardView.Create()); // 汇总工作台
tabControl.TabPages.Add(DataMaintenanceView.Create()); // 数据维护中心
Controls.Add(tabControl);
}
private void BindEvents()
{
// 全局刷新事件
MesEventBus.OnRefreshUI += () =>
{
foreach (TabPage page in ((TabControl)Controls[0]).TabPages)
{
foreach (Control c in page.Controls)
{
if (c is DataGridView dgv) dgv.DataSource = null;
}
// 重新加载各页面数据(简化版,实际可按需优化)
if (page == ((TabControl)Controls[0]).TabPages["分节点工作端"])
{
TaskNodeView._dgv.DataSource = TaskComponent.GetTasks();
}
}
_chart.Refresh();
};
}
}
// 通用数据维护视图(管理9组核心数据)
public static class DataMaintenanceView
{
public static TabPage Create()
{
var page = new TabPage("数据维护中心");
var cboTable = new ComboBox { Dock = DockStyle.Top, DropDownStyle = ComboBoxStyle.DropDownList, Height = 30 };
var dgv = new DataGridView { Dock = DockStyle.Fill, AutoGenerateColumns = true };
var btnRefresh = new Button { Text = "刷新", Dock = DockStyle.Top, Height = 30 };
// 绑定9组核心数据表
cboTable.Items.AddRange(new[]
{
"WorkStation(工站)", "ProcessRoute(工序路径)", "BOM(物料清单)", "WorkTask(工作任务)",
"Material(物料库存)", "ProductionLine(产线)", "Resource(资源)", "Employee(员工)", "Equipment(设备)"
});
cboTable.SelectedIndexChanged += (s, e) =>
{
if (cboTable.SelectedItem == null) return;
string tableName = cboTable.SelectedItem.ToString().Split('(')[0];
dgv.DataSource = LocalDB.Query($"SELECT * FROM {tableName}");
};
btnRefresh.Click += (s, e) =>
{
if (cboTable.SelectedItem != null)
{
string tableName = cboTable.SelectedItem.ToString().Split('(')[0];
dgv.DataSource = LocalDB.Query($"SELECT * FROM {tableName}");
}
};
// 布局
var split = new SplitContainer { Dock = DockStyle.Fill, Orientation = Orientation.Horizontal, SplitterDistance = 60 };
var pnlTop = new Panel { Dock = DockStyle.Fill };
pnlTop.Controls.Add(btnRefresh);
pnlTop.Controls.Add(cboTable);
split.Panel1.Controls.Add(pnlTop);
split.Panel2.Controls.Add(dgv);
page.Controls.Add(split);
return page;
}
}
六、辅助层:补充核心组件
csharp
运行
// 1. BOM计算组件
public static class BomComponent
{
public static DataTable GetBom(string product)
{
return LocalDB.Query($"SELECT * FROM BOM WHERE ProductCode='{product}'");
}
public static decimal Calc(int qty, decimal useQty, decimal loss)
{
// 公式:总需求 = 产量 × 单用量 × (1 + 损耗率)
return Math.Round(qty * useQty * (1 + loss), 2);
}
}
// 2. 任务组件
public static class TaskComponent
{
public static DataTable GetTasks() => LocalDB.Query("SELECT * FROM WorkTask");
public static void UpdateStatus(int taskId, string status)
{
LocalDB.Execute($"UPDATE WorkTask SET Status='{status}' WHERE Id={taskId}");
MesEventBus.SendTask(taskId.ToString(), "分节点执行端", status);
}
public static (DataTable left, DataTable right) SplitTasks()
{
var left = LocalDB.Query("SELECT * FROM WorkTask WHERE Status!='已完成'");
var right = LocalDB.Query("SELECT * FROM WorkTask WHERE Status='已完成'");
return (left, right);
}
}
// 3. 事件总线
public static class MesEventBus
{
public delegate void TaskOperateHandler(string taskId, string station, string status);
public delegate void RefreshUIHandler();
public delegate void BomCalcHandler(string product, decimal total);
public delegate void DataEditHandler(string table);
public static event TaskOperateHandler OnTaskOperated;
public static event RefreshUIHandler OnRefreshUI;
public static event BomCalcHandler OnBomCalculated;
public static event DataEditHandler OnDataEdited;
public static void SendTask(string taskId, string station, string status)
{
OnTaskOperated?.Invoke(taskId, station, status);
}
public static void TriggerRefresh() => OnRefreshUI?.Invoke();
public static void SendBom(string product, decimal total) => OnBomCalculated?.Invoke(product, total);
public static void SendEdit(string table) => OnDataEdited?.Invoke(table);
}
// 4. 汇总工作台(优化版)
public static class DashboardView
{
public static TextBox _logBox = new TextBox();
public static TabPage Create()
{
var page = new TabPage("汇总工作台");
_logBox = new TextBox
{
Dock = DockStyle.Fill,
Multiline = true,
ReadOnly = true,
BackColor = Color.Black,
ForeColor = Color.Lime,
Font = new Font("Consolas", 10),
ScrollBars = ScrollBars.Vertical,
WordWrap = false
};
// 初始化日志
_logBox.AppendText($"{DateTime.Now:HH:mm:ss} MES/ERP协同系统启动完成 \r\n");
_logBox.AppendText($"{DateTime.Now:HH:mm:ss} 本地数据库路径:{LocalDB.Conn.Split('=')[1].Split(';')[0]} \r\n");
page.Controls.Add(_logBox);
return page;
}
}
// 5. 图表视图(增强版)
public static class ChartView
{
public static TabPage Create(GdiChart chart)
{
var page = new TabPage("图表报表");
var pnlTop = new Panel { Dock = DockStyle.Top, Height = 80 };
var btnLoadTask = new Button { Text = "加载任务产量图", Dock = DockStyle.Left, Width = 120 };
var btnLoadBom = new Button { Text = "加载BOM需求图", Dock = DockStyle.Left, Width = 120 };
var btnExport = new Button { Text = "导出图表", Dock = DockStyle.Left, Width = 100 };
var cboType = new ComboBox { Dock = DockStyle.Left, Width = 150, DropDownStyle = ComboBoxStyle.DropDownList };
cboType.Items.AddRange(new[] { "工站产量统计", "物料需求统计" });
cboType.SelectedIndex = 0;
pnlTop.Controls.Add(btnLoadTask);
pnlTop.Controls.Add(btnLoadBom);
pnlTop.Controls.Add(btnExport);
pnlTop.Controls.Add(cboType);
chart.Dock = DockStyle.Fill;
page.Controls.Add(chart);
page.Controls.Add(pnlTop);
// 加载任务产量图
btnLoadTask.Click += (s, e) =>
{
chart.XAxisName = "工站";
chart.YAxisName = "计划产量";
chart.DataSource = LocalDB.Query("SELECT StationName, SUM(PlanQty) Qty FROM WorkTask GROUP BY StationName");
chart.Refresh();
};
// 加载BOM需求图
btnLoadBom.Click += (s, e) =>
{
chart.XAxisName = "物料";
chart.YAxisName = "总需求(1000产量)";
var dt = LocalDB.Query("SELECT Material, UseQty*1000*(1+Loss) Qty FROM BOM WHERE ProductCode='PROD001'");
chart.DataSource = dt;
chart.Refresh();
};
// 导出图表
btnExport.Click += (s, e) =>
{
using (var sfd = new SaveFileDialog { Filter = "PNG图片|*.png", Title = "导出图表" })
{
if (sfd.ShowDialog() == DialogResult.OK)
{
chart.ExportToImage(sfd.FileName);
}
}
};
return page;
}
}
七、程序入口
csharp
运行
using System;
using System.Windows.Forms;
namespace PraticMES_DULTASKEXe
{
internal static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.ThreadException += (s, e) =>
{
MessageBox.Show($"系统异常:{e.Exception.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
DashboardView._logBox?.AppendText($"{DateTime.Now:HH:mm:ss} 异常:{e.Exception.Message} \r\n");
};
Application.Run(new MainForm());
}
}
}
功能说明
- 本地数据库:基于 SQLite 实现 9 组核心数据(工站、工序、BOM、任务、物料、产线、资源、员工、设备)的本地化存储,支持增删改查;
- GDI 图表:标准化柱状图,支持抗锯齿、多颜色柱体、坐标轴标注、图例、数值标注、图表导出,可配置字体 / 颜色 / 坐标轴名称;
- 业务模块 :
- 分节点执行端:任务状态变更(待执行 / 执行中 / 已完成);
- BOM 物料调配:产量需求计算、BOM 数据维护、导出;
- 工序路径管理:工序的新增 / 编辑 / 删除 / 排序;
- 主子调度台:工站 - 任务的主子表联动;
- 汇总工作台:实时日志输出;
- 数据维护中心:统一维护 9 组核心数据;
- 图表报表:多维度数据可视化、导出;
- 交互体验:支持数据校验、异常捕获、日志记录、文件导出等。
部署说明
- 确保项目引用
System.Data.SQLite(可通过 NuGet 安装System.Data.SQLite.Core); - 目标框架设置为
.NET Framework 4.8.1; - 编译后会自动生成
MESData.db本地数据库文件,包含预置的 9 组测试数据; - 支持 Windows 系统直接运行,无需额外安装数据库。
该实现完整覆盖了 MES/ERP 工序 BOM 协同场景的核心需求,模块化设计便于扩展,本地化数据库满足离线使用需求,GDI 图表满足可视化报表需求,数据维护模块支持业务数据的灵活编辑。