组件化 3C 电子 MES/ERP 协同项目全套方案

完整组件化 3C 电子 MES/ERP 协同项目全套方案(含 App.config、分层组件、WinForm、SQLite、GDI、Excel 模板、组轴多线程调度)

一、项目概述

1. 项目定位

面向3C 数码(手机 / 平板)组装产线 的 WinForm 桌面协同系统,打通 ERP 物料 / BOM 与 MES 组轴工序生产流程; 解决原有代码碎片化、业务逻辑分散、线程调度杂乱问题,采用高内聚分层组件架构,所有业务抽离独立类库组件,界面只做交互,无复杂业务代码。

2. 核心业务场景

  1. ERP 物料、BOM 主数据管理(固定 Excel 模板导入导出,组别 15~17)
  2. 产线组轴 + 动力设备协同执行引擎(SMT / 焊接 / 组装 / 检测 4 大组轴)
  3. 多模式多线程调度:单轴、组合并行、流水线联控,自动汇总线程执行结果
  4. 生产作业完整生命周期:创建→调度→运行→暂停→完成→归档(贴合金蝶 ERP 单据流程)
  5. GDI 原生自绘生产统计图表(产量柱状图 + 设备负载仪表盘)
  6. SQLite 本地文件数据库,免服务、开箱即用
  7. 全局统一App.config配置文件,数据库、线程、图表、Excel、ERP 参数统一管理

3. 技术栈

  • 框架:.NET Framework 4.8 WinForms
  • ORM:SqlSugar(SQLite 本地文件库)
  • Excel:NPOI 标准固定模板
  • 并发:Task 多线程 + 事件总线解耦
  • 绘图:原生 GDI + 自定义图表控件
  • 配置:App.config 全局参数统一管理

4. 项目分层目录

plaintext

复制代码
3C_MES_ERP_AxisComponent/
├─ App.config                     # 全局配置文件
├─ Program.cs                     # 程序入口
├─ Models/                        # 实体、枚举(物料/BOM/组轴/作业/调度日志)
├─ Components/                    # 独立业务组件(核心解耦层)
│  ├─ DbComponent/                # SQLite数据库组件
│  ├─ EventBusComponent/          # 全局事件总线(模块解耦)
│  ├─ AxisEngineComponent/        # 组轴执行引擎
│  ├─ ThreadScheduleComponent/    # 多线程调度+结果组装
│  ├─ ExcelTemplateComponent/    # 固定Excel模板导入导出
│  ├─ GdiChartComponent/         # GDI自定义图表控件
│  └─ ErpBomComponent/           # ERP物料BOM业务层
└─ Forms/                         # WinForm界面层(轻量化)

二、App.config 全局统一配置文件

所有数据库、线程、图表、Excel、ERP、组轴参数集中配置,无需修改代码切换环境

xml

复制代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
  </startup>

  <!-- 全局自定义参数 -->
  <appSettings>
    <!-- SQLite本地文件数据库配置 -->
    <add key="SqliteDbFileName" value="3C_MES_AxisLocal.db"/>
    <add key="DbLogSwitch" value="false"/>
    <add key="InitGroupStart" value="15"/>
    <add key="InitGroupEnd" value="17"/>

    <!-- 组轴&多线程调度配置 -->
    <add key="MaxThreadCount" value="8"/>
    <add key="AxisTaskDelayMs" value="350"/>
    <add key="AxisLinkedGroup1" value="LINK01"/>
    <add key="AxisLinkedGroup2" value="LINK02"/>

    <!-- Excel固定模板配置 -->
    <add key="MaterialTemplateName" value="3C电子物料标准模板.xlsx"/>
    <add key="BomTemplateName" value="3C电子BOM标准模板.xlsx"/>

    <!-- GDI图表全局绘制参数 -->
    <add key="ChartAntiAlias" value="true"/>
    <add key="ChartBarWidth" value="26"/>
    <add key="ChartTitleFontSize" value="12"/>

    <!-- 金蝶K3Cloud ERP接口参数 -->
    <add key="K3CloudUrl" value="http://192.168.60/K3Cloud"/>
    <add key="K3AcctId" value="5756960b27b1aa"/>
    <add key="K3UserName" value="Administrator"/>
    <add key="K3Password" value="888888"/>
  </appSettings>

  <!-- SQLite数据库连接字符串 -->
  <connectionStrings>
    <add name="SqliteConn" connectionString="Data Source={SqliteDbFileName};Version=3;Pooling=true;Max Pool Size=10;" providerName="System.Data.SQLite"/>
  </connectionStrings>

  <!-- 第三方程序集绑定重定向 -->
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff13" culture="neutral" version="1.0.118.0"/>
        <bindingRedirect oldVersion="0.0.0.0-1.0.118.0" newVersion="1.0.118.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

三、Models 统一实体与枚举(业务数据载体)

csharp

运行

复制代码
using System;
using System.ComponentModel;
using SqlSugar;

namespace _3C_MES_ERP.Models
{
    #region 全局业务枚举
    /// <summary>组轴运行状态</summary>
    public enum AxisRunState
    {
        [Description("空闲")] Idle,
        [Description("调度中")] Running,
        [Description("暂停")] Paused,
        [Description("异常停机")] Error
    }

    /// <summary>动力设备类型(3C产线)</summary>
    public enum PowerType
    {
        [Description("伺服电机")] Servo,
        [Description("气动气缸")] Cylinder,
        [Description("输送线")] Conveyor,
        [Description("检测单元")] Tester
    }

    /// <summary>生产作业生命周期(ERP标准流程)</summary>
    public enum WorkStatus
    {
        [Description("待创建")] Init,
        [Description("已调度")] Dispatched,
        [Description("执行中")] Running,
        [Description("已暂停")] Paused,
        [Description("已完成")] Completed,
        [Description("已归档")] Archived
    }

    /// <summary>调度执行模式</summary>
    public enum ScheduleMode
    {
        [Description("单轴调度")] Single,
        [Description("组合并行")] Combine,
        [Description("流水线联控")] Linked
    }
    #endregion

