MES/ERP 协同场景导入导出图表展示组件

整体方案说明

基于 Winform + SqlSugar + SQLite(本地文件数据库)实现 3C 行业 MES/ERP 协同场景,涵盖物料 / BOM 数据维护、Excel 导入导出、工序排程、GDI 图表展示 核心能力,以下分模块实现核心代码(适配.NET Framework 4.8,需安装 NuGet:SqlSugarCore/NPOI/System.Data.SQLite)。


1. 核心实体定义(Models 文件夹)

1.1 3C 半成品物料实体(MaterialModel.cs)

csharp

运行

复制代码
using SqlSugar;
using System;

namespace MES_ERP.Models
{
    /// <summary>
    /// 3C电子半成品物料表(12组核心字段)
    /// </summary>
    [SugarTable("Material_3C")]
    public class MaterialModel
    {
        /// <summary>
        /// 物料ID(主键)
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int MaterialId { get; set; }

        /// <summary>
        /// 物料编码
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false)]
        public string MaterialCode { get; set; }

        /// <summary>
        /// 物料名称
        /// </summary>
        [SugarColumn(Length = 100, IsNullable = false)]
        public string MaterialName { get; set; }

        /// <summary>
        /// 规格型号
        /// </summary>
        [SugarColumn(Length = 100)]
        public string Spec { get; set; }

        /// <summary>
        /// 单位
        /// </summary>
        [SugarColumn(Length = 20)]
        public string Unit { get; set; }

        /// <summary>
        /// 库存数量
        /// </summary>
        public decimal StockQty { get; set; }

        /// <summary>
        /// 供应商编码
        /// </summary>
        [SugarColumn(Length = 50)]
        public string SupplierCode { get; set; }

        /// <summary>
        /// 采购单价
        /// </summary>
        public decimal PurchasePrice { get; set; }

        /// <summary>
        /// 生产批次
        /// </summary>
        [SugarColumn(Length = 50)]
        public string BatchNo { get; set; }

        /// <summary>
        /// 有效期
        /// </summary>
        public DateTime? ExpiryDate { get; set; }

        /// <summary>
        /// 物料类型(半成品/原材料)
        /// </summary>
        [SugarColumn(Length = 20)]
        public string MaterialType { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;
    }
}

1.2 工序工站 BOM 实体(BomModel.cs)

csharp

运行

复制代码
using SqlSugar;
using System;

namespace MES_ERP.Models
{
    /// <summary>
    /// 3C工序工站BOM表(12组核心字段)
    /// </summary>
    [SugarTable("BOM_ProcessStation")]
    public class BomModel
    {
        /// <summary>
        /// BOMID(主键)
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int BomId { get; set; }

        /// <summary>
        /// 父物料编码
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false)]
        public string ParentMaterialCode { get; set; }

        /// <summary>
        /// 子物料编码
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false)]
        public string ChildMaterialCode { get; set; }

        /// <summary>
        /// 工站编码
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false)]
        public string StationCode { get; set; }

        /// <summary>
        /// 工序编码
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false)]
        public string ProcessCode { get; set; }

        /// <summary>
        /// 工序名称
        /// </summary>
        [SugarColumn(Length = 100)]
        public string ProcessName { get; set; }

        /// <summary>
        /// 用量
        /// </summary>
        public decimal Qty { get; set; }

        /// <summary>
        /// 损耗率
        /// </summary>
        public decimal LossRate { get; set; }

        /// <summary>
        /// 工序顺序
        /// </summary>
        public int ProcessOrder { get; set; }

        /// <summary>
        /// 支轴组编号(多支轴调度)
        /// </summary>
        [SugarColumn(Length = 20)]
        public string SpindleGroupNo { get; set; }

        /// <summary>
        /// 预估工时(分钟)
        /// </summary>
        public int EstimateWorkTime { get; set; }

        /// <summary>
        /// 更新时间
        /// </summary>
        public DateTime UpdateTime { get; set; } = DateTime.Now;
    }
}

2. SqlSugar 数据库上下文(Data 文件夹 / SqlSugarContext.cs)

csharp

运行

复制代码
using MES_ERP.Models;
using SqlSugar;
using System;
using System.IO;

namespace MES_ERP.Data
{
    /// <summary>
    /// SqlSugar数据库上下文(本地SQLite文件)
    /// </summary>
    public class SqlSugarContext
    {
        public static SqlSugarClient Db { get; private set; }

