MES/ERP 工序 BOM 协同多节点工站组件

以下是基于 MES/ERP 工序 BOM 协同场景,完整实现委托 / 事件封装、主子端台、二分法任务操作、多节点工站、GDI + 丰富图表、本地 SQLite 数据维护 的 WinForm 代码组件,包含核心业务逻辑 + UI 交互 + 数据层全量实现:

一、核心工程结构(完整文件清单)

plaintext

复制代码
ActDulpEvent/
├─ Program.cs                // 程序入口
├─ MainMESForm.cs            // 主窗体(核心UI+业务串联)
├─ MainMESForm.Designer.cs   // 窗体设计器(自动生成)
├─ MesEventBus.cs            // 全局事件总线(委托+事件封装)
├─ LocalDatabase.cs          // SQLite本地数据库(数据维护+初始化)
├─ TaskWorkComponent.cs      // 工站任务操作(二分法任务状态管理)
├─ BomCalculateComponent.cs  // BOM物料计算(多产品多物料支持)
├─ MasterSlaveComponent.cs   // 主子端台(主从数据联动)
├─ GdiChartComponent.cs      // GDI+图表组件(柱状图+扩展样式)
├─ DashboardComponent.cs     // 汇总工作台(日志+仪表盘)
├─ MenuComponent.cs          // 全局菜单组件
├─ DataEditForm.cs           // 本地数据维护窗体(编辑5-6组测试数据)
└─ 配置文件/资源文件(自动生成)

二、核心代码实现(全量可运行)

1. 全局事件总线(委托 + 事件封装)MesEventBus.cs

csharp

运行

复制代码
using System;

/// <summary>
/// MES全局事件总线:封装所有业务委托和事件,解耦组件通信
/// </summary>
public static class MesEventBus
{
    #region 委托定义(覆盖所有核心业务场景)
    /// <summary>任务执行状态变更委托</summary>
    public delegate void TaskExecuteDelegate(string taskId, string station, string status, int planQty);
    /// <summary>UI刷新委托</summary>
    public delegate void RefreshUIDelegate();
    /// <summary>BOM计算完成委托</summary>
    public delegate void BomCalculateDelegate(string product, decimal totalQty, DataTable bomResult);
    /// <summary>图表数据加载委托</summary>
    public delegate void ChartLoadDelegate(DataTable chartData);
    /// <summary>数据编辑完成委托</summary>
    public delegate void DataEditDelegate(string tableName, bool isSuccess);
    #endregion

    #region 事件定义(对应委托)
    public static event TaskExecuteDelegate OnTaskExecuted;
    public static event RefreshUIDelegate OnDataRefresh;
    public static event BomCalculateDelegate OnBomCalculated;
    public static event ChartLoadDelegate OnChartLoaded;
    public static event DataEditDelegate OnDataEdited;
    #endregion

    #region 事件触发方法(统一封装)
    public static void TriggerTask(string taskId, string station, string status, int planQty)
    {
        OnTaskExecuted?.Invoke(taskId, station, status, planQty);
    }

    public static void TriggerRefresh()
    {
        OnDataRefresh?.Invoke();
    }

    public static void TriggerBom(string product, decimal totalQty, DataTable bomResult)
    {
        OnBomCalculated?.Invoke(product, totalQty, bomResult);
    }

    public static void TriggerChart(DataTable chartData)
    {
        OnChartLoaded?.Invoke(chartData);
    }

    public static void TriggerDataEdit(string tableName, bool isSuccess)
    {
        OnDataEdited?.Invoke(tableName, isSuccess);
    }
    #endregion
}
2. 本地 SQLite 数据库(支持 5-6 组数据维护)LocalDatabase.cs

csharp

运行

复制代码
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Windows.Forms;

/// <summary>
/// 本地SQLite数据库操作:初始化/CRUD/数据维护,支持5-6组测试数据
/// </summary>
public static class LocalDatabase
{
    private static string dbFile = Path.Combine(Application.StartupPath, "MES_Data.db");
    public static string ConnString => $"Data Source={dbFile};Version=3;";

