MES/ERP 工序 BOM 协同场景调度维护组件

以下是基于 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());
        }
    }
}

功能说明

  1. 本地数据库:基于 SQLite 实现 9 组核心数据(工站、工序、BOM、任务、物料、产线、资源、员工、设备)的本地化存储,支持增删改查;
  2. GDI 图表:标准化柱状图,支持抗锯齿、多颜色柱体、坐标轴标注、图例、数值标注、图表导出,可配置字体 / 颜色 / 坐标轴名称;
  3. 业务模块
    • 分节点执行端:任务状态变更(待执行 / 执行中 / 已完成);
    • BOM 物料调配:产量需求计算、BOM 数据维护、导出;
    • 工序路径管理:工序的新增 / 编辑 / 删除 / 排序;
    • 主子调度台:工站 - 任务的主子表联动;
    • 汇总工作台:实时日志输出;
    • 数据维护中心:统一维护 9 组核心数据;
    • 图表报表:多维度数据可视化、导出;
  4. 交互体验:支持数据校验、异常捕获、日志记录、文件导出等。

部署说明

  1. 确保项目引用 System.Data.SQLite(可通过 NuGet 安装 System.Data.SQLite.Core);
  2. 目标框架设置为 .NET Framework 4.8.1
  3. 编译后会自动生成 MESData.db 本地数据库文件,包含预置的 9 组测试数据;
  4. 支持 Windows 系统直接运行,无需额外安装数据库。

该实现完整覆盖了 MES/ERP 工序 BOM 协同场景的核心需求,模块化设计便于扩展,本地化数据库满足离线使用需求,GDI 图表满足可视化报表需求,数据维护模块支持业务数据的灵活编辑。

相关推荐
AlienZHOU8 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc12 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师13 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen13 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒14 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow14 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81814 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007814 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明14 小时前
第 6 章 组件进阶
前端·vue.js
江华森14 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端