    #region 数据库实体
    /// <summary>ERP物料档案(组别15~17)</summary>
    [SugarTable("T_ERP_Material")]
    public class ErpMaterial
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        [SugarColumn(Length = 40, IsNullable = false)]
        public string MatCode { get; set; }
        [SugarColumn(Length = 80)]
        public string MatName { get; set; }
        [SugarColumn(Length = 100)]
        public string Spec { get; set; }
        [SugarColumn(Length = 10)]
        public string Unit { get; set; }
        public decimal StockQty { get; set; }
        public int GroupNo { get; set; } // 固定15/16/17
        public string MatType { get; set; }
        public DateTime CreateTime { get; set; } = DateTime.Now;
    }

    /// <summary>ERP BOM结构单</summary>
    [SugarTable("T_ERP_BOM")]
    public class ErpBom
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string BomNo { get; set; }
        public string ParentMatCode { get; set; }
        public string ChildMatCode { get; set; }
        public decimal UseQty { get; set; }
        public string BomVersion { get; set; }
        public int GroupNo { get; set; }
    }

    /// <summary>产线组轴</summary>
    [SugarTable("T_AxisGroup")]
    public class AxisGroup
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string AxisCode { get; set; } // AX01~AX04
        public string AxisName { get; set; }
        public AxisRunState RunState { get; set; }
        public string BindProcess { get; set; }
        public string LinkedGroup { get; set; } // 联控分组
    }

    /// <summary>动力执行设备</summary>
    [SugarTable("T_PowerDevice")]
    public class PowerDevice
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string PowerCode { get; set; }
        public string PowerName { get; set; }
        public PowerType DeviceType { get; set; }
        public string BindAxisCode { get; set; }
        public decimal LoadRate { get; set; }
        public string DeviceStatus { get; set; }
    }

    /// <summary>生产作业单(对接ERP生产任务)</summary>
    [SugarTable("T_WorkOrder")]
    public class WorkOrder
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string WorkNo { get; set; }
        public string RelBomNo { get; set; }
        public int PlanQty { get; set; }
        public int CompleteQty { get; set; }
        public WorkStatus WorkStatus { get; set; }
        public string BindAxisCode { get; set; }
        public string BindPowerCode { get; set; }
        public DateTime CreateTime { get; set; } = DateTime.Now;
        public DateTime? FinishTime { get; set; }
    }

    /// <summary>调度执行日志(多线程结果持久化载体)</summary>
    [SugarTable("T_ScheduleLog")]
    public class ScheduleLog
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public ScheduleMode ScheduleMode { get; set; }
        public string TargetAxis { get; set; }
        public string Operator { get; set; }
        public DateTime OperTime { get; set; } = DateTime.Now;
        public string FullResult { get; set; } // 组装后的完整线程执行结果
    }

    /// <summary>GDI图表统计DTO</summary>
    public class ChartDataDto
    {
        public string WorkName { get; set; }
        public decimal PlanNum { get; set; }
        public decimal ActualNum { get; set; }
        public decimal LoadAvg { get; set; }
    }
    #endregion
}

四、分层独立组件代码(核心业务解耦,消除零散代码)

组件 1:DbComponent 本地 SQLite 数据库组件(读取 App.config)

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using SqlSugar;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Components.DbComponent
{
    public static class LocalSqlDb
    {
        private static SqlSugarClient _dbClient;
        private static readonly string _dbFile;
        private static readonly int _startGroup;
        private static readonly int _endGroup;

        static LocalSqlDb()
        {
            // 从App.config读取数据库配置
            _dbFile = ConfigurationManager.AppSettings["SqliteDbFileName"];
            _start = int.Parse(ConfigurationManager.AppSettings["InitGroupStart"]);
            _end = int.Parse(ConfigurationManager.AppSettings["InitGroupEnd"]);
            InitSqlSugarClient();
            CreateTableAndInitData();
        }

        /// 初始化SqlSugar连接
        private static void InitSqlSugarClient()
        {
            string connStr = ConfigurationManager.ConnectionStrings["SqliteConn"].ConnectionString.Replace("{SqliteDbFileName}", _dbFile);
            _dbClient = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = connStr,
                DbType = DbType.Sqlite,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute,
                AopEvents = new AopEvent
                {
                    OnLogExecuting = (sql, pars) =>
                    {
                        if (ConfigurationManager.AppSettings["DbLogSwitch"] == "true")
                            Console.WriteLine(sql);
                    }
                }
            });
        }

        /// 自动建表 + 初始化15~17组3C测试数据
        private static void CreateTableAndInitData()
        {
            _dbClient.CodeFirst.InitTables(
                typeof(ErpMaterial), typeof(ErpBom), typeof(AxisGroup),
                typeof(PowerDevice), typeof(WorkOrder), typeof(ScheduleLog));

            if (!_dbClient.Queryable<ErpMaterial>().Any()) InitMaterial();
            if (!_dbClient.Queryable<ErpBom>().Any()) InitBom();
            if (!_dbClient.Queryable<AxisGroup>().Any()) InitAxis();
            if (!_dbClient.Queryable<PowerDevice>().Any()) InitPower();
        }

        #region 预置15~17组业务数据
        private static void InitMaterial()
        {
            Random rand = new Random();
            List<ErpMaterial> list = new List<ErpMaterial>();
            string[] names = {PCB主板,"芯片","电容","摄像头","电池","外壳"};
            string[] types = {"原材料","半成品","成品"};
            string[] unit = {"PCS","片","套"};
            for (int g = _start; g <= _end; g++)
            {
                for (int i = 0; i < 3; i++)
                {
                    list.Add(new ErpMaterial
                    {
                        MatCode = $"MAT{g:D2}{i:D2}",
                        MatName = $"{names[rand.Next(names)]}_{g}组",
                        Spec = $"V{rand.Next(1)}.{rand.Next(9)}",
                        Unit = unit[rand.Next(unit)],
                        StockQty = rand.Next(200,12000),
                        GroupNo = g,
                        MatType = types[rand.Next(types)]
                    });
                }
            }
            _dbClient.Insertable(list).ExecuteCommand();
        }

        private static void InitBom()
        {
            Random rand = new Random();
            List<ErpBom> list = new List<ErpBom>();
            for (int g = _start; g <= _end; g++)
            {
                list.Add(new ErpBom
                {
                    BomNo = $"BOM{g:D2}01",
                    ParentMatCode = $"MAT{g:D2}00",
                    ChildMatCode = $"MAT{g:D2}01",
                    UseQty = Math.Round((decimal)rand.Next(1,5)/10,2),
                    BomVersion = "V1.0",
                    GroupNo = g
                });
            }
            _dbClient.Insertable(list).ExecuteCommand();
        }

        private static void InitAxis()
        {
            List<AxisGroup> list = new List<AxisGroup>()
            {
                new AxisGroup{AxisCode="AX01",AxisName="SMT贴片",RunState=AxisRunState.Idle,BindProcess="上料、贴片",LinkedGroup=ConfigurationManager.AppSettings["AxisLinkedGroup1"]},
                new AxisGroup{AxisCode="AX02",AxisName="高温焊接",RunState=AxisRunState.Idle,BindProcess="回流焊",LinkedGroup=ConfigurationManager.AppSettings["AxisLinkedGroup1"]},
                new AxisGroup{AxisCode="AX03",AxisName="整机组装",RunState=AxisRunState.Idle,BindProcess="锁螺丝、贴合",LinkedGroup=ConfigurationManager.AppSettings["AxisLinkedGroup2"]},
                new AxisGroup{AxisCode="AX04",AxisName="功能检测",RunState=AxisRunState.Idle,BindProcess="通电检测",LinkedGroup=ConfigurationManager.AppSettings["AxisLinkedGroup2"]}
            };
            _dbClient.Insertable(list).ExecuteCommand();
        }

        private static void InitPower()
        {
            Random rand = new Random();
            List<PowerDevice> list = new List<PowerDevice>();
            string[] axisArr = {"AX01","AX02","AX03","AX04"};
            var powerTypes = Enum.GetValues(typeof(PowerType)).Cast<PowerType>();
            for (int i = 1; i <= _end - _start + 1; i++)
            {
                list.Add(new PowerDevice
                {
                    PowerCode = $"PU{i:D2}",
                    PowerName = $"动力单元{i}",
                    DeviceType = powerTypes.ElementAt(rand.Next(powerTypes.Count())),
                    BindAxisCode = axisArr[rand.Next(axisArr.Length)],
                    LoadRate = rand.Next(10,80),
                    DeviceStatus = "正常"
                });
            }
            _dbClient.Insertable(list).ExecuteCommand();
        }
        #endregion

        // 通用CRUD封装
        public static SqlSugarClient GetDb() => _dbClient;
        public static bool Insert<T>(T entity) => _dbClient.Insertable(entity).ExecuteCommand() > 0;
        public static bool Update<T>(T entity) => _dbClient.Updateable(entity).ExecuteCommand() > 0;
        public static List<T> QueryAll<T>() => _dbClient.Queryable<T>().ToList();
        public static T QueryById<T>(object id) => _dbClient.Queryable<T>().InSingle(id);
    }
}