        static SqlSugarContext()
        {
            // 本地数据库文件路径(程序目录下MES_ERP.db)
            string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MES_ERP.db");
            Db = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = $"Data Source={dbPath};Version=3;",
                DbType = DbType.Sqlite,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute // 从特性读取主键/自增信息
            });

            // 初始化表(不存在则创建)
            Db.CodeFirst.InitTables(typeof(MaterialModel), typeof(BomModel));

            // 日志打印(调试用)
            Db.Aop.OnLogExecuting = (sql, pars) =>
            {
                Console.WriteLine($"SQL:{sql} \r\n参数:{string.Join(",", pars.Select(p => $"{p.ParameterName}:{p.Value}"))}");
            };
        }

        /// <summary>
        /// 获取仓储(简化CRUD)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static SimpleClient<T> GetRepository<T>() where T : class, new()
        {
            return new SimpleClient<T>(Db);
        }
    }
}

3. Excel 导入导出工具(Utils 文件夹 / ExcelHelper.cs)

csharp

运行

复制代码
using MES_ERP.Models;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace MES_ERP.Utils
{
    /// <summary>
    /// Excel导入导出帮助类(固定模板)
    /// </summary>
    public class ExcelHelper
    {
        #region 模板下载
        /// <summary>
        /// 下载物料模板(固定格式)
        /// </summary>
        /// <param name="savePath">保存路径</param>
        public static void DownloadMaterialTemplate(string savePath)
        {
            // 创建Excel工作簿
            IWorkbook workbook = new XSSFWorkbook();
            ISheet sheet = workbook.CreateSheet("3C物料模板");

            // 写入表头(固定模板字段)
            string[] headers = { "物料编码", "物料名称", "规格型号", "单位", "库存数量", "供应商编码", "采购单价", "生产批次", "有效期", "物料类型" };
            IRow headerRow = sheet.CreateRow(0);
            for (int i = 0; i < headers.Length; i++)
            {
                headerRow.CreateCell(i).SetCellValue(headers[i]);
            }

            // 写入示例行
            IRow exampleRow = sheet.CreateRow(1);
            exampleRow.CreateCell(0).SetCellValue("MAT001");
            exampleRow.CreateCell(1).SetCellValue("手机主板半成品");
            exampleRow.CreateCell(2).SetCellValue("X90 Pro");
            exampleRow.CreateCell(3).SetCellValue("PCS");
            exampleRow.CreateCell(4).SetCellValue(1000);
            exampleRow.CreateCell(5).SetCellValue("SUP001");
            exampleRow.CreateCell(6).SetCellValue(200.5);
            exampleRow.CreateCell(7).SetCellValue("B202405");
            exampleRow.CreateCell(8).SetCellValue("2025-05-01");
            exampleRow.CreateCell(9).SetCellValue("半成品");

            // 保存文件
            using (FileStream fs = new FileStream(savePath, FileMode.Create))
            {
                workbook.Write(fs);
            }
            MessageBox.Show($"物料模板已下载至:{savePath}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        #endregion

        #region 导入物料数据
        /// <summary>
        /// 从Excel导入物料数据(验证固定模板)
        /// </summary>
        /// <param name="filePath">Excel文件路径</param>
        /// <returns></returns>
        public static List<MaterialModel> ImportMaterialFromExcel(string filePath)
        {
            List<MaterialModel> list = new List<MaterialModel>();
            IWorkbook workbook = null;

            // 读取Excel文件
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                if (filePath.EndsWith(".xlsx"))
                    workbook = new XSSFWorkbook(fs);
                else if (filePath.EndsWith(".xls"))
                    workbook = new HSSFWorkbook(fs);
                else
                    throw new Exception("仅支持.xlsx/.xls格式");
            }

            ISheet sheet = workbook.GetSheetAt(0);
            if (sheet == null) throw new Exception("Excel无有效工作表");

            // 验证表头
            IRow headerRow = sheet.GetRow(0);
            string[] requiredHeaders = { "物料编码", "物料名称", "规格型号", "单位" };
            foreach (var header in requiredHeaders)
            {
                bool isExist = false;
                for (int i = 0; i < headerRow.LastCellNum; i++)
                {
                    if (headerRow.GetCell(i)?.ToString() == header)
                    {
                        isExist = true;
                        break;
                    }
                }
                if (!isExist) throw new Exception($"模板格式错误:缺少表头【{header}】");
            }

            // 读取数据行
            for (int i = 1; i <= sheet.LastRowNum; i++)
            {
                IRow row = sheet.GetRow(i);
                if (row == null || row.GetCell(0) == null) continue;

                var model = new MaterialModel
                {
                    MaterialCode = row.GetCell(0)?.ToString()?.Trim(),
                    MaterialName = row.GetCell(1)?.ToString()?.Trim(),
                    Spec = row.GetCell(2)?.ToString()?.Trim(),
                    Unit = row.GetCell(3)?.ToString()?.Trim(),
                    StockQty = Convert.ToDecimal(row.GetCell(4)?.ToString() ?? "0"),
                    SupplierCode = row.GetCell(5)?.ToString()?.Trim(),
                    PurchasePrice = Convert.ToDecimal(row.GetCell(6)?.ToString() ?? "0"),
                    BatchNo = row.GetCell(7)?.ToString()?.Trim(),
                    ExpiryDate = string.IsNullOrEmpty(row.GetCell(8)?.ToString()) ? null : (DateTime?)Convert.ToDateTime(row.GetCell(8).ToString()),
                    MaterialType = row.GetCell(9)?.ToString()?.Trim(),
                    CreateTime = DateTime.Now
                };
                list.Add(model);
            }
            return list;
        }
        #endregion
    }
}

4. 多支轴工序排程模块(Scheduling 文件夹 / ProcessScheduler.cs)

csharp

运行

复制代码
using MES_ERP.Models;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace MES_ERP.Scheduling
{
    /// <summary>
    /// 多支轴工序排程调度器(多线程任务执行)
    /// </summary>
    public class ProcessScheduler
    {
        /// <summary>
        /// 支轴组任务队列
        /// </summary>
        private Dictionary<string, List<BomModel>> _spindleTaskQueue = new Dictionary<string, List<BomModel>>();

        /// <summary>
        /// 排程完成回调
        /// </summary>
        public event Action<string, string> SchedulingCompleted;

        /// <summary>
        /// 加载支轴组任务
        /// </summary>
        /// <param name="spindleGroupNo">支轴组编号</param>
        /// <param name="bomList">BOM工序列表</param>
        public void LoadSpindleTask(string spindleGroupNo, List<BomModel> bomList)
        {
            if (!_spindleTaskQueue.ContainsKey(spindleGroupNo))
                _spindleTaskQueue.Add(spindleGroupNo, new List<BomModel>());
            _spindleTaskQueue[spindleGroupNo] = bomList;
        }

        /// <summary>
        /// 启动多支轴并行排程
        /// </summary>
        public void StartMultiSpindleScheduling()
        {
            foreach (var spindleGroup in _spindleTaskQueue)
            {
                // 每个支轴组启动独立线程执行工序调度
                Thread thread = new Thread(new ParameterizedThreadStart(ExecuteSpindleTask));
                thread.IsBackground = true;
                thread.Start(spindleGroup);
            }
        }

        /// <summary>
        /// 执行单个支轴组任务
        /// </summary>
        /// <param name="obj">支轴组+任务列表</param>
        private void ExecuteSpindleTask(object obj)
        {
            var spindleGroup = (KeyValuePair<string, List<BomModel>>)obj;
            string spindleNo = spindleGroup.Key;
            var taskList = spindleGroup.Value;

            // 按工序顺序排序
            taskList.Sort((a, b) => a.ProcessOrder.CompareTo(b.ProcessOrder));

            // 模拟工序执行(实际场景替换为真实业务逻辑)
            foreach (var task in taskList)
            {
                string log = $"[{DateTime.Now:HH:mm:ss}] 支轴组{spindleNo} - 执行工序:{task.ProcessCode}({task.ProcessName}),工站:{task.StationCode},耗时:{task.EstimateWorkTime}分钟";
                // 跨线程更新UI(需通过Invoke)
                SchedulingCompleted?.Invoke(spindleNo, log);
                Thread.Sleep(task.EstimateWorkTime * 100); // 模拟耗时(100ms=1分钟)
            }

            SchedulingCompleted?.Invoke(spindleNo, $"[{DateTime.Now:HH:mm:ss}] 支轴组{spindleNo} 所有工序执行完成!");
        }

        /// <summary>
        /// 清空支轴任务队列
        /// </summary>
        public void ClearTaskQueue()
        {
            _spindleTaskQueue.Clear();
        }
    }
}

5. GDI + 图表绘制控件(Controls 文件夹 / ChartControl.cs)

csharp

运行

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

namespace MES_ERP.Controls
{
    /// <summary>
    /// GDI+自定义图表控件(柱状图+仪表盘)
    /// </summary>
    public class ChartControl : Control
    {
        /// <summary>
        /// 柱状图数据(工序工时统计)
        /// </summary>
        public Dictionary<string, int> BarChartData { get; set; } = new Dictionary<string, int>();

        /// <summary>
        /// 仪表盘数值(0-100)
        /// </summary>
        public int GaugeValue { get; set; } = 0;

        public ChartControl()
        {
            DoubleBuffered = true; // 双缓冲防闪烁
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Graphics g = e.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias; // 抗锯齿

            // 绘制背景
            g.Clear(Color.White);

            // 1. 绘制柱状图(左侧)
            DrawBarChart(g);

            // 2. 绘制仪表盘(右侧)
            DrawGauge(g);
        }

        /// <summary>
        /// 绘制工序工时柱状图
        /// </summary>
        /// <param name="g"></param>
        private void DrawBarChart(Graphics g)
        {
            if (BarChartData.Count == 0) return;

            // 绘图区域
            Rectangle barRect = new Rectangle(20, 20, this.Width / 2 - 40, this.Height - 40);
            g.DrawRectangle(Pens.Gray, barRect);

            // 计算刻度
            int maxValue = BarChartData.Values.Max();
            float unitY = barRect.Height / (float)maxValue;

            // 绘制柱子
            float barWidth = barRect.Width / (float)BarChartData.Count - 10;
            float x = barRect.X + 10;
            foreach (var item in BarChartData)
            {
                // 柱子区域
                float barHeight = item.Value * unitY;
                RectangleF bar = new RectangleF(x, barRect.Bottom - barHeight, barWidth, barHeight);
                
                // 渐变填充
                using (LinearGradientBrush brush = new LinearGradientBrush(bar, Color.LightBlue, Color.Blue, LinearGradientMode.Vertical))
                {
                    g.FillRectangle(brush, bar);
                }
                g.DrawRectangle(Pens.DarkBlue, Rectangle.Round(bar));

                // 绘制文字
                g.DrawString(item.Key, Font, Brushes.Black, x, barRect.Bottom - barHeight - 20);
                g.DrawString(item.Value.ToString(), Font, Brushes.Red, x + barWidth / 2 - 10, barRect.Bottom - barHeight - 40);

                x += barWidth + 10;
            }

            // 绘制标题
            g.DrawString("3C工序工时统计(分钟)", Font, Brushes.Black, barRect.X + barRect.Width / 2 - 50, barRect.Y - 20);
        }

        /// <summary>
        /// 绘制排程完成率仪表盘
        /// </summary>
        /// <param name="g"></param>
        private void DrawGauge(Graphics g)
        {
            // 绘图区域
            int gaugeRadius = Math.Min(this.Width / 4, this.Height / 2 - 20);
            Point gaugeCenter = new Point(this.Width - this.Width / 4 - 20, this.Height / 2);

            // 绘制外圆
            g.DrawEllipse(Pens.DarkGray, gaugeCenter.X - gaugeRadius, gaugeCenter.Y - gaugeRadius, gaugeRadius * 2, gaugeRadius * 2);

            // 绘制扇形背景(0-100)
            using (Brush bgBrush = new SolidBrush(Color.LightGray))
            {
                g.FillPie(bgBrush, gaugeCenter.X - gaugeRadius, gaugeCenter.Y - gaugeRadius, gaugeRadius * 2, gaugeRadius * 2, 135, 270);
            }

            // 绘制进度扇形
            float angle = GaugeValue * 2.7f; // 270°对应100%
            using (Brush progressBrush = new LinearGradientBrush(new Rectangle(gaugeCenter.X - gaugeRadius, gaugeCenter.Y - gaugeRadius, gaugeRadius * 2, gaugeRadius * 2), Color.GreenYellow, Color.Green, LinearGradientMode.ForwardDiagonal))
            {
                g.FillPie(progressBrush, gaugeCenter.X - gaugeRadius, gaugeCenter.Y - gaugeRadius, gaugeRadius * 2, gaugeRadius * 2, 135, angle);
            }

            // 绘制指针
            float radian = (135 + angle) * (float)Math.PI / 180;
            int pointerLength = gaugeRadius - 20;
            PointF pointerEnd = new PointF(
                gaugeCenter.X + (float)Math.Cos(radian) * pointerLength,
                gaugeCenter.Y + (float)Math.Sin(radian) * pointerLength
            );
            g.DrawLine(new Pen(Color.Red, 3), gaugeCenter, pointerEnd);

            // 绘制数值
            g.DrawString($"{GaugeValue}%", new Font(Font.FontFamily, 20, FontStyle.Bold), Brushes.Red, gaugeCenter.X - 20, gaugeCenter.Y + 20);
            g.DrawString("排程完成率", Font, Brushes.Black, gaugeCenter.X - 30, gaugeCenter.Y - gaugeRadius - 20);
        }

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

6. Winform 主界面(FormMain.cs)

csharp

运行

复制代码
using MES_ERP.Controls;
using MES_ERP.Data;
using MES_ERP.Models;
using MES_ERP.Scheduling;
using MES_ERP.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace MES_ERP
{
    public partial class FormMain : Form
    {
        private ProcessScheduler _scheduler = new ProcessScheduler();
        private ChartControl _chartControl = new ChartControl();

        public FormMain()
        {
            InitializeComponent();
            InitForm();
        }

        /// <summary>
        /// 初始化窗体
        /// </summary>
        private void InitForm()
        {
            // 1. 初始化图表控件
            _chartControl.Dock = DockStyle.Fill;
            panelChart.Controls.Add(_chartControl);

            // 2. 绑定排程回调
            _scheduler.SchedulingCompleted += Scheduler_SchedulingCompleted;

            // 3. 加载物料数据到DataGridView
            LoadMaterialData();

            // 4. 初始化支轴组下拉框
            cboSpindleGroup.Items.AddRange(new string[] { "SP001", "SP002", "SP003", "SP004" });
            cboSpindleGroup.SelectedIndex = 0;
        }

        #region 物料数据维护
        /// <summary>
        /// 加载物料数据
        /// </summary>
        private void LoadMaterialData()
        {
            var repo = SqlSugarContext.GetRepository<MaterialModel>();
            dgvMaterial.DataSource = repo.GetList();
        }

        /// <summary>
        /// 模板下载按钮点击
        /// </summary>
        private void btnDownloadTemplate_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog sfd = new SaveFileDialog())
            {
                sfd.Filter = "Excel文件(*.xlsx)|*.xlsx";
                sfd.FileName = "3C物料模板.xlsx";
                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    ExcelHelper.DownloadMaterialTemplate(sfd.FileName);
                }
            }
        }

        /// <summary>
        /// Excel导入按钮点击
        /// </summary>
        private void btnImportExcel_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                ofd.Filter = "Excel文件(*.xlsx;*.xls)|*.xlsx;*.xls";
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        var materialList = ExcelHelper.ImportMaterialFromExcel(ofd.FileName);
                        var repo = SqlSugarContext.GetRepository<MaterialModel>();
                        repo.InsertRange(materialList); // 批量插入
                        LoadMaterialData(); // 刷新数据
                        MessageBox.Show($"成功导入{materialList.Count}条物料数据", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"导入失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }
        #endregion

        #region 工序排程
        /// <summary>
        /// 加载支轴组工序任务
        /// </summary>
        private void btnLoadTask_Click(object sender, EventArgs e)
        {
            try
            {
                string spindleGroupNo = cboSpindleGroup.SelectedItem.ToString();
                // 模拟从数据库加载该支轴组的BOM工序数据
                var bomRepo = SqlSugarContext.GetRepository<BomModel>();
                var bomList = bomRepo.GetList(b => b.SpindleGroupNo == spindleGroupNo);
                if (bomList.Count == 0)
                {
                    MessageBox.Show($"支轴组{spindleGroupNo}无工序数据!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
                // 加载任务到调度器
                _scheduler.LoadSpindleTask(spindleGroupNo, bomList);
                txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 已加载支轴组{spindleGroupNo}工序任务,共{bomList.Count}道工序\r\n");
            }
            catch (Exception ex)
            {
                txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 加载任务失败:{ex.Message}\r\n");
            }
        }

        /// <summary>
        /// 启动排程按钮点击
        /// </summary>
        private void btnStartScheduling_Click(object sender, EventArgs e)
        {
            txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] 启动多支轴工序排程...\r\n");
            _scheduler.StartMultiSpindleScheduling();

            // 模拟图表数据(实际从排程结果获取)
            _chartControl.BarChartData = new Dictionary<string, int>
            {
                { "贴片", 15 },
                { "焊接", 25 },
                { "测试", 20 },
                { "组装", 30 },
                { "包装", 10 }
            };
            _chartControl.GaugeValue = 0;
            _chartControl.RefreshChart();

            // 模拟完成率递增(实际从排程进度更新)
            Timer timer = new Timer();
            timer.Interval = 1000;
            timer.Tick += (s, ev) =>
            {
                if (_chartControl.GaugeValue < 100)
                {
                    _chartControl.GaugeValue += 1;
                    _chartControl.RefreshChart();
                }
                else
                {
                    timer.Stop();
                }
            };
            timer.Start();
        }

        /// <summary>
        /// 排程完成回调(跨线程更新UI)
        /// </summary>
        /// <param name="spindleNo"></param>
        /// <param name="log"></param>
        private void Scheduler_SchedulingCompleted(string spindleNo, string log)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new Action<string, string>(Scheduler_SchedulingCompleted), spindleNo, log);
            }
            else
            {
                txtLog.AppendText(log + "\r\n");
            }
        }
        #endregion

        #region 窗体设计器自动生成代码(简化版)
        private System.ComponentModel.IContainer components = null;
        private Panel panelChart;
        private DataGridView dgvMaterial;
        private Button btnDownloadTemplate;
        private Button btnImportExcel;
        private ComboBox cboSpindleGroup;
        private Button btnLoadTask;
        private Button btnStartScheduling;
        private TextBox txtLog;
        private Label label1;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.panelChart = new System.Windows.Forms.Panel();
            this.dgvMaterial = new System.Windows.Forms.DataGridView();
            this.btnDownloadTemplate = new System.Windows.Forms.Button();
            this.btnImportExcel = new System.Windows.Forms.Button();
            this.cboSpindleGroup = new System.Windows.Forms.ComboBox();
            this.btnLoadTask = new System.Windows.Forms.Button();
            this.btnStartScheduling = new System.Windows.Forms.Button();
            this.txtLog = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.dgvMaterial)).BeginInit();
            this.SuspendLayout();
            // 
            // panelChart
            // 
            this.panelChart.Location = new System.Drawing.Point(12, 12);
            this.panelChart.Name = "panelChart";
            this.panelChart.Size = new System.Drawing.Size(800, 300);
            this.panelChart.TabIndex = 0;
            // 
            // dgvMaterial
            // 
            this.dgvMaterial.Location = new System.Drawing.Point(12, 318);
            this.dgvMaterial.Name = "dgvMaterial";
            this.dgvMaterial.RowTemplate.Height = 23;
            this.dgvMaterial.Size = new System.Drawing.Size(500, 200);
            this.dgvMaterial.TabIndex = 1;
            // 
            // btnDownloadTemplate
            // 
            this.btnDownloadTemplate.Location = new System.Drawing.Point(518, 318);
            this.btnDownloadTemplate.Name = "btnDownloadTemplate";
            this.btnDownloadTemplate.Size = new System.Drawing.Size(100, 30);
            this.btnDownloadTemplate.TabIndex = 2;
            this.btnDownloadTemplate.Text = "下载物料模板";
            this.btnDownloadTemplate.Click += new System.EventHandler(this.btnDownloadTemplate_Click);
            // 
            // btnImportExcel
            // 
            this.btnImportExcel.Location = new System.Drawing.Point(624, 318);
            this.btnImportExcel.Name = "btnImportExcel";
            this.btnImportExcel.Size = new System.Drawing.Size(100, 30);
            this.btnImportExcel.TabIndex = 3;
            this.btnImportExcel.Text = "导入Excel数据";
            this.btnImportExcel.Click += new System.EventHandler(this.btnImportExcel_Click);
            // 
            // cboSpindleGroup
            // 
            this.cboSpindleGroup.Location = new System.Drawing.Point(518, 354);
            this.cboSpindleGroup.Name = "cboSpindleGroup";
            this.cboSpindleGroup.Size = new System.Drawing.Size(100, 20);
            this.cboSpindleGroup.TabIndex = 4;
            // 
            // btnLoadTask
            // 
            this.btnLoadTask.Location = new System.Drawing.Point(624, 354);
            this.btnLoadTask.Name = "btnLoadTask";
            this.btnLoadTask.Size = new System.Drawing.Size(100, 30);
            this.btnLoadTask.TabIndex = 5;
            this.btnLoadTask.Text = "加载支轴任务";
            this.btnLoadTask.Click += new System.EventHandler(this.btnLoadTask_Click);
            // 
            // btnStartScheduling
            // 
            this.btnStartScheduling.Location = new System.Drawing.Point(518, 390);
            this.btnStartScheduling.Name = "btnStartScheduling";
            this.btnStartScheduling.Size = new System.Drawing.Size(206, 30);
            this.btnStartScheduling.TabIndex = 6;
            this.btnStartScheduling.Text = "启动多支轴工序排程";
            this.btnStartScheduling.Click += new System.EventHandler(this.btnStartScheduling_Click);
            // 
            // txtLog
            // 
            this.txtLog.Location = new System.Drawing.Point(12, 524);
            this.txtLog.Multiline = true;
            this.txtLog.Name = "txtLog";
            this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtLog.Size = new System.Drawing.Size(800, 150);
            this.txtLog.TabIndex = 7;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(12, 508);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(53, 12);
            this.label1.TabIndex = 8;
            this.label1.Text = "排程日志:";
            // 
            // FormMain
            // 
            this.ClientSize = new System.Drawing.Size(824, 686);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.txtLog);
            this.Controls.Add(this.btnStartScheduling);
            this.Controls.Add(this.btnLoadTask);
            this.Controls.Add(this.cboSpindleGroup);
            this.Controls.Add(this.btnImportExcel);
            this.Controls.Add(this.btnDownloadTemplate);
            this.Controls.Add(this.dgvMaterial);
            this.Controls.Add(this.panelChart);
            this.Name = "FormMain";
            this.Text = "3C行业MES/ERP协同系统";
            ((System.ComponentModel.ISupportInitialize)(this.dgvMaterial)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();
        }
        #endregion
    }
}