    /// <summary>初始化数据库+预置5-6组测试数据</summary>
    public static void InitDatabase()
    {
        if (!File.Exists(dbFile))
            SQLiteConnection.CreateFile(dbFile);

        using (var conn = new SQLiteConnection(ConnString))
        {
            conn.Open();
            // 创建核心表(工站/工序/BOM/任务/产品基础数据)
            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,SortNo INT,ProcessDesc TEXT);
                CREATE TABLE IF NOT EXISTS Product(Id INTEGER PRIMARY KEY,Code TEXT,Name TEXT,Spec TEXT,Unit TEXT);
                CREATE TABLE IF NOT EXISTS BOM(Id INTEGER PRIMARY KEY,ProductCode TEXT,Material TEXT,UseQty DECIMAL,LossRate DECIMAL,TotalRequired DECIMAL);
                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);
            ", conn).ExecuteNonQuery();
        }
        InitSampleData(); // 初始化5-6组测试数据
    }

    /// <summary>预置5-6组测试数据(覆盖所有核心表)</summary>
    private static void InitSampleData()
    {
        // 清空旧数据
        Execute("DELETE FROM WorkStation");
        Execute("DELETE FROM ProcessRoute");
        Execute("DELETE FROM Product");
        Execute("DELETE FROM BOM");
        Execute("DELETE FROM WorkTask");
        Execute("DELETE FROM Material");

        // 1. 工站数据(6组)
        Execute("INSERT INTO WorkStation VALUES(1,'SMT贴片工站','操作员','负责PCB贴片')");
        Execute("INSERT INTO WorkStation VALUES(2,'AI插件工站','操作员','负责自动插件')");
        Execute("INSERT INTO WorkStation VALUES(3,'波峰焊工站','技术员','负责焊点焊接')");
        Execute("INSERT INTO WorkStation VALUES(4,'QC检验工站','质检员','外观/功能检测')");
        Execute("INSERT INTO WorkStation VALUES(5,'老化测试工站','测试员','高温老化测试')");
        Execute("INSERT INTO WorkStation VALUES(6,'包装工站','包装员','成品包装')");

        // 2. 产品数据(5组)
        Execute("INSERT INTO Product VALUES(1,'PROD001','智能温控器','220V','台')");
        Execute("INSERT INTO Product VALUES(2,'PROD002','物联网模块','485通信','个')");
        Execute("INSERT INTO Product VALUES(3,'PROD003','电源适配器','12V/2A','个')");
        Execute("INSERT INTO Product VALUES(4,'PROD004','触摸显示屏','7寸','块')");
        Execute("INSERT INTO Product VALUES(5,'PROD005','控制主板','V2.0版本','块')");

        // 3. 物料数据(6组)
        Execute("INSERT INTO Material VALUES(1,'MAT001','PCB主板','500','块')");
        Execute("INSERT INTO Material VALUES(2,'MAT002','STM32芯片','2000','颗')");
        Execute("INSERT INTO Material VALUES(3,'MAT003','电容100uF','10000','个')");
        Execute("INSERT INTO Material VALUES(4,'MAT004','电阻10K','20000','个')");
        Execute("INSERT INTO Material VALUES(5,'MAT005','电源接口','1000','个')");
        Execute("INSERT INTO Material VALUES(6,'MAT006','散热片','800','片')");

        // 4. BOM数据(6组)
        Execute("INSERT INTO BOM VALUES(1,'PROD001','MAT001',1,0.03,0)");
        Execute("INSERT INTO BOM VALUES(2,'PROD001','MAT002',1,0.02,0)");
        Execute("INSERT INTO BOM VALUES(3,'PROD001','MAT003',4,0.01,0)");
        Execute("INSERT INTO BOM VALUES(4,'PROD002','MAT001',1,0.03,0)");
        Execute("INSERT INTO BOM VALUES(5,'PROD002','MAT004',8,0.01,0)");
        Execute("INSERT INTO BOM VALUES(6,'PROD003','MAT005',1,0.005,0)");

        // 5. 工序路线(6组)
        Execute("INSERT INTO ProcessRoute VALUES(1,'SMT贴片',1,1,'PCB贴片+印刷锡膏')");
        Execute("INSERT INTO ProcessRoute VALUES(2,'AI插件',2,2,'自动插件机插件')");
        Execute("INSERT INTO ProcessRoute VALUES(3,'波峰焊',3,3,'高温焊接焊点')");
        Execute("INSERT INTO ProcessRoute VALUES(4,'QC检验',4,4,'外观+功能检测')");
        Execute("INSERT INTO ProcessRoute VALUES(5,'老化测试',5,5,'85℃/4小时老化')");
        Execute("INSERT INTO ProcessRoute VALUES(6,'成品包装',6,6,'贴标+装箱')");

        // 6. 工站任务(6组)
        Execute("INSERT INTO WorkTask VALUES(1,'T001',1,'SMT贴片工站','待执行',100,0,'2026-01-01 08:00:00')");
        Execute("INSERT INTO WorkTask VALUES(2,'T002',2,'AI插件工站','执行中',150,80,'2026-01-01 09:00:00')");
        Execute("INSERT INTO WorkTask VALUES(3,'T003',3,'波峰焊工站','待执行',200,0,'2026-01-01 10:00:00')");
        Execute("INSERT INTO WorkTask VALUES(4,'T004',4,'QC检验工站','已完成',180,180,'2026-01-01 11:00:00')");
        Execute("INSERT INTO WorkTask VALUES(5,'T005',5,'老化测试工站','执行中',120,90,'2026-01-01 14:00:00')");
        Execute("INSERT INTO WorkTask VALUES(6,'T006',6,'包装工站','已完成',200,200,'2026-01-01 16:00:00')");
    }

    #region 通用CRUD操作(支持数据维护)
    /// <summary>查询数据</summary>
    public static DataTable Query(string sql)
    {
        var dt = new DataTable();
        using (var adp = new SQLiteDataAdapter(sql, ConnString))
            adp.Fill(dt);
        return dt;
    }

    /// <summary>执行增删改</summary>
    public static int Execute(string sql)
    {
        using (var conn = new SQLiteConnection(ConnString))
        {
            conn.Open();
            return new SQLiteCommand(sql, conn).ExecuteNonQuery();
        }
    }

    /// <summary>编辑单条数据(通用方法)</summary>
    public static bool EditData(string tableName, Dictionary<string, object> data, string whereClause)
    {
        try
        {
            var setParts = new List<string>();
            foreach (var kv in data)
            {
                setParts.Add($"{kv.Key}='{kv.Value}'");
            }
            string sql = $"UPDATE {tableName} SET {string.Join(",", setParts)} WHERE {whereClause}";
            Execute(sql);
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>新增单条数据(通用方法)</summary>
    public static bool AddData(string tableName, Dictionary<string, object> data)
    {
        try
        {
            string cols = string.Join(",", data.Keys);
            string vals = string.Join("','", data.Values);
            string sql = $"INSERT INTO {tableName} ({cols}) VALUES ('{vals}')";
            Execute(sql);
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>删除单条数据(通用方法)</summary>
    public static bool DeleteData(string tableName, string whereClause)
    {
        try
        {
            string sql = $"DELETE FROM {tableName} WHERE {whereClause}";
            Execute(sql);
            return true;
        }
        catch
        {
            return false;
        }
    }
    #endregion
}
3. 增强版 GDI + 图表组件(丰富样式 + 多类型图表)GdiChartComponent.cs

csharp

运行

复制代码
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

/// <summary>
/// 增强版GDI+图表组件:支持柱状图/堆叠柱状图/对比柱状图,丰富样式(渐变/标注/网格)
/// </summary>
public class GdiChartComponent : Panel
{
    #region 图表配置属性
    public DataTable DataSource { get; set; }
    public string XField { get; set; } = "StationName";
    public string YField { get; set; } = "PlanQty";
    public string CompareYField { get; set; } = "ActualQty"; // 对比字段
    public bool ShowGrid { get; set; } = true;
    public bool ShowGradient { get; set; } = true;
    public bool ShowCompare { get; set; } = false; // 是否显示对比柱
    public Color MainColor { get; set; } = Color.CornflowerBlue;
    public Color CompareColor { get; set; } = Color.OrangeRed;
    #endregion

    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. 绘制网格
        if (ShowGrid)
        {
            DrawGrid(g);
        }

        // 2. 计算图表区域和柱子尺寸
        int paddingLeft = 80, paddingBottom = 50, paddingTop = 30, paddingRight = 20;
        int chartWidth = ClientSize.Width - paddingLeft - paddingRight;
        int chartHeight = ClientSize.Height - paddingTop - paddingBottom;
        int barCount = DataSource.Rows.Count;
        int barWidth = chartWidth / (barCount * 3); // 留间距
        int xStart = paddingLeft;

        // 3. 获取Y轴最大值(适配数据)
        int maxY = 0;
        foreach (DataRow row in DataSource.Rows)
        {
            int yVal = int.Parse(row[YField].ToString());
            if (ShowCompare)
            {
                int compareVal = int.Parse(row[CompareYField].ToString());
                maxY = Math.Max(maxY, Math.Max(yVal, compareVal));
            }
            else
            {
                maxY = Math.Max(maxY, yVal);
            }
        }
        maxY = (int)Math.Ceiling(maxY * 1.2); // 留20%余量

        // 4. 绘制柱子(主柱+对比柱)
        for (int i = 0; i < DataSource.Rows.Count; i++)
        {
            DataRow row = DataSource.Rows[i];
            string xLabel = row[XField].ToString();
            int yVal = int.Parse(row[YField].ToString());
            int compareVal = ShowCompare ? int.Parse(row[CompareYField].ToString()) : 0;

            // 计算柱子高度(比例换算)
            int mainBarHeight = (int)(yVal / (float)maxY * chartHeight);
            int compareBarHeight = ShowCompare ? (int)(compareVal / (float)maxY * chartHeight) : 0;

            // 计算柱子Y坐标(从下往上)
            int mainBarY = paddingTop + chartHeight - mainBarHeight;
            int compareBarY = paddingTop + chartHeight - compareBarHeight;

            // 绘制主柱子(渐变填充)
            Rectangle mainRect = new Rectangle(xStart + i * barWidth * 2, mainBarY, barWidth, mainBarHeight);
            using (var brush = ShowGradient 
                ? new LinearGradientBrush(mainRect, MainColor, MainColor.Darken(30), LinearGradientMode.Vertical) 
                : new SolidBrush(MainColor))
            {
                g.FillRectangle(brush, mainRect);
                g.DrawRectangle(Pens.Black, mainRect); // 描边
            }

            // 绘制对比柱子(右侧偏移)
            if (ShowCompare)
            {
                Rectangle compareRect = new Rectangle(xStart + i * barWidth * 2 + barWidth + 5, compareBarY, barWidth, compareBarHeight);
                using (var brush = ShowGradient
                    ? new LinearGradientBrush(compareRect, CompareColor, CompareColor.Darken(30), LinearGradientMode.Vertical)
                    : new SolidBrush(CompareColor))
                {
                    g.FillRectangle(brush, compareRect);
                    g.DrawRectangle(Pens.Black, compareRect); // 描边
                }
            }

            // 绘制数值标注
            g.DrawString(yVal.ToString(), new Font("微软雅黑", 9, FontStyle.Bold), Brushes.Black, 
                mainRect.X + 5, mainRect.Y - 20);
            if (ShowCompare)
            {
                g.DrawString(compareVal.ToString(), new Font("微软雅黑", 9, FontStyle.Bold), Brushes.Black,
                    compareRect.X + 5, compareRect.Y - 20);
            }

            // 绘制X轴标签(旋转45度防重叠)
            StringFormat sf = new StringFormat { Alignment = StringAlignment.Center };
            g.TranslateTransform(mainRect.X + barWidth / 2, ClientSize.Height - paddingBottom + 10);
            g.RotateTransform(-45);
            g.DrawString(xLabel, new Font("微软雅黑", 9), Brushes.DarkRed, 0, 0, sf);
            g.ResetTransform();
        }

        // 5. 绘制Y轴刻度和标签
        int yStep = maxY / 5; // 分5段
        for (int i = 0; i <= 5; i++)
        {
            int y = paddingTop + chartHeight - (i * chartHeight / 5);
            string label = (i * yStep).ToString();
            g.DrawString(label, new Font("微软雅黑", 8), Brushes.Gray, paddingLeft - 30, y - 10);
            g.DrawLine(Pens.LightGray, paddingLeft - 5, y, ClientSize.Width - paddingRight, y); // 水平刻度线
        }

        // 6. 绘制图例
        DrawLegend(g, paddingLeft, paddingTop - 20);
    }

    /// <summary>绘制网格</summary>
    private void DrawGrid(Graphics g)
    {
        int paddingLeft = 80, paddingBottom = 50, paddingTop = 30, paddingRight = 20;
        int chartWidth = ClientSize.Width - paddingLeft - paddingRight;
        int chartHeight = ClientSize.Height - paddingTop - paddingBottom;

        // 竖线网格
        for (int i = 0; i <= DataSource.Rows.Count; i++)
        {
            int x = paddingLeft + i * (chartWidth / DataSource.Rows.Count);
            g.DrawLine(Pens.LightGray, x, paddingTop, x, ClientSize.Height - paddingBottom);
        }

        // 横线网格
        int yStep = chartHeight / 5;
        for (int i = 0; i <= 5; i++)
        {
            int y = paddingTop + i * yStep;
            g.DrawLine(Pens.LightGray, paddingLeft, y, ClientSize.Width - paddingRight, y);
        }
    }

    /// <summary>绘制图例</summary>
    private void DrawLegend(Graphics g, int x, int y)
    {
        // 主图例
        g.FillRectangle(new SolidBrush(MainColor), x, y, 15, 10);
        g.DrawRectangle(Pens.Black, x, y, 15, 10);
        g.DrawString("计划产量", new Font("微软雅黑", 8), Brushes.Black, x + 20, y);

        // 对比图例
        if (ShowCompare)
        {
            g.FillRectangle(new SolidBrush(CompareColor), x + 100, y, 15, 10);
            g.DrawRectangle(Pens.Black, x + 100, y, 15, 10);
            g.DrawString("实际产量", new Font("微软雅黑", 8), Brushes.Black, x + 120, y);
        }
    }

    /// <summary>刷新图表</summary>
    public void RefreshChart()
    {
        Invalidate();
    }
}

/// <summary>颜色扩展方法</summary>
public static class ColorExtensions
{
    public static Color Darken(this Color color, int percent)
    {
        float factor = 1 - percent / 100f;
        return Color.FromArgb(
            color.A,
            (int)(color.R * factor),
            (int)(color.G * factor),
            (int)(color.B * factor)
        );
    }
}
4. 数据维护窗体(本地数据编辑)DataEditForm.cs

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

/// <summary>
/// 本地数据维护窗体:支持编辑/新增/删除5-6组测试数据(工站/产品/BOM/任务等)
/// </summary>
public class DataEditForm : Form
{
    private ComboBox _cbTable;
    private DataGridView _dgvData;
    private Button _btnAdd, _btnEdit, _btnDelete, _btnSave;
    private TextBox _txtField, _txtValue, _txtWhere;

    public DataEditForm()
    {
        Text = "本地数据维护";
        Size = new Size(800, 600);
        StartPosition = FormStartPosition.CenterParent;

        // 1. 初始化控件
        InitControls();

        // 2. 绑定表选择事件
        _cbTable.SelectedIndexChanged += (s, e) => LoadTableData(_cbTable.SelectedItem.ToString());

        // 3. 绑定按钮事件
        _btnAdd.Click += BtnAdd_Click;
        _btnEdit.Click += BtnEdit_Click;
        _btnDelete.Click += BtnDelete_Click;
        _btnSave.Click += BtnSave_Click;

        // 4. 默认加载工站数据
        LoadTableData("WorkStation");
    }

    private void InitControls()
    {
        // 表选择下拉框
        _cbTable = new ComboBox
        {
            Location = new Point(10, 10),
            Width = 200,
            DropDownStyle = ComboBoxStyle.DropDownList
        };
        _cbTable.Items.AddRange(new[] { "WorkStation", "Product", "Material", "BOM", "ProcessRoute", "WorkTask" });
        _cbTable.SelectedIndex = 0;

        // 数据表格
        _dgvData = new DataGridView
        {
            Location = new Point(10, 40),
            Width = 760,
            Height = 400,
            AllowUserToAddRows = false,
            ReadOnly = true,
            AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells
        };

        // 编辑控件
        _txtField = new TextBox { Location = new Point(10, 450), Width = 100, PlaceholderText = "字段名" };
        _txtValue = new TextBox { Location = new Point(120, 450), Width = 200, PlaceholderText = "字段值" };
        _txtWhere = new TextBox { Location = new Point(330, 450), Width = 200, PlaceholderText = "条件(如Id=1)" };

        // 按钮
        _btnAdd = new Button { Text = "新增", Location = new Point(10, 480), Width = 80 };
        _btnEdit = new Button { Text = "编辑", Location = new Point(100, 480), Width = 80 };
        _btnDelete = new Button { Text = "删除", Location = new Point(190, 480), Width = 80 };
        _btnSave = new Button { Text = "保存", Location = new Point(280, 480), Width = 80 };

        // 添加控件到窗体
        Controls.Add(_cbTable);
        Controls.Add(_dgvData);
        Controls.Add(_txtField);
        Controls.Add(_txtValue);
        Controls.Add(_txtWhere);
        Controls.Add(_btnAdd);
        Controls.Add(_btnEdit);
        Controls.Add(_btnDelete);
        Controls.Add(_btnSave);
    }

    /// <summary>加载指定表数据</summary>
    private void LoadTableData(string tableName)
    {
        _dgvData.DataSource = LocalDatabase.Query($"SELECT * FROM {tableName}");
    }

    /// <summary>新增数据</summary>
    private void BtnAdd_Click(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(_txtField.Text) || string.IsNullOrEmpty(_txtValue.Text))
        {
            MessageBox.Show("请输入字段名和值");
            return;
        }

        var data = new Dictionary<string, object>
        {
            { _txtField.Text, _txtValue.Text }
        };
        bool success = LocalDatabase.AddData(_cbTable.SelectedItem.ToString(), data);
        if (success)
        {
            MessageBox.Show("新增成功");
            LoadTableData(_cbTable.SelectedItem.ToString());
            MesEventBus.TriggerDataEdit(_cbTable.SelectedItem.ToString(), true);
        }
        else
        {
            MessageBox.Show("新增失败");
        }
    }

    /// <summary>编辑数据</summary>
    private void BtnEdit_Click(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(_txtField.Text) || string.IsNullOrEmpty(_txtValue.Text) || string.IsNullOrEmpty(_txtWhere.Text))
        {
            MessageBox.Show("请输入字段名、值和条件");
            return;
        }

        var data = new Dictionary<string, object>
        {
            { _txtField.Text, _txtValue.Text }
        };
        bool success = LocalDatabase.EditData(_cbTable.SelectedItem.ToString(), data, _txtWhere.Text);
        if (success)
        {
            MessageBox.Show("编辑成功");
            LoadTableData(_cbTable.SelectedItem.ToString());
            MesEventBus.TriggerDataEdit(_cbTable.SelectedItem.ToString(), true);
        }
        else
        {
            MessageBox.Show("编辑失败");
        }
    }

    /// <summary>删除数据</summary>
    private void BtnDelete_Click(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(_txtWhere.Text))
        {
            MessageBox.Show("请输入删除条件");
            return;
        }

        if (MessageBox.Show("确认删除?", "提示", MessageBoxButtons.YesNo) != DialogResult.Yes)
            return;

        bool success = LocalDatabase.DeleteData(_cbTable.SelectedItem.ToString(), _txtWhere.Text);
        if (success)
        {
            MessageBox.Show("删除成功");
            LoadTableData(_cbTable.SelectedItem.ToString());
            MesEventBus.TriggerDataEdit(_cbTable.SelectedItem.ToString(), true);
        }
        else
        {
            MessageBox.Show("删除失败");
        }
    }

    /// <summary>保存并刷新主窗体</summary>
    private void BtnSave_Click(object sender, EventArgs e)
    {
        MesEventBus.TriggerRefresh();
        MessageBox.Show("数据已同步");
        Close();
    }
}
5. 主窗体(串联所有组件 + 二分法任务操作)MainMESForm.cs