组件 2:EventBusComponent 全局事件总线(解耦模块,消除跨窗体零散回调)

csharp

运行

复制代码
using System;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Components.EventBusComponent
{
    public static class GlobalEventBus
    {
        #region 全局委托定义
        public delegate void LogPrintDel(string logInfo);
        public delegate void UIRefreshDel();
        public delegate AxisMsgDel(string axisCode, string info);
        public delegate WorkStateDel(WorkOrder order, WorkStatus status);
        public delegate ScheduleResultDel(string fullResult);
        #endregion

        #region 全局订阅事件
        public static event LogPrintDel OnLog;
        public static event UIRefreshDel OnUiRefresh;
        public static event AxisMsgDel OnAxisNotify;
        public static event WorkStateDel OnWorkChange;
        public static event ScheduleResultDel OnScheduleComplete;
        #endregion

        // 输出日志
        public static void PublishLog(string msg)
        {
            string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | {msg}";
            OnLog?.Invoke(log);
        }
        // 通知界面刷新
        public static void PublishRefresh() => OnUiRefresh?.Invoke();
        // 推送组轴执行消息
        public static void PublishAxisMsg(string axis, string info)
        {
            PublishLog($"【组轴{axis}】{info}");
            OnAxisNotify?.Invoke(axis, info);
        }
        // 作业状态变更推送
        public static void PublishWorkState(WorkOrder order, WorkStatus status)
        {
            PublishLog($"作业{order.WorkNo} 状态:{status}");
            OnWorkChange?.Invoke(order, status);
        }
        // 推送多线程组装完整调度结果
        public static void PublishScheduleResult(string result)
        {
            OnScheduleComplete?.Invoke(result);
        }
    }
}

