整体设计方案
核心设计准则
- 实体分层清晰、简洁
- 分层:
DB实体(仅数据持久层)→业务DTO(组件之间传输) - 数据库实体仅在本地数据库组件内部使用,向外输出统一业务 DTO,避免全局到处引用数据库实体;杜绝大而全的实体,字段按需精简。
- 分层:
- 业务目标(3C 电子组装产线:SMT 贴片 AX01、回流焊 AX02、整机组装 AX03、功能检测 AX04)
- 组轴执行引擎(组支轴模式)
- 多线程调度 + 多任务执行结果统一汇总组装
- 固定 Excel 模板导入导出(物料、多级 BOM,3C 物料组别 15/16/17 校验)
- 原生 GDI + 自定义图表控件(产量柱状图、设备负载仪表盘报表)
- ERP 物料 + BOM 主子分层树形协同场景
- SQLite 本地文件数据库可视化管理组件
- 架构规范
- 所有业务封装独立组件,消除窗体零散 Click 业务代码;
- WinForm 窗体只负责 UI 交互展示,不编写业务逻辑;
- 组件通过全局事件总线解耦通信,减少硬依赖;
- 技术栈:.NET Framework4.8 WinForm、SqlSugar、NPOI、原生 GDI+、Task 异步并发
项目目录结构
plaintext
3C_MES_WinStationClient
├─ App.config
├─ Program.cs
├─ DataTransfer # 【业务DTO,组件通用传输对象,轻量化】
│ AxisDto.cs
│ MaterialDto.cs
│ BomNodeDto.cs
│ ScheduleResultDto.cs
│ ChartStatDto.cs
├─ Components
│ ├─ LocalDbComponent # 本地SQLite文件数据库模块
│ │ DatabaseEntity/ # 数据库实体,仅限本组件内部访问
│ │ AxisDbEntity.cs
│ │ MaterialDbEntity.cs
│ │ BomDbEntity.cs
│ │ ScheduleLogDbEntity.cs
│ │ SqliteDataProvider.cs # 数据提供层,DbEntity→DTO转换
│ │ DbManageForm.cs # 数据库可视化管理窗体
│ ├─ AxisEngineComponent # 组轴执行引擎
│ │ AxisExecuteEngine.cs
│ ├─ MultiScheduleComponent # 多线程调度+结果组装
│ │ ScheduleTaskScheduler.cs
│ ├─ BomCooperComponent # ERP物料BOM树形协同
│ │ BomTreeBuilder.cs
│ ├─ ExcelTemplateComponent # 固定Excel模板导入导出
│ │ FixedExcelHelper.cs
│ ├─ GdiChartComponent # GDI自定义绘图控件
│ │ BaseGdiRender.cs
│ │ OutputBarChart.cs
│ │ DeviceLoadGauge.cs
│ ├─ ApiClientComponent # 对接 IFactoryMom5.Api-dev
│ ├─ IotMqttComponent # 对接iot-master物联网采集
│ └─ GlobalEventBus
│ GlobalEventBus.cs
├─ Forms # UI窗体,只处理界面交互
│ MainFrm
│ AxisScheduleFrm
│ BomManageFrm
│ StatReportFrm
├─ Utils
└─ Resources 内置Excel标准模板
一、通用传输 DTO(DataTransfer 文件夹)
DataTransfer/AxisDto.cs
csharp
运行
namespace DataTransfer
{
/// <summary>产线组轴 业务传输DTO</summary>
public class AxisDto
{
public string AxisCode { get; set; }
public string AxisName { get; set; }
public string LinkedGroup { get; set; }
public string RunState { get; set; }
public string BindProcess { get; set; }
}
}
DataTransfer/MaterialDto.cs
csharp
运行
namespace DataTransfer
{
/// <summary>物料 DTO,ERP物料同步、Excel导入共用</summary>
public class MaterialDto
{
public string MatCode { get; set; }
public string MatName { get; set; }
public string Spec { get; set; }
public string Unit { get; set; }
public decimal StockQty { get; set; }
public int GroupNo { get; set; } //15原材料/16半成品/17成品
public string MatType { get; set; }
}
}
DataTransfer/BomNodeDto.cs
csharp
运行
using System.Collections.Generic;
namespace DataTransfer
{
/// <summary>BOM树形节点DTO,主子分层结构</summary>
public class BomNodeDto
{
public long Id { get; set; }
public string BomNo { get; set; }
public string MaterialCode { get; set; }
public long ParentNodeId { get; set; }
public int BomLevel { get; set; }
public decimal UseQty { get; set; }
public string BindAxisCode { get; set; }
public List<BomNodeDto> ChildNodes { get; set; } = new List<BomNodeDto>();
}
}
DataTransfer/ScheduleResultDto.cs
csharp
运行
using System;
namespace DataTransfer
{
/// <summary>多线程调度完成后组装汇总结果DTO</summary>
public class ScheduleResultDto
{
public string ScheduleMode { get; set; }
public string TargetInfo { get; set; }
public string Operator { get; set; }
public string FullMergeLog { get; set; }
public DateTime OperTime { get; set; }
}
}
DataTransfer/ChartStatDto.cs
csharp
运行
namespace DataTransfer
{
/// <summary>GDI图表统计数据DTO</summary>
public class ChartStatDto
{
public string WorkNo { get; set; }
public decimal PlanQty { get; set; }
public decimal ActualQty { get; set; }
}
}
二、组件 1:LocalDbComponent 本地 SQLite 数据库模块
Components/LocalDbComponent/DatabaseEntity/AxisDbEntity.cs
csharp
运行
namespace Components.LocalDbComponent.DatabaseEntity
{
/// <summary>数据库实体【仅本组件内部使用】</summary>
internal class AxisDbEntity
{
public long Id { get; set; }
public string AxisCode { get; set; }
public string AxisName { get; set; }
public string LinkedGroup { get; set; }
public string RunState { get; set; }
public string BindProcess { get; set; }
}
}
Components/LocalDbComponent/DatabaseEntity/MaterialDbEntity.cs
csharp
运行
using System;
namespace Components.LocalDbComponent.DatabaseEntity
{
internal class MaterialDbEntity
{
public long Id { get; set; }
public string MatCode { get; set; }
public string MatName { get; set; }
public string Spec { get; set; }
public string Unit { get; set; }
public decimal StockQty { get; set; }
public int GroupNo { get; set; }
public string MatType { get; set; }
public DateTime CreateTime { get; set; }
}
}
Components/LocalDbComponent/DatabaseEntity/BomDbEntity.cs
csharp
运行
namespace Components.LocalDbComponent.DatabaseEntity
{
internal class BomDbEntity
{
public long Id { get; set; }
public string BomNo { get; set; }
public string MaterialCode { get; set; }
public long ParentNodeId { get; set; }
public int BomLevel { get; set; }
public decimal UseQty { get; set; }
public string BindAxisCode { get; set; }
public int SortIndex { get; set; }
}
}
Components/LocalDbComponent/DatabaseEntity/ScheduleLogDbEntity.cs
csharp
运行
using System;
namespace Components.LocalDbComponent.DatabaseEntity
{
internal class ScheduleLogDbEntity
{
public long Id { get; set; }
public string ScheduleMode { get; set; }
public string TargetInfo { get; set; }
public string Operator { get; set; }
public string FullMergeLog { get; set; }
public DateTime OperTime { get; set; }
}
}
Components/LocalDbComponent/SqliteDataProvider.cs
核心:内部 DbEntity ↔ 对外 DTO 转换,上层业务看不到数据库实体
csharp
运行
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using SqlSugar;
using Components.LocalDbComponent.DatabaseEntity;
using DataTransfer;
using Components.GlobalEventBus;
namespace Components.LocalDbComponent
{
public static class SqliteDataProvider
{
private static SqlSugarClient _db;
private static readonly string _dbFileName;
static SqliteDataProvider()
{
_dbFileName = ConfigurationManager.AppSettings["SqliteDbFileName"];
string fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbFileName);
string connStr = ConfigurationManager.ConnectionStrings["SqliteConn"]
.Replace("{DbPath}", fullPath);
_db = new SqlSugarClient(new ConnectionConfig
{
ConnectionString = connStr,
DbType = DbType.Sqlite,
IsAutoCloseConnection = true
});
InitDatabaseTables();
}
private static void InitDatabaseTables()
{
//自动创建数据表(内部实体)
_db.CodeFirst.InitTables(
typeof(AxisDbEntity),
typeof(MaterialDbEntity),
typeof(BomDbEntity),
typeof(ScheduleLogDbEntity));
InitBaseAxisData();
}
//初始化3C标准4条产线组轴
private static void InitBaseAxisData()
{
var axisList = new List<AxisDbEntity>()
{
new AxisDbEntity{AxisCode="AX01",AxisName="SMT贴片",LinkedGroup="LINK01",RunState="Idle",BindProcess="锡膏印刷、元件贴片"},
new AxisDbEntity{AxisCode="AX02",AxisName="回流焊",LinkedGroup="LINK01",RunState="Idle"},
new AxisDbEntity{AxisCode="AX03",AxisName="整机组装",LinkedGroup="LINK02",RunState="Idle"},
new AxisDbEntity{AxisCode="AX04",AxisName="功能检测",LinkedGroup="LINK02",RunState="Idle"}
};
_db.Insertable(axisList).ExecuteCommand();
}
#region 对外公开接口【返回DTO】
public static List<AxisDto> QueryAllAxis()
{
var dbList = _db.Queryable<AxisDbEntity>().ToList();
List<AxisDto> result = new List<AxisDto>();
foreach (var item in dbList)
{
result.Add(new AxisDto()
{
AxisCode = item.AxisCode,
AxisName = item.AxisName,
LinkedGroup = item.LinkedGroup,
RunState = item.RunState,
BindProcess = item.BindProcess
});
}
return result;
}
public static bool UpdateAxisState(AxisDto dto)
{
var entity = _db.Queryable<AxisDbEntity>()
.Where(x => x.AxisCode == dto.AxisCode).First();
if (entity == null) return false;
entity.RunState = dto.RunState;
return _db.Updateable(entity).ExecuteCommand() > 0;
}
public static List<MaterialDto> QueryAllMaterial()
{
var dbList = _db.Queryable<MaterialDbEntity>().ToList();
List<MaterialDto> dtoList = new List<MaterialDto>();
foreach (var m in dbList)
{
dtoList.Add(new MaterialDto()
{
MatCode = m.MatCode,
MatName = m.MatName,
Spec = m.Spec,
Unit = m.Unit,
StockQty = m.StockQty,
GroupNo = m.GroupNo,
MatType = m.MatType
});
}
return dtoList;
}
//批量物料入库(Excel导入调用)
public static bool BatchInsertMaterial(List<MaterialDto> dtoList)
{
return TransactionExecute(() =>
{
List<MaterialDbEntity> dbEntityList = new List<MaterialDbEntity>();
foreach (var dto in dtoList)
{
dbEntityList.Add(new MaterialDbEntity()
{
MatCode = dto.MatCode,
MatName = dto.MatName,
Spec = dto.Spec,
Unit = dto.Unit,
StockQty = dto.StockQty,
GroupNo = dto.GroupNo,
MatType = dto.MatType,
CreateTime = DateTime.Now
});
}
_db.Insertable(dbEntityList).ExecuteCommand();
});
}
//保存调度汇总日志
public static bool SaveScheduleLog(ScheduleResultDto dto)
{
var logEntity = new ScheduleLogDbEntity()
{
ScheduleMode = dto.ScheduleMode,
TargetInfo = dto.TargetInfo,
Operator = dto.Operator,
FullMergeLog = dto.FullMergeLog,
OperTime = dto.OperTime
};
return _db.Insertable(logEntity).ExecuteCommand() > 0;
}
#endregion
#region 事务、数据库运维方法(可视化窗体调用)
public static bool TransactionExecute(Action action)
{
try
{
_db.Ado.BeginTran();
action.Invoke();
_db.Ado.CommitTran();
return true;
}
catch (Exception ex)
{
_db.Ado.RollbackTran();
GlobalEventBus.PublishLog($"事务执行失败:{ex.Message}");
return false;
}
}
public static bool BackupDatabase(string savePath)
{
try
{
string sourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbFileName);
File.Copy(sourcePath, savePath, true);
return true;
}
catch
{
return false;
}
}
public static void ClearScheduleLog()
{
_db.Deleteable<ScheduleLogDbEntity>().Where(x => x.Id > 0).ExecuteCommand();
}
public static string GetDbFullPath() => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbFileName);
#endregion
}
}
Components/LocalDbComponent/DbManageForm.cs(数据库可视化窗口)
csharp
运行
using System;
using System.Windows.Forms;
namespace Components.LocalDbComponent
{
public partial class DbManageForm : Form
{
public DbManageForm()
{
InitializeComponent();
lblDbPath.Text = $"数据库路径:{SqliteDataProvider.GetDbFullPath()}";
RefreshStatistics();
}
private void RefreshStatistics()
{
var matCount = SqliteDataProvider.QueryAllMaterial().Count;
var axisCount = SqliteDataProvider.QueryAllAxis().Count;
txtStat.Text = $"物料总数:{matCount}\r\n产线组轴数量:{axisCount}";
}
private void btnBackup_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "*.db";
sfd.FileName = $"MES数据库备份_{DateTime.Now:yyyyMMdd}.db";
if (sfd.ShowDialog() == DialogResult.OK)
{
bool res = SqliteDataProvider.BackupDatabase(sfd.FileName);
MessageBox.Show(res ? "备份成功" : "备份失败");
RefreshStatistics();
}
}
private void btnClearLog_Click(object sender, EventArgs e)
{
if (MessageBox.Show("确认清空调度日志?数据不可恢复", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
return;
SqliteDataProvider.ClearScheduleLog();
MessageBox.Show("日志已清空");
RefreshStatistics();
}
}
}
三、组件 2:AxisEngineComponent 组轴执行引擎
csharp
运行
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Components.LocalDbComponent;
using Components.GlobalEventBus;
using DataTransfer;
using System.Configuration;
namespace Components.AxisEngineComponent
{
public class AxisExecuteEngine
{
private readonly int _simDelayMs;
private readonly Dictionary<string, AxisDto> _axisCache = new Dictionary<string, AxisDto>();
public AxisExecuteEngine()
{
_simDelayMs = int.Parse(ConfigurationManager.AppSettings["AxisSimDelayMs"]);
LoadAxisCache();
}
private void LoadAxisCache()
{
var list = SqliteDataProvider.QueryAllAxis();
_axisCache.Clear();
foreach (var dto in list)
{
_axisCache[dto.AxisCode] = dto;
}
}
/// <summary>单组轴执行加工工序,返回完整执行日志</summary>
public string RunSingleAxisProcess(string axisCode)
{
if (!_axisCache.ContainsKey(axisCode) || _axisCache[axisCode].RunState != "Idle")
return $"【{axisCode}】设备当前繁忙,无法启动工序";
AxisDto targetAxis = _axisCache[axisCode];
targetAxis.RunState = "Running";
SqliteDataProvider.UpdateAxisState(targetAxis);
GlobalEventBus.PublishAxisState(axisCode, "开始执行工序");
List<string> logBuffer = new List<string>();
logBuffer.Add($"=====组轴{axisCode}【{targetAxis.AxisName}】工序开始=====");
string[] processes = targetAxis.BindProcess?.Split('、') ?? new[] { "标准加工工序" };
foreach (var proc in processes)
{
Thread.Sleep(_simDelayMs);
logBuffer.Add($"工序:{proc} 执行完成");
}
targetAxis.RunState = "Idle";
SqliteDataProvider.UpdateAxisState(targetAxis);
logBuffer.Add($"=====组轴{axisCode}工序全部执行完毕=====\r\n");
string fullLog = string.Join("\r\n", logBuffer);
GlobalEventBus.PublishLog(fullLog);
return fullLog;
}
public void PauseAxis(string axisCode)
{
if (_axisCache.TryGetValue(axisCode, out var axis) && axis.RunState == "Running")
{
axis.RunState = "Paused";
SqliteDataProvider.UpdateAxisState(axis);
GlobalEventBus.PublishLog($"组轴{axisCode}已手动暂停");
}
}
public void EmergencyStopLinkedGroup(string linkGroup)
{
var groupAxisList = _axisCache.Values.Where(x => x.LinkedGroup == linkGroup).ToList();
foreach (var ax in groupAxisList)
{
ax.RunState = "Error";
SqliteDataProvider.UpdateAxisState(ax);
}
GlobalEventBus.PublishLog($"联控分组 {linkGroup} 紧急停机");
}
public List<string> GetAllAxisCode() => _axisCache.Keys.ToList();
public List<string> GetAllLinkedGroup() => _axisCache.Values.Select(x => x.LinkedGroup).Distinct().ToList();
}
}
四、组件 3:MultiScheduleComponent 多线程调度 + 结果组装
csharp
运行
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Components.AxisEngineComponent;
using Components.LocalDbComponent;
using Components.GlobalEventBus;
using DataTransfer;
namespace Components.MultiScheduleComponent
{
public class ScheduleTaskScheduler
{
private readonly AxisExecuteEngine _axisEngine;
public ScheduleTaskScheduler(AxisExecuteEngine engine)
{
_axisEngine = engine;
}
/// <summary>单轴异步调度</summary>
public async Task<ScheduleResultDto> RunSingleTask(string axisCode, string operatorName)
{
var task = Task.Run(() => _axisEngine.RunSingleAxisProcess(axisCode));
await task;
string logText = task.Result;
var resultDto = new ScheduleResultDto()
{
ScheduleMode = "SingleAxis",
TargetInfo = axisCode,
Operator = operatorName,
FullMergeLog = logText,
OperTime = DateTime.Now
};
SqliteDataProvider.SaveScheduleLog(resultDto);
GlobalEventBus.PublishScheduleComplete(logText);
return resultDto;
}
/// <summary>多轴并行调度,多线程并发执行,统一汇总所有线程输出日志</summary>
public async Task<ScheduleResultDto> RunCombineParallelTask(List<string> axisCodes, string operatorName)
{
List<Task<string>> taskList = new List<Task<string>>();
foreach (var ax in axisCodes)
{
taskList.Add(Task.Run(() => _axisEngine.RunSingleAxisProcess(ax)));
}
await Task.WhenAll(taskList);
string mergeLog = "【多轴并行调度汇总】\r\n"
+ string.Join("\r\n----------------\r\n", taskList.Select(t => t.Result));
var resultDto = new ScheduleResultDto()
{
ScheduleMode = "CombineParallel",
TargetInfo = string.Join("、", axisCodes),
Operator = operatorName,
FullMergeLog = mergeLog,
OperTime = DateTime.Now
};
SqliteDataProvider.SaveScheduleLog(resultDto);
GlobalEventBus.PublishScheduleComplete(mergeLog);
return resultDto;
}
/// <summary>流水线顺序联控调度</summary>
public async Task<ScheduleResultDto> RunLinkedPipelineTask(string linkGroup, string operatorName)
{
var axisList = _axisEngine.GetAllAxisCode()
.Where(c => _axisEngine.GetAllLinkedGroup().Contains(linkGroup)).ToList();
string totalLog = $"【流水线联控 {linkGroup}】执行日志\r\n";
foreach (var ax in axisList)
{
string log = await Task.Run(() => _axisEngine.RunSingleAxisProcess(ax));
totalLog += log;
if (log.Contains("过载预警"))
{
_axisEngine.EmergencyStopLinkedGroup(linkGroup);
totalLog += ">>>> 设备异常,流水线强制停机 <<<<\r\n";
break;
}
}
var resultDto = new ScheduleResultDto()
{
ScheduleMode = "LinkedPipeline",
TargetInfo = linkGroup,
Operator = operatorName,
FullMergeLog = totalLog,
OperTime = DateTime.Now
};
SqliteDataProvider.SaveScheduleLog(resultDto);
GlobalEventBus.PublishScheduleComplete(totalLog);
return resultDto;
}
public void StopAllTask()
{
foreach (var axCode in _axisEngine.GetAllAxisCode())
{
_axisEngine.PauseAxis(axCode);
}
}
}
}
五、组件 4:ExcelTemplateComponent 固定 Excel 模板导入导出
csharp
运行
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using Components.LocalDbComponent;
using DataTransfer;
namespace Components.ExcelTemplateComponent
{
public static class FixedExcelHelper
{
private static readonly List<string> MaterialHeader = new List<string>()
{ "物料编码", "物料名称", "规格型号", "单位", "库存数量", "组别(15/16/17)", "物料类型" };
public static void DownloadMaterialBlankTemplate()
{
SaveFileDialog sfd = new SaveFileDialog
{
Filter = "Excel文件(*.xlsx)|*.xlsx",
FileName = "3C电子物料标准模板.xlsx"
};
if (sfd.ShowDialog() != DialogResult.OK) return;
CreateTemplateFile(MaterialHeader, sfd.FileName);
MessageBox.Show("空白物料模板下载完成");
}
private static void CreateTemplateFile(List<string> header, string path)
{
using IWorkbook wb = new XSSFWorkbook();
ISheet sheet = wb.CreateSheet("标准模板");
IRow headerRow = sheet.CreateRow(0);
for (int i = 0; i < header.Count; i++)
{
headerRow.CreateCell(i).SetCellValue(header[i]);
sheet.SetColumnWidth(i, 24 * 256);
}
using var fs = new FileStream(path, FileMode.Create);
wb.Write(fs);
}
/// <summary>导入物料模板,强制校验组别15/16/17</summary>
public static (bool success, string msg, List<MaterialDto> dataList) ImportMaterialExcel()
{
List<MaterialDto> resultList = new List<MaterialDto>();
OpenFileDialog ofd = new OpenFileDialog { Filter = "*.xlsx" };
if (ofd.ShowDialog() != DialogResult.OK)
return (false, "取消文件选择", resultList);
try
{
using var fs = new FileStream(ofd.FileName, FileMode.Open);
IWorkbook wb = new XSSFWorkbook(fs);
ISheet sheet = wb.GetSheetAt(0);
for (int r = 1; r <= sheet.LastRowNum; r++)
{
IRow row = sheet.GetRow(r);
if (row == null) continue;
int groupNo = int.TryParse(GetCellText(row, 5), out int g) ? g : 0;
if (groupNo < 15 || groupNo > 17)
return (false, $"第{r + 1}行物料组别必须为15/16/17", resultList);
var dto = new MaterialDto()
{
MatCode = GetCellText(row, 0),
MatName = GetCellText(row, 1),
Spec = GetCellText(row, 2),
Unit = GetCellText(row, 3),
StockQty = decimal.TryParse(GetCellText(row, 4), out var q) ? q : 0,
GroupNo = groupNo,
MatType = GetCellText(row, 6)
};
resultList.Add(dto);
}
bool insertOk = SqliteDataProvider.BatchInsertMaterial(resultList);
return (insertOk, $"成功读取{resultList.Count}条物料", resultList);
}
catch (Exception ex)
{
return (false, $"解析异常:{ex.Message}", resultList);
}
}
private static string GetCellText(IRow row, int idx)
{
var cell = row.GetCell(idx);
return cell == null ? "" : cell.ToString().Trim();
}
}
}
六、组件 5:GlobalEventBus 全局事件总线(模块解耦)
csharp
运行
using System;
namespace Components.GlobalEventBus
{
public static class GlobalEventBus
{
public delegate void LogMessageHandler(string logContent);
public delegate void AxisStateHandler(string axisCode, string info);
public delegate void ScheduleCompleteHandler(string fullLog);
public delegate void UiRefreshHandler();
public static event LogMessageHandler OnLogOutput;
public static event AxisStateHandler OnAxisStateChanged;
public static event ScheduleCompleteHandler OnScheduleFinish;
public static event UiRefreshHandler OnRequestUiRefresh;
public static void PublishLog(string text)
{
string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | {text}";
OnLogOutput?.Invoke(log);
}
public static void PublishAxisState(string axisCode, string msg)
{
PublishLog($"【组轴{axisCode}】{msg}");
OnAxisStateChanged?.Invoke(axisCode, msg);
}
public static void PublishScheduleComplete(string fullLog)
{
OnScheduleFinish?.Invoke(fullLog);
}
public static void PublishUiRefresh()
{
OnRequestUiRefresh?.Invoke();
}
}
}
七、窗体调用示例(窗体仅做 UI 交互,无业务逻辑)
csharp
运行
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Components.AxisEngineComponent;
using Components.MultiScheduleComponent;
using Components.ExcelTemplateComponent;
using Components.GlobalEventBus;
namespace Forms
{
public partial class AxisScheduleFrm : Form
{
private readonly AxisExecuteEngine _axisEngine;
private readonly ScheduleTaskScheduler _scheduler;
public AxisScheduleFrm()
{
_axisEngine = new AxisExecuteEngine();
_scheduler = new ScheduleTaskScheduler(_axisEngine);
InitializeComponent();
BindEventBus();
InitUiSource();
}
private void BindEventBus()
{
GlobalEventBus.OnLogOutput += log => txtLog.AppendText(log + "\r\n");
GlobalEventBus.OnRequestUiRefresh += RefreshData;
}
private void InitUiSource()
{
var axisCodes = _axisEngine.GetAllAxisCode();
cboSingleAxis.Items.AddRange(axisCodes.ToArray());
clbCombineAxis.Items.AddRange(axisCodes.ToArray());
cboLinkGroup.Items.AddRange(_axisEngine.GetAllLinkedGroup().ToArray());
}
private void RefreshData()
{
//刷新表格、树形、图表数据源
}
private async void btnSingleRun_Click(object sender, EventArgs e)
{
if (cboSingleAxis.SelectedItem == null) return;
await _scheduler.RunSingleTask(cboSingleAxis.Text, "车间操作员");
}
private async void btnCombineRun_Click(object sender, EventArgs e)
{
List<string> selectAxis = new List<string>();
for (int i = 0; i < clbCombineAxis.Items.Count; i++)
{
if (clbCombineAxis.GetItemChecked(i))
selectAxis.Add(clbCombineAxis.Items[i].ToString());
}
if (selectAxis.Count == 0)
{
MessageBox.Show("请勾选至少一条产线组轴");
return;
}
await _scheduler.RunCombineParallelTask(selectAxis, "车间操作员");
}
private void btnDownloadTemplate_Click(object sender, EventArgs e)
{
FixedExcelHelper.DownloadMaterialBlankTemplate();
}
private void btnImportMaterial_Click(object sender, EventArgs e)
{
var res = FixedExcelHelper.ImportMaterialExcel();
MessageBox.Show(res.msg);
GlobalEventBus.PublishUiRefresh();
}
private void btnOpenDbManager_Click(object sender, EventArgs e)
{
new Components.LocalDbComponent.DbManageForm().ShowDialog();
}
}
}
架构优势说明
- 实体分层清晰简洁
- 数据库实体
DbEntity隔离在数据库组件内部,不对外暴露; - 上层业务全部使用轻量化 DTO,分层边界明确;避免全局大实体到处引用造成耦合。
- 数据库实体
- 业务全部组件化 消除窗体零散业务代码,组轴引擎、多线程调度、Excel 模板、数据库操作各自独立组件。
- 满足核心业务场景 ✅ 组轴式执行引擎 ✅ 多线程调度 + 任务结果统一组装汇总 ✅ 固定 Excel 模板导入导出、3C 物料组别校验 ✅ 预留扩展:BOM 树形协同组件、GDI 自定义图表控件可直接接入 ✅ SQLite 本地文件数据库可视化管理模块
- 组件低耦合 使用全局事件总线通信,减少类之间直接实例依赖,方便后续迭代维护。
如果你需要,我可以继续输出:
- BOM 树形协同
BomTreeBuilder.cs完整代码 - GDI 柱状图、仪表盘绘图控件完整代码
- App.config 配置样板
- BOM Excel 导入导出补充代码