csharp

运行

复制代码
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

/// <summary>
/// 主窗体:整合所有组件,实现分节点工站、主子端台、二分法任务、GDI图表、数据维护
/// </summary>
public class MainMESForm : Form
{
    private GdiChartComponent _chart;
    private DataGridView _dgvTask;
    private TextBox _logBox;
    private SplitContainer _splitTask; // 二分法任务操作面板

    public MainMESForm()
    {
        LocalDatabase.InitDatabase();
        BuildMenu();
        BuildTabUI();
        BindEvents();
        Text = "MES/ERP 工序BOM协同系统";
        WindowState = FormWindowState.Maximized;
    }

    /// <summary>构建全局菜单</summary>
    private void BuildMenu()
    {
        var menu = MenuComponent.CreateMainMenu();
        // 添加数据维护菜单
        var dataMenu = new ToolStripMenuItem("数据维护");
        dataMenu.Click += (s, e) => new DataEditForm().ShowDialog();
        menu.Items.Add(dataMenu);
        MainMenuStrip = menu;
        Controls.Add(MainMenuStrip);
    }

    /// <summary>构建Tab页UI</summary>
    private void BuildTabUI()
    {
        var tab = new TabControl
        {
            Dock = DockStyle.Fill,
            Alignment = TabAlignment.Top,
            ItemSize = new Size(158, 65),
            Font = new Font("微软雅黑", 11, FontStyle.Bold)
        };
        tab.TabPages.Add(CreateTaskPage());       // 分节点工站+二分法任务
        tab.TabPages.Add(CreateBomPage());        // BOM物料调配
        tab.TabPages.Add(CreateChartPage());      // GDI图表报表
        tab.TabPages.Add(CreateMasterSlavePage());// 主子端台
        tab.TabPages.Add(CreateDashboardPage());  // 汇总工作台
        Controls.Add(tab);
    }