组件 3:AxisEngineComponent 组轴执行引擎(统一收拢零散工序执行代码)

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using _3C_MES_ERP.Components.DbComponent;
using _3C_MES_ERP.Components.EventBusComponent;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Components.AxisEngineComponent
{
    public class AxisExecuteEngine
    {
        private readonly int _taskDelay;
        private readonly Dictionary<string, List<string>> _axisTaskMap = new();
        private readonly Dictionary<string, AxisRunState> _axisStateMap = new();
        private readonly Dictionary<string, List<string>> _linkGroupMap = new();

        public AxisExecuteEngine()
        {
            _taskDelay = int.Parse(ConfigurationManager.AppSettings["AxisTaskDelayMs"]);
            LoadAxisConfigData();
        }

        /// 加载组轴、联控分组配置
        private void LoadAxisConfigData()
        {
            var axisList = LocalSqlDb.QueryAll<AxisGroup>();
            foreach (var axis in axisList)
            {
                _axisStateMap[axis.AxisCode] = axis.RunState;
                _axisTaskMap[axis.AxisCode] = axis.BindProcess.Split('、').ToList();
            }
            // 构建联控分组映射
            var groupQuery = axisList.GroupBy(g => g.LinkedGroup);
            foreach (var g in groupQuery)
                _linkGroupMap[g.Key] = g.Select(x => x.AxisCode).ToList();
        }

        /// 单组轴同步执行工序,返回完整执行文本结果
        public string RunSingleAxis(string axisCode)
        {
            if (!_axisStateMap.ContainsKey(axisCode) || _axisStateMap[axisCode] != AxisRunState.Idle)
                return $"组轴{axisCode}当前状态不可执行";

            _axisStateMap[axisCode] = AxisRunState.Running;
            var powerList = LocalSqlDb.QueryAll<PowerDevice>().Where(p => p.BindAxisCode == axisCode).ToList();
            var taskList = _axisTaskMap[axisCode];
            string resultText = $"====组轴{axisCode}执行开始====\r\n";

            foreach (var proc in taskList)
            {
                if (_axisStateMap[axisCode] == AxisRunState.Paused)
                {
                    result += $"工序{proc}:手动暂停\r\n";
                    continue;
                }
                // 动力设备负载上升
                foreach (var power in powerList)
                {
                    power.LoadRate += 8;
                    if (power.LoadRate > 90) power.DeviceStatus = "过载预警";
                    LocalSqlDb.Update(power);
                }
                Thread.Sleep(_taskDelay);
                resultText += $"工序{proc}执行完成\r\n";
                GlobalEventBus.PublishAxisMsg(axisCode, $"工序{proc}完成");
            }

            // 执行完毕负载回落
            foreach (var power in powerList)
            {
                power.LoadRate = Math.Max(0, power.LoadRate - 15);
                power.DeviceStatus = "正常";
                LocalSqlDb.Update(power);
            }
            _axisStateMap[axisCode] = AxisRunState.Idle;
            resultText += $"====组轴{axisCode}全部工序结束====\r\n";
            return resultText;
        }

        /// 暂停指定组轴
        public void PauseSingleAxis(string axisCode)
        {
            if (_axisStateMap.ContainsKey(axisCode) && _axisStateMap[axisCode] == AxisRunState.Running)
                _axisStateMap[axisCode] = AxisRunState.Paused;
        }

        /// 紧急停止整个联控分组
        public void StopLinkedGroup(string linkCode)
        {
            if (!_linkGroupMap.TryGetValue(linkCode, out var axisArr)) return;
            foreach (var ax in axisArr)
                _axisStateMap[ax] = AxisRunState.Error;
            GlobalEventBus.PublishLog($"联控组{linkCode}全部设备紧急停机");
        }

        /// 重置所有组轴为空闲
        public void ResetAllAxis()
        {
            foreach (var key in _axisStateMap.Keys)
                _axisStateMap[key] = AxisRunState.Idle;
            GlobalEventBus.PublishLog("所有组状态重置完成");
        }

        public List<string> GetAllAxisCode() => _axisStateMap.Keys.ToList();
        public List<string> GetAllLinkGroup() => _linkGroupMap.Keys.ToList();
    }
}

组件 4:ThreadScheduleComponent 多线程调度 + 结果组装(收拢零散 Task 代码)

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using _3C_MES_ERP.Components.AxisEngineComponent;
using _3C_MES_ERP.Components.DbComponent;
using _3C_MES_ERP.Components.EventBusComponent;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Components.ThreadScheduleComponent
{
    public class MultiThreadSchedule
    {
        private readonly AxisExecuteEngine _axisEngine;
        private readonly List<string> _resultBuffer = new();

        public MultiThreadSchedule(AxisExecuteEngine engine)
        {
            _axisEngine = engine;
        }

        /// 单线程调度
        public async Task<string> RunSingleTask(string axisCode, string operatorName)
        {
            _resultBuffer.Clear();
            string singleRes = await Task.Run(() => _axisEngine.RunSingleAxis(axisCode));
            _resultBuffer.Add(singleRes);
            string fullText = string.Join("\r\n", _resultBuffer);
            SaveScheduleLog(ScheduleMode.Single, axisCode, operatorName, fullText);
            GlobalEventBus.PublishScheduleResult(fullText);
            return fullText;
        }

        /// 多轴组合并行调度,自动组装全部线程返回结果
        public async Task<string> RunCombineTask(List<string> axisList, string operatorName)
        {
            _resultBuffer.Clear();
            List<Task<string>> taskGroup = new List<Task<string>>();
            foreach (var ax in axisList)
                taskGroup.Add(Task.Run(() => _axisEngine.RunSingleAxis(ax)));
            await Task.WhenAll(taskGroup);
            // 统一组装所有线程执行输出
            string combineResult = "【组合并行调度汇总】\r\n" + string.Join("\r\n-----------------\r\n", taskGroup.Select(t => t.Result));
            SaveScheduleLog(ScheduleMode.Combine, string.Join("、", axisList), operatorName, combineResult);
            GlobalEventBus.PublishScheduleResult(combineResult);
            return combineResult;
        }

        /// 流水线联控调度(顺序执行,单异常整组停机)
        public async Task<string> RunLinkedTask(string linkGroup, string operatorName)
        {
            _resultBuffer.Clear();
            var axisArr = _axisEngine.GetAllLinkGroup().Contains(linkGroup) ? _axisEngine.GetAllLinkGroup()[linkGroup] : new List<string>();
            if (!axisArr.Any()) return "该联控分组不存在";
            string totalText = $"【联控流水线{linkGroup}】\r\n";
            foreach (var ax in axisArr)
            {
                string res = await Task.Run(() => _axisEngine.RunSingleAxis(ax));
                totalText += res + "\r\n";
                if (res.Contains("过载预警") || res.Contains("暂停"))
                {
                    _axisEngine.StopLinkedGroup(linkGroup);
                    totalText += ">>>> 设备异常,流水线整体终止 <<<<\r\n";
                    break;
                }
            }
            SaveScheduleLog(ScheduleMode.Linked, linkGroup, operatorName, totalText);
            GlobalEventBus.PublishScheduleResult(totalText);
            return totalText;
        }

        /// 全部调度停止
        public void StopAllSchedule() => _axisEngine.ResetAllAxis();

        /// 调度日志入库(组装完整结果持久化)
        private void SaveScheduleLog(ScheduleMode mode, string target, string user, string fullResult)
        {
            var log = new ScheduleLog
            {
                ScheduleMode = mode,
                TargetAxis = target,
                Operator = user,
                FullResult = fullResult
            };
            LocalSqlDb.Insert(log);
            GlobalEventBus.PublishRefresh();
        }
    }
}

组件 5:ExcelTemplateComponent 固定模板导入导出(标准化 Excel 逻辑收拢)