7. 程序入口(Program.cs)

csharp

运行

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

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

核心功能说明

  1. 本地文件数据库:基于 SQLite 实现,数据库文件存储在程序目录下,无需额外安装数据库;
  2. Excel 导入导出:提供固定模板下载,导入时验证模板格式,支持 3C 物料批量导入;
  3. 多支轴工序排程:每个支轴组独立线程执行工序任务,模拟多指令端并行调度;
  4. GDI 图表展示:自定义控件实现柱状图(工序工时)+ 仪表盘(排程完成率);
  5. 数据维护:基于 SqlSugar 实现物料 / BOM 数据的 CRUD,适配 3C 行业 11-12 组核心字段。

扩展建议

  1. 补充 BOM 数据的导入导出、工序工站维护功能(复用 ExcelHelper 逻辑);
  2. 增加排程进度实时监控、异常暂停 / 重启功能;
  3. 优化 GDI 图表交互(如鼠标悬浮显示详情);
  4. 增加数据备份 / 恢复、权限控制等企业级特性;
  5. 适配.NET 6/7 Winform(仅需少量语法调整)。
相关推荐
晓13131 小时前
【Cocos Creator 3.x】篇——第一章 简介
前端·javascript·游戏引擎
周杰伦fans1 小时前
AutoCAD2016经典模式不见了-设置回14版本前的经典工作空间
服务器·c语言·前端
Front思1 小时前
shopify前端开发
前端
风骏时光牛马2 小时前
Julia常见问题汇总与代码示例
前端
ZC跨境爬虫2 小时前
跟着 MDN 学JavaScript day_10:数组——数据的有序集合
android·java·开发语言·前端·javascript
广州华水科技2 小时前
如何利用单北斗变形监测实现大坝安全监测?
前端
hxy06012 小时前
Flutter showModalBottomSheet等弹窗宽度问题
前端·flutter
Wireless_wifi62 小时前
IPQ9574 + WiFi 7: Building the Foundation for Scalable Edge AI Deployments
前端·人工智能·edge