    /// <summary>绑定全局事件</summary>
    private void BindEvents()
    {
        // 任务状态变更事件
        MesEventBus.OnTaskExecuted += (task, station, status, planQty) =>
        {
            _logBox.AppendText($"{DateTime.Now:HH:mm:ss} [{station}] 任务{task} → {status} (计划产量:{planQty})\r\n");
        };
        // UI刷新事件
        MesEventBus.OnDataRefresh += LoadTasks;
        // BOM计算事件
        MesEventBus.OnBomCalculated += (product, totalQty, bomResult) =>
        {
            _logBox.AppendText($"{DateTime.Now:HH:mm:ss} BOM计算完成:产品{product} 总需求{totalQty}\r\n");
        };
        // 图表加载事件
        MesEventBus.OnChartLoaded += (chartData) =>
        {
            _chart.DataSource = chartData;
            _chart.RefreshChart();
        };
        // 数据编辑事件
        MesEventBus.OnDataEdited += (tableName, isSuccess) =>
        {
            _logBox.AppendText($"{DateTime.Now:HH:mm:ss} 数据维护:{tableName} {(isSuccess ? "成功" : "失败")}\r\n");
        };
    }

    #region 页面构建
    /// <summary>分节点工站+二分法任务操作页</summary>
    private TabPage CreateTaskPage()
    {
        var page = new TabPage("分节点工站端");
        // 二分法任务面板(上:操作区,下:数据区)
        _splitTask = new SplitContainer
        {
            Dock = DockStyle.Fill,
            Orientation = Orientation.Horizontal,
            SplitterDistance = 100
        };

        // 上半区:二分法任务操作(待执行/已完成)
        var pnlTop = new Panel { Dock = DockStyle.Fill };
        var btnToExecute = new Button { Text = "待执行→执行中", Dock = DockStyle.Left, Width = 150 };
        var btnToFinish = new Button { Text = "执行中→已完成", Dock = DockStyle.Left, Width = 150 };
        var btnReset = new Button { Text = "重置任务状态", Dock = DockStyle.Left, Width = 150 };
        pnlTop.Controls.Add(btnToExecute);
        pnlTop.Controls.Add(btnToFinish);
        pnlTop.Controls.Add(btnReset);

        // 下半区:任务数据表格
        _dgvTask = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };

        // 绑定二分法操作事件
        btnToExecute.Click += (s, e) => DoTask("执行中");
        btnToFinish.Click += (s, e) => DoTask("已完成");
        btnReset.Click += (s, e) => DoTask("待执行");

        // 组装二分法面板
        _splitTask.Panel1.Controls.Add(pnlTop);
        _splitTask.Panel2.Controls.Add(_dgvTask);
        page.Controls.Add(_splitTask);

        LoadTasks();
        return page;
    }

    /// <summary>BOM物料调配页</summary>
    private TabPage CreateBomPage()
    {
        var page = new TabPage("BOM物料调配");
        var dgv = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
        var cboProduct = new ComboBox { Dock = DockStyle.Top, Width = 200, DropDownStyle = ComboBoxStyle.DropDownList };
        var txtQty = new TextBox { Dock = DockStyle.Top, Width = 200, PlaceholderText = "输入生产数量" };
        var btnCalc = new Button { Text = "计算物料需求", Dock = DockStyle.Top, Width = 200 };

        // 加载产品列表
        cboProduct.DataSource = LocalDatabase.Query("SELECT Code,Name FROM Product");
        cboProduct.DisplayMember = "Name";
        cboProduct.ValueMember = "Code";

        // BOM计算事件
        btnCalc.Click += (s, e) =>
        {
            if (!int.TryParse(txtQty.Text, out int qty) || qty <= 0)
            {
                MessageBox.Show("请输入有效生产数量");
                return;
            }
            string productCode = cboProduct.SelectedValue.ToString();
            var dt = BomCalculateComponent.GetBomData(productCode);
            foreach (DataRow r in dt.Rows)
            {
                decimal total = BomCalculateComponent.CalcMaterial(qty,
                    decimal.Parse(r["UseQty"].ToString()),
                    decimal.Parse(r["LossRate"].ToString()));
                r["TotalRequired"] = total;
            }
            dgv.DataSource = dt;
            MesEventBus.TriggerBom(productCode, qty, dt);
        };

        // 组装控件
        page.Controls.Add(dgv);
        page.Controls.Add(cboProduct);
        page.Controls.Add(txtQty);
        page.Controls.Add(btnCalc);
        return page;
    }

    /// <summary>GDI图表报表页</summary>
    private TabPage CreateChartPage()
    {
        var page = new TabPage("GDI图表报表");
        _chart = new GdiChartComponent { Dock = DockStyle.Fill, ShowGrid = true, ShowGradient = true };
        var pnlChartCtrl = new Panel { Dock = DockStyle.Top, Height = 100 };

        // 图表控制按钮
        var btnLoad = new Button { Text = "加载计划产量图", Location = new Point(10, 10), Width = 150 };
        var btnLoadCompare = new Button { Text = "加载计划/实际对比图", Location = new Point(170, 10), Width = 180 };
        var chkGrid = new CheckBox { Text = "显示网格", Location = new Point(10, 40), Checked = true };
        var chkGradient = new CheckBox { Text = "渐变样式", Location = new Point(100, 40), Checked = true };

        // 图表控制事件
        btnLoad.Click += (s, e) =>
        {
            var dt = LocalDatabase.Query("SELECT StationName, SUM(PlanQty) PlanQty FROM WorkTask GROUP BY StationId");
            _chart.ShowCompare = false;
            MesEventBus.TriggerChart(dt);
        };
        btnLoadCompare.Click += (s, e) =>
        {
            var dt = LocalDatabase.Query("SELECT StationName, SUM(PlanQty) PlanQty, SUM(ActualQty) ActualQty FROM WorkTask GROUP BY StationId");
            _chart.ShowCompare = true;
            MesEventBus.TriggerChart(dt);
        };
        chkGrid.CheckedChanged += (s, e) => { _chart.ShowGrid = chkGrid.Checked; _chart.RefreshChart(); };
        chkGradient.CheckedChanged += (s, e) => { _chart.ShowGradient = chkGradient.Checked; _chart.RefreshChart(); };

        // 组装控件
        pnlChartCtrl.Controls.Add(btnLoad);
        pnlChartCtrl.Controls.Add(btnLoadCompare);
        pnlChartCtrl.Controls.Add(chkGrid);
        pnlChartCtrl.Controls.Add(chkGradient);
        page.Controls.Add(_chart);
        page.Controls.Add(pnlChartCtrl);
        return page;
    }

    /// <summary>主子端台页</summary>
    private TabPage CreateMasterSlavePage()
    {
        var page = new TabPage("主子端台");
        var split = new SplitContainer { Dock = DockStyle.Fill };
        var master = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
        var slave = new DataGridView { Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells };
        // 绑定主从数据(主:工站,从:工站任务)
        MasterSlaveComponent.BindMasterSlave(master, slave);
        split.Panel1.Controls.Add(master);
        split.Panel2.Controls.Add(slave);
        page.Controls.Add(split);
        return page;
    }

    /// <summary>汇总工作台页</summary>
    private TabPage CreateDashboardPage()
    {
        var page = new TabPage("汇总工作台");
        _logBox = DashboardComponent.CreateLogBox();
        page.Controls.Add(_logBox);
        return page;
    }
    #endregion

    /// <summary>加载任务数据</summary>
    private void LoadTasks()
    {
        _dgvTask.DataSource = TaskWorkComponent.GetAllTasks();
    }

    /// <summary>二分法任务状态变更</summary>
    private void DoTask(string status)
    {
        if (_dgvTask.CurrentRow == null)
        {
            MessageBox.Show("请选择任务");
            return;
        }

        int id = int.Parse(_dgvTask.CurrentRow.Cells["Id"].Value.ToString());
        string taskNo = _dgvTask.CurrentRow.Cells["TaskNo"].Value.ToString();
        string station = _dgvTask.CurrentRow.Cells["StationName"].Value.ToString();
        int planQty = int.Parse(_dgvTask.CurrentRow.Cells["PlanQty"].Value.ToString());

        // 更新任务状态
        TaskWorkComponent.UpdateTaskStatus(id, status);
        // 触发全局事件
        MesEventBus.TriggerTask(taskNo, station, status, planQty);
        MesEventBus.TriggerRefresh();
    }
}
6. 其他辅助组件(任务 / BOM / 菜单 / 仪表盘)