csharp

运行

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

namespace _3C_MES_ERP.Components.ExcelTemplateComponent
{
    public static class ExcelFixedTemplate
    {
        // 两套标准固定表头(不可修改)
        private static readonly List<string> MaterialHeader = new()
        { "物料编码","物料名称","规格型号","单位","库存数量","组别(15/16/17)","物料类型" };
        private static readonly List<string> BomHeader = new()
        { "BOM单号","父物料编码","子物料编码","单台用量","版本","组别(15/16/17)" };

        #region 下载标准空白模板
        public static void DownloadMaterialTemplate() => CreateTemplate(MaterialHeader, ConfigurationManager.AppSettings["MaterialTemplateName"]);
        public static void DownloadBomTemplate() => CreateTemplate(BomHeader, ConfigurationManager.AppSettings["BomTemplateName"]);

        private static void CreateTemplate(List<string> header, string fileName)
        {
            using SaveFileDialog sfd = new SaveFileDialog();
            s.Filter = "Excel(*.xlsx)|*.xlsx";
            s.FileName = fileName;
            if (s.ShowDialog() != DialogResult) return;
            IWorkbook wb = new XSSFWorkbook();
            ISheet sheet = wb.CreateSheet("标准模板");
            IRow hRow = sheet.CreateRow(0);
            for (int i = 0; i < header.Count; i++)
            {
                hRow.CreateCell(i).SetCellValue(header[i]);
                sheet.SetColumnWidth(i, 22 * 256);
            }
            // 示例行
            IRow demoRow = sheet.CreateRow(1);
            header.ForEach((h, idx) => demoRow.CreateCell(idx).SetCellValue($"示例_{h}"));
            using FileStream fs = new FileStream(sfd.FileName, FileMode.Create);
            wb.Write(fs);
            MessageBox.Show("标准模板下载完成");
        }
        #endregion

        #region Excel导入(强制校验组别15~17)
        public static (bool Success, string Msg, List<ErpMaterial> ImportMaterial)
        {
            List<ErpMaterial> dataList = new List<ErpMaterial>();
            using OpenFileDialog ofd = new OpenFileDialog();
            of.Filter = "*.xlsx;*.xls";
            if (of.ShowDialog() != DialogResult) return (false, "已取消", dataList);
            try
            {
                IWorkbook wb;
                using FileStream fs = new FileStream(of.FileName, FileMode.Open);
                wb = of.FileName.EndsWith(".xlsx") ? new XSSFWorkbook(fs) : new HSSFWorkbook(fs);
                ISheet sheet = wb.GetSheetAt(0);
                for (int r = 1; r <= sheet.LastRowNum; r++)
                {
                    IRow row = sheet.GetRow(r);
                    if (row == null) continue;
                    int group = int.TryParse(GetCell(row, 5), out int g) ? g : 0;
                    if (group < 15 || group > 17)
                        return (false, $"第{r+1}行组别必须15/16/17", dataList);
                    dataList.Add(new ErpMaterial
                    {
                        MatCode = GetCell(row, 0),
                        MatName = GetCell(row, 1),
                        Spec = GetCell(row, 2),
                        Unit = GetCell(row, 3),
                        StockQty = decimal.TryParse(GetCell(row, 4), out var q) ? q : 0,
                        GroupNo = group,
                        MatType = GetCell(row, 6)
                    });
                }
                return (true, $"读取{dataList}条物料", dataList);
            }
            catch (Exception ex)
            {
                return (false, $"解析异常:{ex.Message}", dataList);
            }
        }
        #endregion

        #region 数据导出
        public static void ExportMaterial() => ExportData(MaterialHeader, LocalSqlDb.QueryAll<ErpMaterial>, m =>
            new object[] { m.MatCode, m.MatName, m.Spec, m.Unit, m.StockQty, m.GroupNo, m.MatType });
        public static void ExportBom() => ExportData(BomHeader, LocalSqlDb.QueryAll<ErpBom>, b =>
            new object[] { b.BomNo, b.ParentMatCode, b.ChildMatCode, b.UseQty, b.BomVersion, b.GroupNo });

        private static void ExportData<T>(List<string> header, List<T> source, Func<T, object[]> rowFunc)
        {
            if (source.Count == 0) { MessageBox.Show("无数据"); return; }
            using SaveFileDialog sfd = new SaveFileDialog();
            s.Filter = "*.xlsx";
            if (s.ShowDialog() != DialogResult) return;
            IWorkbook wb = new XSSFWorkbook();
            ISheet sheet = wb.CreateSheet("导出数据");
            IRow hRow = sheet.CreateRow(0);
            for (int i = 0; i < header.Count; i++) hRow.CreateCell(i).SetCellValue(header[i]);
            for (int i = 0; i < source.Count; i++)
            {
                IRow row = sheet.CreateRow(i + 1);
                var cells = rowFunc(source[i]);
                for (int c = 0; c < cells.Length; c++)
                    row.CreateCell(c, cells[c]?.ToString() ?? "");
            }
            using FileStream fs = new FileStream(sfd.FileMode.Create);
            wb.Write(fs);
            MessageBox.Show("导出完成");
        }
        #endregion

        private static string GetCell(IRow row, int idx)
        {
            var cell = row.GetCell(idx);
            return cell == null ? "" : cell.ToString().Trim();
        }
    }
}

组件 6:GdiChartComponent GDI 自绘图表组件(独立绘图逻辑,无窗体混杂代码)

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Components.GdiChartComponent
{
    public class ProductionGdiChart : Control
    {
        private List<ChartDataDto> _chartData = new();
        private readonly bool _antiAlias;
        private readonly int _barWidth;
        private readonly int _fontSize;

        public ProductionGdiChart()
        {
            // 读取App.config绘图参数
            _antiAlias = bool.Parse(ConfigurationManager.AppSettings["ChartAntiAlias"]);
            _barWidth = int.Parse(ConfigurationManager.AppSettings["ChartBarWidth"]);
            _fontSize = int.Parse(ConfigurationManager.AppSettings["ChartTitleFontSize"]);
            DoubleBuffered = true;
            SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        }

        public List<ChartDataDto> DataSource
        {
            set { _chartData = value; Invalidate(); }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            if (_antiAlias) g.SmoothingMode = SmoothingMode.AntiAlias;
            g.Clear(Color.White);
            if (_chart.Count == 0)
            {
                g.DrawString("暂无生产统计数据", new Font("微软雅黑", _fontSize), Brushes.Gray, Width / 2 - 60, Height / 2);
                return;
            }
            DrawTitle(g);
            DrawAxis(g);
            DrawBarChart(g);
            DrawLoadGauge(g);
        }

        private void DrawTitle(Graphics g)
        {
            Font font = new Font("微软雅黑", _fontSize, FontStyle.Bold);
            string title = "3C电子产线产量&设备负载报表";
            var size = g.MeasureString(title, font);
            g.DrawString(title, font, Brushes.Black, (Width - size.Width) / 2, 8);
            font.Dispose();
        }

        private void DrawAxis(Graphics g)
        {
            int pad = 40;
            Pen gridPen = new Pen(Color.LightGray, 1) { DashStyle = DashStyle.Dash };
            g.DrawLine(Pens.Black, pad, pad, pad, Height - pad);
            g.DrawLine(Pens.Black, pad, Height - pad, Width - pad, Height - pad);
            // Y刻度网格
            for (int i = 1; i <= 5; i++)
            {
                int y = Height - pad - (Height - pad) / 5 * i;
                g.DrawLine(grid, pad, y, Width - pad, y);
                g.DrawString($"{i * 20}%", new Font("微软雅黑", 9), Brushes.Gray, 10, y - 8);
            }
            gridPen.Dispose();
        }

        private void DrawBarChart(Graphics g)
        {
            int pad = 40;
            int x = pad + 10;
            decimal maxVal = Math.Max(_chart.Max(d => d.PlanNum), _chart.Max(d => d.ActualNum));
            float scale = (Height - pad * 2) / (float)maxVal;
            foreach (var item in _chartData)
            {
                float hPlan = (float)item.PlanNum * scale;
                g.FillRectangle(Brushes.RoyalBlue, x, Height - pad - hPlan, _barWidth, hPlan);
                float hAct = (float)item.ActualNum * scale;
                g.FillRectangle(Brushes.SeaGreen, x + _barWidth + 6, Height - pad - hAct, _barWidth, hAct);
                g.DrawString(item.WorkName, new Font("微软雅黑", 8), Brushes.Black, x, Height - pad + 5);
                x += _barWidth * 2 + 30;
            }
        }

        private void DrawLoadGauge(Graphics g)
        {
            decimal avgLoad = _chartData.Average(d => d.LoadAvg);
            int cx = Width - 130;
            int cy = Height / 2;
            int r = 80;
            using Pen bgPen = new Pen(Color.LightGray, 16) { StartCap = LineCap.Round, EndCap = LineCap.Round };
            g.DrawArc(bgPen, cx - r, cy - r, r * 2, r * 2, 135, 270);
            using Pen forePen = new Pen(Color.OrangeRed, 16);
            g.DrawArc(forePen, cx - r + 8, cy - r + 8, (r - 8) * 2, (r - 8) * 2, 135, (float)(270 * avgLoad / 100));
            g.DrawString($"平均负载\n{avgLoad:F1}%", new Font("微软雅黑", 10), Brushes.DarkRed, cx - 40, cy - 25);
        }

        protected override void Dispose(bool dispos)
        {
            base.Dispose(dis);
        }
    }
}

组件 7:ErpBomComponent ERP 物料 BOM 作业业务层(收拢零散单据逻辑)

csharp

运行

复制代码
using System.Collections.Generic;
using System.Linq;
using _3C_MES_ERP.Components.DbComponent;
using _3C_MES_ERP.Components.EventBusComponent;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Components.ErpBomComponent
{
    public class ErpBusinessService
    {
        #region 物料CRUD
        public List<ErpMaterial> GetAllMaterial() => LocalSqlDb.QueryAll<ErpMaterial>();
        public bool AddMaterial(ErpMaterial m) => LocalSqlDb.Insert(m);
        public bool UpdateMaterial(ErpMaterial m) => LocalSqlDb.Update(m);
        #endregion

        #region BOM查询
        public List<ErpBom> GetAllBom() => LocalSqlDb.QueryAll<ErpBom>();
        public List<ErpBom> GetBomByGroup(int groupNo) => LocalSqlDb.QueryAll<ErpBom>().Where(b => b.GroupNo == group).ToList();
        #endregion

        #region 生产作业单生命周期(ERP标准单据流程)
        /// 状态流转校验,禁止非法跨状态操作
        private bool CheckWorkFlow(WorkOrder curr, WorkOrder targetStatus)
        {
            return (curr.WorkStatus, targetStatus) switch
            {
                (WorkStatus.Init, WorkStatus.Dispatched) => true,
                (WorkStatus.Dispatched, WorkStatus.Running) => true,
                (WorkStatus.Running, WorkStatus.Paused) => true,
                (WorkStatus.Paused, WorkStatus.Running) => true,
                (WorkStatus.Running, WorkStatus.Completed) => true,
                (WorkStatus.Paused, WorkStatus.Completed) => true,
                (WorkStatus.Completed, WorkStatus.Archived) => true,
                _ => false
            };
        }

        /// 创建ERP生产作业单
        public WorkOrder CreateWork(string bomNo, string axisCode, int planQty)
        {
            WorkOrder wo = new WorkOrder
            {
                WorkNo = $"WO{DateTime.Now:yyyyMMddHHmmss}",
                RelBomNo = bomNo,
                PlanQty = planQty,
                CompleteQty = 0,
                WorkStatus = WorkStatus.Init,
                BindAxisCode = axisCode
            };
            LocalSqlDb.Insert(wo);
            GlobalEventBus.PublishWorkState(wo, WorkStatus.Init);
            GlobalEventBus.PublishRefresh();
            return wo;
        }

        /// 调度作业
        public bool DispatchWork(WorkOrder wo)
        {
            if (!CheckWorkFlow(wo, WorkStatus.Dispatched)) return false;
            wo.WorkStatus = WorkStatus.Dispatched;
            LocalSqlDb.Update(wo);
            GlobalEventBus.PublishWorkState(wo, WorkStatus.Dispatched);
            return true;
        }

        /// 暂停作业
        public bool PauseWork(WorkOrder wo)
        {
            if (!CheckWorkFlow(wo, WorkStatus.Paused)) return false;
            wo.WorkStatus = WorkStatus.Paused;
            LocalSqlDb.Update(wo);
            GlobalEventBus.PublishWorkState(wo, WorkStatus.Paused);
            return true;
        }

        /// 完成作业上报产量
        public bool FinishWork(WorkOrder wo, int completeQty)
        {
            if (!CheckWorkFlow(wo, WorkStatus.Completed) || completeQty > wo.PlanQty) return false;
            wo.CompleteQty = completeQty;
            wo.WorkStatus = WorkStatus.Completed;
            wo.FinishTime = DateTime.Now;
            LocalSqlDb.Update(wo);
            GlobalEventBus.PublishWorkState(wo, WorkStatus.Completed);
            return true;
        }

        /// ERP单据归档
        public bool ArchiveWork(WorkOrder wo)
        {
            if (!CheckWorkFlow(wo, WorkStatus.Archived)) return false;
            wo.WorkStatus = WorkStatus.Archived;
            LocalSqlDb.Update(wo);
            GlobalEventBus.PublishWorkState(wo, WorkStatus.Archived);
            return true;
        }

        public List<WorkOrder> GetAllWorkOrder() => LocalSqlDb.QueryAll<WorkOrder>().OrderByDesc(w => w.CreateTime).ToList();
        #endregion
    }
}