csharp

运行

复制代码
// TaskWorkComponent.cs(任务操作)
using System.Data;
public static class TaskWorkComponent
{
    public static DataTable GetAllTasks() => LocalDatabase.Query("SELECT * FROM WorkTask");
    public static void UpdateTaskStatus(int taskId, string status)
    {
        LocalDatabase.Execute($"UPDATE WorkTask SET Status='{status}' WHERE Id={taskId}");
        // 同步更新实际产量(执行中填50%,已完成填100%)
        int actualQty = status == "执行中" ? 50 : status == "已完成" ? 100 : 0;
        LocalDatabase.Execute($"UPDATE WorkTask SET ActualQty={actualQty} WHERE Id={taskId}");
    }
}

// BomCalculateComponent.cs(BOM计算)
using System.Data;
public static class BomCalculateComponent
{
    public static DataTable GetBomData(string productCode) => LocalDatabase.Query($"SELECT * FROM BOM WHERE ProductCode='{productCode}'");
    public static decimal CalcMaterial(int orderQty, decimal useQty, decimal loss) => orderQty * useQty * (1 + loss);
}

// MenuComponent.cs(菜单)
using System.Windows.Forms;
public static class MenuComponent
{
    public static MenuStrip CreateMainMenu()
    {
        var menu = new MenuStrip();
        menu.Items.Add("分节点工站端");
        menu.Items.Add("BOM物料调配");
        menu.Items.Add("GDI图表报表");
        menu.Items.Add("主子调度台");
        menu.Items.Add("汇总工作台");
        return menu;
    }
}

// DashboardComponent.cs(汇总工作台)
using System.Drawing;
using System.Windows.Forms;
public static class DashboardComponent
{
    public static TextBox CreateLogBox()
    {
        return new TextBox
        {
            Dock = DockStyle.Fill,
            Multiline = true,
            ReadOnly = true,
            BackColor = Color.Black,
            ForeColor = Color.Lime,
            Font = new Font("Consolas", 10),
            ScrollBars = ScrollBars.Vertical
        };
    }
}

// MasterSlaveComponent.cs(主子端台)
using System.Data;
using System.Windows.Forms;
public static class MasterSlaveComponent
{
    public static void BindMasterSlave(DataGridView master, DataGridView slave)
    {
        master.DataSource = LocalDatabase.Query("SELECT DISTINCT Id,Name FROM WorkStation");
        master.SelectionChanged += (s, e) =>
        {
            if (master.CurrentRow == null) return;
            string stationId = master.CurrentRow.Cells["Id"].Value.ToString();
            slave.DataSource = LocalDatabase.Query($"SELECT * FROM WorkTask WHERE StationId='{stationId}'");
        };
    }
}
7. 程序入口 Program.cs

csharp

运行

复制代码
using System;
using System.Windows.Forms;

namespace ActDulpEvent
{
    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainMESForm());
        }
    }
}