五、Forms WinForm 界面层(轻量化,无复杂业务,仅调用组件)

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using _3C_MES_ERP.Components.AxisEngineComponent;
using _3C_MES_ERP.Components.EventBusComponent;
using _3C_MES_ERP.Components.ExcelTemplateComponent;
using _3C_MES_ERP.Components.GdiChartComponent;
using _3C_MES_ERP.Components.ErpBomComponent;
using _3C_MES_ERP.Components.ThreadScheduleComponent;
using _3C_MES_ERP.Models;

namespace _3C_MES_ERP.Forms
{
    public partial class MainCooperForm : Form
    {
        // 全局组件实例(界面仅调用,不写业务逻辑)
        private readonly AxisExecuteEngine _axisEngine;
        private readonly MultiThreadSchedule _schedule;
        private readonly ErpBusinessService _erpBiz;
        private readonly ProductionGdiChart _chart;

        public MainCooperForm()
        {
            // 实例化所有底层业务组件
            _axisEngine = new AxisExecuteEngine();
            _schedule = new MultiThreadSchedule(_axisEngine);
            _erpBiz = new ErpBusinessService();
            _chart = new ProductionGdiChart { Dock = DockStyle.Fill };

            InitializeComponent();
            InitMainUI();
            BindEventBusSubscribe();
            LoadAllTableData();
        }

        /// 初始化顶部菜单、下拉框UI
        private void InitMainUI()
        {
            Text = "3C电子MES/ERP物料BOM协同平台 | 组轴多线程调度";
            WindowState = FormWindowState.Maximized;
            // 顶部菜单
            MenuStrip menu = new MenuStrip { Dock = DockStyle.Top };
            // Excel菜单
            ToolStripMenuItem excelMenu = new ToolStripMenuItem("Excel标准模板");
            excel.DropDownItems.Add("下载物料模板", null, (s, e) => ExcelFixedTemplate.DownloadMaterialTemplate());
            excel.DropDownItems.Add("下载BOM模板", null, (s, e) => ExcelFixedTemplate.DownloadBomTemplate());
            ToolStripMenuItem dataMenu = new ToolStripMenuItem("导入导出");
            data.DropDownItems.Add("导入物料", null, ImportMatClick);
            data.DropDownItems.Add("全部导出", null, (s, e) => { ExcelFixedTemplate.ExportMaterial(); ExcelFixedTemplate.ExportBom(); });
            // 调度菜单
            ToolStripMenuItem schMenu = new ToolStripMenuItem("调度指令中心");
            sch.DropDownItems.Add("单轴调度", null, async (s, e) => await RunSingle());
            sch.DropDownItems.Add("组合并行", null, async (s, e) => await RunCombine());
            sch.DropDownItems.Add("流水线联控", null, async (s, e) => await RunLink());
            sch.DropDownItems.Add("全部停止", null, (s, e) => _schedule.StopAllSchedule());
            menu.Items.Add(excelMenu);
            menu.Items.Add(dataMenu);
            menu.Items.Add(sch);
            Controls.Add(menu);

            // 下拉框绑定组轴、联控分组
            cboSingleAxis.Items.AddRange(_axisEngine.GetAllAxisCode().ToArray());
            clbCombine.Items.AddRange(_axisEngine.GetAllAxisCode().ToArray());
            cboLinkGroup.Items.AddRange(_axisEngine.GetAllLinkGroup().ToArray());
            if (cboSingleAxis.Items.Count > 0) cboSingleAxis.SelectedIndex = 0;
            // 图表容器
            panelChart.Controls.Add(_chart);
        }

        /// 订阅全局事件总线(日志、自动刷新、调度结果弹窗)
        private void BindEventBusSubscribe()
        {
            GlobalEventBus.OnLog += log =>
            {
                if (txtLog.InvokeRequired) txtLog.Invoke(() => txtLog.AppendText(log + "\r\n"));
                else txtLog.AppendText(log + "\r\n");
            };
            GlobalEventBus.OnUiRefresh += LoadAllTableData;
            GlobalEventBus.OnScheduleComplete += res => MessageBox.Show("调度执行完成:\r\n" + res);
        }

        /// 刷新所有物料/BOM/作业表格与图表
        private void LoadAllTableData()
        {
            dgvMaterial.DataSource = _erpBiz.GetAllMaterial();
            dgvBom.DataSource = _erpBiz.GetAllBom();
            dgvWork.DataSource = _erpBiz.GetAllWorkOrder();
            // 图表数据源
            List<ChartDataDto> chartSource = new List<ChartDataDto>();
            foreach (var wo in _erpBiz.GetAllWorkOrder())
            {
                chartSource.Add(new ChartDataDto
                {
                    WorkName = wo.WorkNo,
                    PlanNum = wo.PlanQty,
                    ActualNum = wo.CompleteQty,
                    LoadAvg = new Random().Next(10, 85)
                });
            }
            _chart.DataSource = chartSource;
        }