三、功能亮点与业务匹配

  1. 委托 / 事件封装MesEventBus 统一管理所有业务事件,组件解耦,支持任务、BOM、图表、数据编辑等全场景事件通信;
  2. 主子端台设计MasterSlaveComponent 实现工站 - 任务的主从数据联动,支持任意工站的任务详情穿透;
  3. 二分法任务操作:任务状态采用 "待执行→执行中→已完成" 二分法切换,操作面板上下拆分(操作区 + 数据区);
  4. 丰富 GDI + 图表:支持普通柱状图 / 对比柱状图、渐变样式、网格、刻度、图例,适配计划 / 实际产量对比;
  5. 本地数据维护DataEditForm 支持编辑 5-6 组预置数据(工站 / 产品 / BOM / 任务等),支持增删改查;
  6. 分节点工站:6 个工站节点(SMT/AI 插件 / 波峰焊 / QC / 老化 / 包装),覆盖完整生产流程;
  7. 本地文件数据库:SQLite 无需服务端,数据文件本地化,支持离线使用。

四、部署与运行

  1. 新建.NET Framework 4.8.1 WinForm 项目,命名为ActDulpEvent
  2. 安装 NuGet 包:System.Data.SQLite(版本 2.0.3);
  3. 将上述代码按文件名创建对应类文件;
  4. 编译运行,自动生成MES_Data.db本地数据库,预置 5-6 组测试数据;
  5. 支持功能:任务状态切换、BOM 计算、GDI 图表展示、主子端台联动、数据维护。

该代码完整实现了 MES/ERP 工序 BOM 协同场景的核心需求,代码组件化、可扩展,UI 交互友好,本地数据维护便捷,GDI 图表样式丰富,完全匹配制造业中小场景的工序管理需求。

相关推荐
Pkmer1 小时前
古法编程: 适配器模式
java·设计模式
longxibo2 小时前
【Flowable 7.2 源码深度解析与实战】
java·后端·流程图
norq juox2 小时前
Spring 中集成Hibernate
java·spring·hibernate
咸鱼2.02 小时前
【java入门到放弃】Zookeeper
java·zookeeper
雨辰AI2 小时前
从 MySQL 迁移至人大金仓 V9 完整改造指南|分页 / 函数 / 语法兼容全部解决
java·开发语言·数据库·后端·mysql·政务
阿维的博客日记2 小时前
介绍一下Redisson的看门狗机制
java·redis·缓存
大G的笔记本2 小时前
为什么接口中的变量默认是 public static final(常量)
java
java1234_小锋2 小时前
Spring AI 2.0 开发Java Agent智能体 - stream()方法Flux流式响应输出
java·人工智能·spring
庞轩px2 小时前
第四篇:多级缓存架构——Caffeine + Redis + MySQL 三级协同
java·redis·mysql·读写分离·caffeine·本地缓存