        #region 三种调度按钮逻辑(仅调用组件,无业务代码)
        private async Task RunSingle()
        {
            if (cboSingleAxis.SelectedItem == null) return;
            await _schedule.RunSingleTask(cboSingle.Text, "系统管理员");
        }
        private async Task RunCombine()
        {
            List<string> selAxis = new List<string>();
            for (int i = 0; i < clbCombine.Items.Count; i++)
                if (clbCombine.GetItemChecked(i)) selAxis.Add(clbCombine.Items[i].ToString());
            if (selAxis.Count == 0) { MessageBox.Show("请勾选组轴"); return; }
            await _schedule.RunCombineTask(selAxis, "系统管理员");
        }
        private async Task RunLink()
        {
            if (cboLinkGroup.SelectedItem == null) return;
            await _schedule.RunLinkedTask(cboLink.Text, "系统管理员");
        }
        #endregion

        #region ERP作业生命周期按钮
        private void btnCreateWork_Click(object sender, EventArgs e)
        {
            if (!int.TryParse(txtPlanQty.Text, out int qty))
            {
                MessageBox.Show("请输入合法计划产量");
                return;
            }
            var work = _erpBiz.CreateWork(txtBomNo.Text.Trim(), cboSingle.Text, qty);
            MessageBox.Show($"作业创建成功:{work.WorkNo}");
        }
        private void btnPauseWork_Click(object sender, EventArgs e)
        {
            var sel = GetSelectWork();
            if (sel == null) return;
            bool ok = _erpBiz.PauseWork(sel);
            MessageBox.Show(ok ? "暂停成功" : "当前状态禁止操作");
        }
        private void btnArchiveWork_Click(object sender, EventArgs e)
        {
            var sel = GetSelectWork();
            if (sel == null) return;
            bool ok = _erpBiz.ArchiveWork(sel);
            MessageBox.Show(ok ? "ERP单据归档完成" : "状态不支持归档");
        }
        private WorkOrder GetSelectWork()
        {
            if (dgvWork.SelectedRows.Count == 0)
            {
                MessageBox.Show("请选中作业行");
                return null;
            }
            return dgvWork.SelectedRows[0].DataBoundItem as WorkOrder;
        }
        #endregion

        /// Excel导入物料按钮
        private void ImportMatClick(object sender, EventArgs e)
        {
            var res = ExcelFixedTemplate.ImportMaterial();
            if (res.Success && res.ImportMaterial.Count > 0)
                LocalSqlDb.Insert(res.ImportMaterial);
            MessageBox.Show(res.Msg);
            LoadAllTableData();
        }

        #region 窗体控件定义(VS设计器自动生成)
        private DataGridView dgvMaterial, dgvBom, dgvWork;
        private ComboBox cboSingleAxis, cboLinkGroup;
        private CheckedListBox clbCombine;
        private TextBox txtLog, txtBomNo, txtPlanQty;
        private Button btnCreateWork, btnPauseWork, btnArchiveWork;
        private Panel panelChart;
        private void InitializeComponent()
        {
            this.ClientSize = new Size(1300, 860);
            // 省略VS自动布局代码
        }
        #endregion
    }
}

六、Program 程序入口

csharp

运行

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

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

五、项目完整介绍

1. 项目解决核心痛点

  1. 业务代码碎片化 :原调度、Excel、绘图、数据库逻辑全部散落在窗体按钮事件内,维护困难;本方案按领域拆独立组件,单一职责,可单独复用。
  2. 参数硬编码 :所有数据库、线程、图表、ERP、Excel 路径统一放入App.config,改配置无需修改代码。
  3. 多线程混乱:统一封装多线程调度组件,自动汇总所有并行线程执行结果并持久化,消除零散 Task/Thread 代码。
  4. 业务耦合严重:物料 / BOM / 组轴 / 作业分层隔离,通过事件总线解耦模块通信,无跨层硬依赖。

2. 核心业务能力清单

  1. 本地文件 SQLite 数据库 免数据库服务,程序启动自动建表、生成 15~17 组 3C 电子物料 / BOM 测试数据,SqlSugar 完整封装 CRUD。
  2. 组轴 + 动力设备协同执行引擎 模拟 3C 产线 SMT / 焊接 / 组装 / 检测四大组轴,工序执行自动变更设备负载,支持单轴暂停、整组紧急停机。
  3. 三模式多线程调度 ①单轴独立调度;②多轴并行组合调度;③流水线联控调度(一故障全流水线停止);所有线程执行文本自动组装存入调度日志。
  4. ERP 物料 BOM 完整协同流程 物料档案→BOM 结构→生产作业单,作业生命周期严格贴合金蝶 ERP 单据:创建→调度→运行→暂停→完成→归档,禁止跨状态非法操作。
  5. 标准化 Excel 固定模板 两套不可修改标准模板,支持下载空白模板、导入校验组别 15~17、批量导出物料 / BOM 数据。
  6. 原生 GDI 图表控件 不依赖第三方图表库,自绘产量柱状图 + 设备负载仪表盘,绘图参数全部从 App.config 读取,可嵌入任意窗体。
  7. 全局事件总线 日志、界面刷新、调度通知、作业状态变更统一发布订阅,各组件无直接依赖。
  8. WinForm 轻量化界面 界面只做展示与按钮交互,无任何复杂业务逻辑,所有操作调用底层组件。

3. 架构优势

  1. 组件可移植:Db/Excel/GDI/ 调度引擎均可单独提取到其他 WinForm 项目复用;
  2. 易扩展:新增工序、报表、ERP 接口仅扩展对应组件,不改动其他模块;
  3. 环境快速切换:修改 App.config 参数即可切换数据库、ERP 地址、线程并发数;
  4. 可维护:业务按领域拆分,定位 BUG 只需进入对应组件,不用翻遍窗体零散代码;
  5. 工业场景适配:完全贴合 3C 数码组装 MES+ERP 协同生产业务流程。