# WMS库存页面MES接口集成实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 为WMS库存信息页面和库存明细页面添加操作列,调用MES系统的进站/出站/绑定/解绑/NG上报接口 **Architecture:** 前端在库存表格中添加操作列,点击后弹出确认对话框,确认后调用后端API;后端接收请求,调用MES服务,记录完整日志,返回结果 **Tech Stack:** Vue 3, Element Plus, .NET 8, SqlSugar ORM, SQL Server, HttpClient --- ## 文件结构 ### 前端文件 ``` WMS/WIDESEA_WMSClient/src/ ├── views/stock/ │ ├── stockInfo.vue # 修改:添加操作列 │ └── stockInfoDetail.vue # 修改:添加操作列 ├── components/ │ └── MesConfirmDialog.vue # 新增:MES确认对话框 └── api/ └── mes.js # 新增:MES API调用 ``` ### 后端文件 ``` WMS/WIDESEA_WMSServer/ ├── Controllers/ │ └── Stock/ │ ├── StockInfoController.cs # 修改:添加进站/出站接口 │ └── StockInfoDetailController.cs # 修改:添加绑定/解绑/NG上报接口 ├── Services/ │ └── Mes/ │ ├── IMesLogService.cs # 新增:日志服务接口 │ └── MesLogService.cs # 新增:日志服务实现 ├── DTO/ │ ├── Mes/ │ │ ├── MesApiLogDto.cs # 新增:日志DTO │ │ ├── InboundInContainerRequestDto.cs # 新增:进站请求DTO │ │ ├── OutboundInContainerRequestDto.cs # 新增:出站请求DTO │ │ ├── BindContainerRequestDto.cs # 新增:绑定请求DTO │ │ ├── UnbindContainerRequestDto.cs # 新增:解绑请求DTO │ │ └── ContainerNgReportRequestDto.cs # 新增:NG上报请求DTO │ └── Models/ │ └── Mes/ │ └── Dt_MesApiLog.cs # 新增:日志实体 └── Database/ └── Scripts/ └── 20260412_MesApiLog.sql # 新增:日志表创建脚本 ``` --- ## Task 1: 创建数据库表 **Files:** - Create: `WMS/WIDESEA_WMSServer/Database/Scripts/20260412_MesApiLog.sql` - [ ] **Step 1: 创建MES接口日志表SQL脚本** ```sql -- ============================================= -- WMS MES接口调用日志表 -- ============================================= IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Dt_MesApiLog') BEGIN CREATE TABLE Dt_MesApiLog ( Id BIGINT PRIMARY KEY IDENTITY(1,1), ApiType NVARCHAR(50) NOT NULL, -- 接口类型 RequestJson NVARCHAR(MAX) NULL, -- 请求JSON ResponseJson NVARCHAR(MAX) NULL, -- 响应JSON IsSuccess BIT NOT NULL DEFAULT 0, -- 是否成功 ErrorMessage NVARCHAR(500) NULL, -- 错误信息 ElapsedMs INT NOT NULL DEFAULT 0, -- 耗时(毫秒) CreateDate DATETIME NOT NULL, -- 创建时间 Creator NVARCHAR(50) NULL, -- 创建人 ModifyDate DATETIME NULL, -- 修改时间 Modifier NVARCHAR(50) NULL -- 修改人 ); -- 创建索引 CREATE INDEX IX_MesApiLog_ApiType ON Dt_MesApiLog(ApiType); CREATE INDEX IX_MesApiLog_CreateDate ON Dt_MesApiLog(CreateDate); CREATE INDEX IX_MesApiLog_IsSuccess ON Dt_MesApiLog(IsSuccess); PRINT 'MES接口日志表 Dt_MesApiLog 创建成功'; END ELSE BEGIN PRINT 'MES接口日志表 Dt_MesApiLog 已存在'; END -- 插入MES系统配置 IF NOT EXISTS (SELECT * FROM Dt_SystemConfig WHERE ConfigKey = 'MES_EquipmentCode') BEGIN INSERT INTO Dt_SystemConfig (ConfigKey, ConfigValue, Description, CreateDate, Modifier) VALUES ('MES_EquipmentCode', 'WCS_001', 'MES设备编码', GETDATE(), 'System'); INSERT INTO Dt_SystemConfig (ConfigKey, ConfigValue, Description, CreateDate, Modifier) VALUES ('MES_ResourceCode', 'RESOURCE_001', 'MES资源编码', GETDATE(), 'System'); INSERT INTO Dt_SystemConfig (ConfigKey, ConfigValue, Description, CreateDate, Modifier) VALUES ('MES_ApiBaseUrl', 'http://mes-server/api', 'MES接口地址', GETDATE(), 'System'); INSERT INTO Dt_SystemConfig (ConfigKey, ConfigValue, Description, CreateDate, Modifier) VALUES ('MES_TimeoutSeconds', '30', 'MES接口超时时间(秒)', GETDATE(), 'System'); PRINT 'MES系统配置插入成功'; END ``` - [ ] **Step 2: 执行SQL脚本创建表** Run: 在SQL Server Management Studio中执行该脚本 Expected: 表创建成功,配置插入成功 - [ ] **Step 3: 提交** ```bash git add WMS/WIDESEA_WMSServer/Database/Scripts/20260412_MesApiLog.sql git commit -m "feat(MES): 添加MES接口日志表和系统配置 - 创建 Dt_MesApiLog 表记录接口调用日志 - 添加MES相关系统配置项(设备编码、资源编码、接口地址等) Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 2: 创建后端实体和DTO **Files:** - Create: `WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MesApiLog.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/MesApiLogDto.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/InboundInContainerRequestDto.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/OutboundInContainerRequestDto.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/BindContainerRequestDto.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/UnbindContainerRequestDto.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/ContainerNgReportRequestDto.cs` - [ ] **Step 1: 创建MES日志实体** ```csharp using SqlSugar; using System; namespace WIDESEA_Model.Models.Mes { /// /// MES接口调用日志实体 /// [SugarTable("Dt_MesApiLog")] public class Dt_MesApiLog { /// /// 主键ID /// [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] public long Id { get; set; } /// /// 接口类型:InboundInContainer, OutboundInContainer, BindContainer, UnbindContainer, ContainerNgReport /// [SugarColumn(Length = 50, IsNullable = false)] public string ApiType { get; set; } /// /// 请求JSON /// [SugarColumn(ColumnDataType = "nvarchar(max)", IsNullable = true)] public string RequestJson { get; set; } /// /// 响应JSON /// [SugarColumn(ColumnDataType = "nvarchar(max)", IsNullable = true)] public string ResponseJson { get; set; } /// /// 是否成功 /// [SugarColumn(IsNullable = false)] public bool IsSuccess { get; set; } /// /// 错误信息 /// [SugarColumn(Length = 500, IsNullable = true)] public string ErrorMessage { get; set; } /// /// 耗时(毫秒) /// [SugarColumn(IsNullable = false)] public int ElapsedMs { get; set; } /// /// 创建时间 /// [SugarColumn(IsNullable = false)] public DateTime CreateDate { get; set; } /// /// 创建人 /// [SugarColumn(Length = 50, IsNullable = true)] public string Creator { get; set; } /// /// 修改时间 /// public DateTime? ModifyDate { get; set; } /// /// 修改人 /// [SugarColumn(Length = 50, IsNullable = true)] public string Modifier { get; set; } } } ``` - [ ] **Step 2: 创建MES日志DTO** ```csharp namespace WIDESEA_DTO.Mes { /// /// MES接口日志DTO /// public class MesApiLogDto { /// /// 接口类型 /// public string ApiType { get; set; } /// /// 请求JSON /// public string RequestJson { get; set; } /// /// 响应JSON /// public string ResponseJson { get; set; } /// /// 是否成功 /// public bool IsSuccess { get; set; } /// /// 错误信息 /// public string ErrorMessage { get; set; } /// /// 耗时(毫秒) /// public int ElapsedMs { get; set; } /// /// 创建人 /// public string Creator { get; set; } } } ``` - [ ] **Step 3: 创建进站请求DTO** ```csharp namespace WIDESEA_DTO.Mes { /// /// 托盘进站请求DTO /// public class InboundInContainerRequestDto { /// /// 托盘编号 /// public string PalletCode { get; set; } /// /// 库存ID /// public long StockId { get; set; } } } ``` - [ ] **Step 4: 创建出站请求DTO** ```csharp using System.Collections.Generic; namespace WIDESEA_DTO.Mes { /// /// 托盘出站请求DTO /// public class OutboundInContainerRequestDto { /// /// 托盘编号 /// public string PalletCode { get; set; } /// /// 库存ID /// public long StockId { get; set; } /// /// 产品参数列表(可选) /// public List ParamList { get; set; } /// /// 参数项DTO /// public class ParamItemDto { /// /// 参数编码 /// public string ParamCode { get; set; } /// /// 参数值 /// public string ParamValue { get; set; } /// /// 采集时间 /// public string CollectionTime { get; set; } } } } ``` - [ ] **Step 5: 创建绑定请求DTO** ```csharp using System.Collections.Generic; namespace WIDESEA_DTO.Mes { /// /// 托盘电芯绑定请求DTO /// public class BindContainerRequestDto { /// /// 托盘编号 /// public string PalletCode { get; set; } /// /// 电芯码列表 /// public List SfcList { get; set; } /// /// 位置信息 /// public string Location { get; set; } /// /// 操作类型:0-默认 1-进站 2-出站 3-进出站 /// public int OperationType { get; set; } = 1; } } ``` - [ ] **Step 6: 创建解绑请求DTO** ```csharp using System.Collections.Generic; namespace WIDESEA_DTO.Mes { /// /// 托盘电芯解绑请求DTO /// public class UnbindContainerRequestDto { /// /// 托盘编号 /// public string PalletCode { get; set; } /// /// 电芯码列表 /// public List SfcList { get; set; } } } ``` - [ ] **Step 7: 创建NG上报请求DTO** ```csharp using System.Collections.Generic; namespace WIDESEA_DTO.Mes { /// /// 托盘NG电芯上报请求DTO /// public class ContainerNgReportRequestDto { /// /// 托盘编号 /// public string PalletCode { get; set; } /// /// NG电芯列表 /// public List NgSfcList { get; set; } /// /// NG电芯项DTO /// public class NgSfcItemDto { /// /// 产品条码 /// public string Sfc { get; set; } /// /// NG代码 /// public string NgCode { get; set; } /// /// NG设备 /// public string NgEquipmentCode { get; set; } /// /// NG资源 /// public string NgResourceCode { get; set; } } } } ``` - [ ] **Step 8: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MesApiLog.cs git add WMS/WIDESEA_WMSServer/WIDESEA_DTO/Mes/*.cs git commit -m "feat(MES): 添加MES接口相关实体和DTO - 添加 Dt_MesApiLog 日志实体 - 添加 MesApiLogDto 及各接口请求DTO - 支持进站、出站、绑定、解绑、NG上报接口 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 3: 创建MES日志服务 **Files:** - Create: `WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs` - [ ] **Step 1: 创建MES日志服务接口** ```csharp using System.Collections.Generic; using System.Threading.Tasks; using WIDESEA_Core; using WIDESEA_DTO.Mes; namespace WIDESEA_IMesService { /// /// MES接口日志服务接口 /// public interface IMesLogService : IDependency { /// /// 记录MES接口调用日志 /// /// 日志DTO /// 是否记录成功 Task LogAsync(MesApiLogDto log); /// /// 获取最近的MES接口调用记录 /// /// 接口类型 /// 记录数量 /// 日志列表 Task> GetRecentLogsAsync(string apiType, int count = 50); } } ``` - [ ] **Step 2: 创建MES日志服务实现** ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using WIDESEA_Core; using WIDESEA_DTO.Mes; using WIDESEA_IMesService; using WIDESEA_Model.Models.Mes; namespace WIDESEA_MesService { /// /// MES接口日志服务实现 /// public class MesLogService : IMesLogService { private readonly ISqlSugarClient _db; /// /// 构造函数 /// public MesLogService(ISqlSugarClient db) { _db = db; } /// /// 记录MES接口调用日志 /// public async Task LogAsync(MesApiLogDto log) { try { var entity = new Dt_MesApiLog { ApiType = log.ApiType, RequestJson = log.RequestJson, ResponseJson = log.ResponseJson, IsSuccess = log.IsSuccess, ErrorMessage = log.ErrorMessage, ElapsedMs = log.ElapsedMs, CreateDate = DateTime.Now, Creator = log.Creator }; var result = await _db.Insertable(entity).ExecuteCommandAsync(); return result > 0; } catch (Exception ex) { // 记录日志失败不影响主流程 Console.WriteLine($"记录MES日志失败: {ex.Message}"); return false; } } /// /// 获取最近的MES接口调用记录 /// public async Task> GetRecentLogsAsync(string apiType, int count = 50) { try { var entities = await _db.Queryable() .Where(x => x.ApiType == apiType) .OrderByDescending(x => x.CreateDate) .Take(count) .ToListAsync(); return entities.Select(e => new MesApiLogDto { ApiType = e.ApiType, RequestJson = e.RequestJson, ResponseJson = e.ResponseJson, IsSuccess = e.IsSuccess, ErrorMessage = e.ErrorMessage, ElapsedMs = e.ElapsedMs, Creator = e.Creator }).ToList(); } catch (Exception ex) { Console.WriteLine($"获取MES日志失败: {ex.Message}"); return new List(); } } } } ``` - [ ] **Step 3: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs git add WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs git commit -m "feat(MES): 添加MES日志服务 - 实现 IMesLogService 接口 - 支持记录和查询MES接口调用日志 - 异常处理不影响主流程 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 4: 扩展StockInfoController添加进站/出站接口 **Files:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs` - [ ] **Step 1: 在StockInfoController中添加MES接口方法** 在现有StockInfoController类中添加以下方法: ```csharp /// /// MES日志服务 /// private readonly IMesLogService _mesLogService; /// /// MES服务(已在项目中定义) /// private readonly IMesService _mesService; /// /// 系统配置服务 /// private readonly ISystemConfigService _configService; // 在构造函数中注入这些服务(如果尚未注入) ``` 添加进站接口: ```csharp /// /// 托盘进站 - 调用MES接口 /// /// 进站请求DTO /// 操作结果 [HttpPost("inboundInContainer")] [Permission("MES_INBOUND")] public async Task InboundInContainer([FromBody] InboundInContainerRequestDto dto) { var response = new WebResponseContent(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { // 1. 参数验证 if (string.IsNullOrWhiteSpace(dto.PalletCode)) { return response.Error("托盘编号不能为空"); } // 2. 查询库存信息 var stockInfo = await _service.FindAsIQueryable(x => x.Id == dto.StockId) .FirstAsync(); if (stockInfo == null) { return response.Error("库存信息不存在"); } // 3. 验证库存状态(仅"待入库"状态允许进站) if (stockInfo.Status != 0) // 假设0=待入库 { return response.Error($"当前库存状态不允许进站操作"); } // 4. 获取系统配置 var equipmentCode = await _configService.GetConfigValueAsync("MES_EquipmentCode"); var resourceCode = await _configService.GetConfigValueAsync("MES_ResourceCode"); if (string.IsNullOrWhiteSpace(equipmentCode) || string.IsNullOrWhiteSpace(resourceCode)) { return response.Error("MES系统配置不完整,请联系管理员"); } // 5. 构造MES请求 var mesRequest = new InboundInContainerRequest { EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, ContainerCode = dto.PalletCode }; string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest); // 6. 调用MES接口 var mesResult = await _mesService.InboundInContainer(mesRequest); stopwatch.Stop(); // 7. 记录日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "InboundInContainer", RequestJson = requestJson, ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult), IsSuccess = mesResult.Success, ErrorMessage = mesResult.ErrorMessage, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); // 8. 返回结果 if (mesResult.Success) { return response.OK("托盘进站成功"); } else { return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}"); } } catch (System.Exception ex) { stopwatch.Stop(); // 记录错误日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "InboundInContainer", IsSuccess = false, ErrorMessage = ex.Message, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); return response.Error($"托盘进站失败: {ex.Message}"); } } ``` 添加出站接口: ```csharp /// /// 托盘出站 - 调用MES接口 /// /// 出站请求DTO /// 操作结果 [HttpPost("outboundInContainer")] [Permission("MES_OUTBOUND")] public async Task OutboundInContainer([FromBody] OutboundInContainerRequestDto dto) { var response = new WebResponseContent(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { // 1. 参数验证 if (string.IsNullOrWhiteSpace(dto.PalletCode)) { return response.Error("托盘编号不能为空"); } // 2. 查询库存信息 var stockInfo = await _service.FindAsIQueryable(x => x.Id == dto.StockId) .FirstAsync(); if (stockInfo == null) { return response.Error("库存信息不存在"); } // 3. 验证库存状态("在库"或"出库中"状态允许出站) if (stockInfo.Status != 1 && stockInfo.Status != 2) // 假设1=在库, 2=出库中 { return response.Error($"当前库存状态不允许出站操作"); } // 4. 获取系统配置 var equipmentCode = await _configService.GetConfigValueAsync("MES_EquipmentCode"); var resourceCode = await _configService.GetConfigValueAsync("MES_ResourceCode"); if (string.IsNullOrWhiteSpace(equipmentCode) || string.IsNullOrWhiteSpace(resourceCode)) { return response.Error("MES系统配置不完整,请联系管理员"); } // 5. 构造MES请求 var mesRequest = new OutboundInContainerRequest { EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, ContainerCode = dto.PalletCode, ParamList = dto.ParamList?.Select(p => new ParamItem { ParamCode = p.ParamCode, ParamValue = p.ParamValue, CollectionTime = DateTime.TryParse(p.CollectionTime, out var ct) ? ct : DateTime.Now }).ToList() }; string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest); // 6. 调用MES接口 var mesResult = await _mesService.OutboundInContainer(mesRequest); stopwatch.Stop(); // 7. 记录日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "OutboundInContainer", RequestJson = requestJson, ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult), IsSuccess = mesResult.Success, ErrorMessage = mesResult.ErrorMessage, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); // 8. 返回结果 if (mesResult.Success) { return response.OK("托盘出站成功"); } else { return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}"); } } catch (System.Exception ex) { stopwatch.Stop(); // 记录错误日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "OutboundInContainer", IsSuccess = false, ErrorMessage = ex.Message, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); return response.Error($"托盘出站失败: {ex.Message}"); } } ``` - [ ] **Step 2: 添加必要的using语句** 在文件顶部添加: ```csharp using WIDESEA_DTO.MES; using WIDESEA_DTO.Mes; using WIDESEA_IMesService; ``` - [ ] **Step 3: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs git commit -m "feat(MES): 库存信息页面添加进站/出站接口 - POST /api/StockInfo/inboundInContainer 托盘进站 - POST /api/StockInfo/outboundInContainer 托盘出站 - 添加库存状态校验 - 记录完整调用日志 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 5: 扩展StockInfoDetailController添加绑定/解绑/NG上报接口 **Files:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs` - [ ] **Step 1: 在StockInfoDetailController中添加MES接口方法** 在现有StockInfoDetailController类中添加依赖注入和接口方法: ```csharp /// /// MES日志服务 /// private readonly IMesLogService _mesLogService; /// /// MES服务 /// private readonly IMesService _mesService; /// /// 系统配置服务 /// private readonly ISystemConfigService _configService; ``` 添加绑定接口: ```csharp /// /// 托盘电芯绑定 - 调用MES接口 /// /// 绑定请求DTO /// 操作结果 [HttpPost("bindContainer")] [Permission("MES_BIND")] public async Task BindContainer([FromBody] BindContainerRequestDto dto) { var response = new WebResponseContent(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { // 1. 参数验证 if (string.IsNullOrWhiteSpace(dto.PalletCode)) { return response.Error("托盘编号不能为空"); } if (dto.SfcList == null || !dto.SfcList.Any()) { return response.Error("电芯码列表不能为空"); } // 2. 获取系统配置 var equipmentCode = await _configService.GetConfigValueAsync("MES_EquipmentCode"); var resourceCode = await _configService.GetConfigValueAsync("MES_ResourceCode"); if (string.IsNullOrWhiteSpace(equipmentCode) || string.IsNullOrWhiteSpace(resourceCode)) { return response.Error("MES系统配置不完整,请联系管理员"); } // 3. 构造MES请求 var mesRequest = new BindContainerRequest { EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, ContainerCode = dto.PalletCode, ContainerSfcList = dto.SfcList.Select(sfc => new ContainerSfcItem { Sfc = sfc, Location = dto.Location ?? "" }).ToList(), OperationType = dto.OperationType }; string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest); // 4. 调用MES接口 var mesResult = await _mesService.BindContainer(mesRequest); stopwatch.Stop(); // 5. 记录日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "BindContainer", RequestJson = requestJson, ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult), IsSuccess = mesResult.Success, ErrorMessage = mesResult.ErrorMessage, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); // 6. 返回结果 if (mesResult.Success) { return response.OK($"成功绑定 {dto.SfcList.Count} 个电芯"); } else { return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}"); } } catch (System.Exception ex) { stopwatch.Stop(); await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "BindContainer", IsSuccess = false, ErrorMessage = ex.Message, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); return response.Error($"电芯绑定失败: {ex.Message}"); } } ``` 添加解绑接口: ```csharp /// /// 托盘电芯解绑 - 调用MES接口 /// /// 解绑请求DTO /// 操作结果 [HttpPost("unbindContainer")] [Permission("MES_UNBIND")] public async Task UnbindContainer([FromBody] UnbindContainerRequestDto dto) { var response = new WebResponseContent(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { // 1. 参数验证 if (string.IsNullOrWhiteSpace(dto.PalletCode)) { return response.Error("托盘编号不能为空"); } if (dto.SfcList == null || !dto.SfcList.Any()) { return response.Error("电芯码列表不能为空"); } // 2. 获取系统配置 var equipmentCode = await _configService.GetConfigValueAsync("MES_EquipmentCode"); var resourceCode = await _configService.GetConfigValueAsync("MES_ResourceCode"); if (string.IsNullOrWhiteSpace(equipmentCode) || string.IsNullOrWhiteSpace(resourceCode)) { return response.Error("MES系统配置不完整,请联系管理员"); } // 3. 构造MES请求 var mesRequest = new UnBindContainerRequest { EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, ContainCode = dto.PalletCode, SfcList = dto.SfcList }; string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest); // 4. 调用MES接口 var mesResult = await _mesService.UnBindContainer(mesRequest); stopwatch.Stop(); // 5. 记录日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "UnbindContainer", RequestJson = requestJson, ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult), IsSuccess = mesResult.Success, ErrorMessage = mesResult.ErrorMessage, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); // 6. 返回结果 if (mesResult.Success) { return response.OK($"成功解绑 {dto.SfcList.Count} 个电芯"); } else { return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}"); } } catch (System.Exception ex) { stopwatch.Stop(); await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "UnbindContainer", IsSuccess = false, ErrorMessage = ex.Message, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); return response.Error($"电芯解绑失败: {ex.Message}"); } } ``` 添加NG上报接口: ```csharp /// /// 托盘NG电芯上报 - 调用MES接口 /// /// NG上报请求DTO /// 操作结果 [HttpPost("containerNgReport")] [Permission("MES_NG_REPORT")] public async Task ContainerNgReport([FromBody] ContainerNgReportRequestDto dto) { var response = new WebResponseContent(); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { // 1. 参数验证 if (string.IsNullOrWhiteSpace(dto.PalletCode)) { return response.Error("托盘编号不能为空"); } if (dto.NgSfcList == null || !dto.NgSfcList.Any()) { return response.Error("NG电芯列表不能为空"); } // 2. 获取系统配置 var equipmentCode = await _configService.GetConfigValueAsync("MES_EquipmentCode"); var resourceCode = await _configService.GetConfigValueAsync("MES_ResourceCode"); if (string.IsNullOrWhiteSpace(equipmentCode) || string.IsNullOrWhiteSpace(resourceCode)) { return response.Error("MES系统配置不完整,请联系管理员"); } // 3. 构造MES请求 var mesRequest = new ContainerNgReportRequest { EquipmentCode = equipmentCode, ResourceCode = resourceCode, LocalTime = DateTime.Now, ContainerCode = dto.PalletCode, NgSfcList = dto.NgSfcList.Select(ng => new NgSfcItem { Sfc = ng.Sfc, NgCode = ng.NgCode, NgEquipmentCode = ng.NgEquipmentCode, NgResourceCode = ng.NgResourceCode }).ToList() }; string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest); // 4. 调用MES接口 var mesResult = await _mesService.ContainerNgReport(mesRequest); stopwatch.Stop(); // 5. 记录日志 await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "ContainerNgReport", RequestJson = requestJson, ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult), IsSuccess = mesResult.Success, ErrorMessage = mesResult.ErrorMessage, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); // 6. 返回结果 if (mesResult.Success) { return response.OK($"成功上报 {dto.NgSfcList.Count} 个NG电芯"); } else { return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}"); } } catch (System.Exception ex) { stopwatch.Stop(); await _mesLogService.LogAsync(new MesApiLogDto { ApiType = "ContainerNgReport", IsSuccess = false, ErrorMessage = ex.Message, ElapsedMs = (int)stopwatch.ElapsedMilliseconds, Creator = UserContext.Current.UserName }); return response.Error($"NG上报失败: {ex.Message}"); } } ``` - [ ] **Step 2: 添加必要的using语句** 在文件顶部添加: ```csharp using WIDESEA_DTO.MES; using WIDESEA_DTO.Mes; using WIDESEA_IMesService; ``` - [ ] **Step 3: 提交** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs git commit -m "feat(MES): 库存明细页面添加绑定/解绑/NG上报接口 - POST /api/StockInfoDetail/bindContainer 托盘电芯绑定 - POST /api/StockInfoDetail/unbindContainer 托盘电芯解绑 - POST /api/StockInfoDetail/containerNgReport NG电芯上报 - 记录完整调用日志 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 6: 创建前端MES API调用模块 **Files:** - Create: `WMS/WIDESEA_WMSClient/src/api/mes.js` - [ ] **Step 1: 创建MES API模块** ```javascript /** * MES接口API模块 */ import axios from 'axios'; const baseURL = '/api'; // 库存信息相关MES接口 export const stockInfoMesApi = { /** * 托盘进站 * @param {Object} data - 请求数据 { palletCode, stockId } * @returns {Promise} */ inboundInContainer(data) { return axios.post(`${baseURL}/StockInfo/inboundInContainer`, data); }, /** * 托盘出站 * @param {Object} data - 请求数据 { palletCode, stockId, paramList } * @returns {Promise} */ outboundInContainer(data) { return axios.post(`${baseURL}/StockInfo/outboundInContainer`, data); } }; // 库存明细相关MES接口 export const stockDetailMesApi = { /** * 托盘电芯绑定 * @param {Object} data - 请求数据 { palletCode, sfcList, location, operationType } * @returns {Promise} */ bindContainer(data) { return axios.post(`${baseURL}/StockInfoDetail/bindContainer`, data); }, /** * 托盘电芯解绑 * @param {Object} data - 请求数据 { palletCode, sfcList } * @returns {Promise} */ unbindContainer(data) { return axios.post(`${baseURL}/StockInfoDetail/unbindContainer`, data); }, /** * 托盘NG电芯上报 * @param {Object} data - 请求数据 { palletCode, ngSfcList } * @returns {Promise} */ containerNgReport(data) { return axios.post(`${baseURL}/StockInfoDetail/containerNgReport`, data); } }; export default { stockInfo: stockInfoMesApi, stockDetail: stockDetailMesApi }; ``` - [ ] **Step 2: 提交** ```bash git add WMS/WIDESEA_WMSClient/src/api/mes.js git commit -m "feat(MES): 添加前端MES API调用模块 - 封装库存信息进站/出站接口 - 封装库存明细绑定/解绑/NG上报接口 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 7: 创建MES确认对话框组件 **Files:** - Create: `WMS/WIDESEA_WMSClient/src/components/MesConfirmDialog.vue` - [ ] **Step 1: 创建确认对话框组件** ```vue ``` - [ ] **Step 2: 提交** ```bash git add WMS/WIDESEA_WMSClient/src/components/MesConfirmDialog.vue git commit -m "feat(MES): 添加MES操作确认对话框组件 - 支持多种操作类型(进站、出站、绑定、解绑、NG上报) - 显示关键信息(托盘码、电芯数量等) - 错误状态展示 - 确认/取消操作 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 8: 修改stockInfo.vue添加操作列 **Files:** - Modify: `WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue` - [ ] **Step 1: 在script中添加组件引入和方法** 在现有的script setup中添加: ```javascript import MesConfirmDialog from '@/components/MesConfirmDialog.vue'; import { stockInfoMesApi } from '@/api/mes'; import { ElMessage, ElMessageBox } from 'element-plus'; import { ref } from 'vue'; // MES对话框状态 const mesDialogVisible = ref(false); const currentOperationType = ref(''); const currentStockRow = ref(null); const mesLoading = ref(false); // 打开MES确认对话框 const openMesDialog = (operationType, row) => { currentOperationType.value = operationType; currentStockRow.value = row; mesDialogVisible.value = true; }; // 处理MES确认 const handleMesConfirm = async ({ onSuccess, onError }) => { const row = currentStockRow.value; const operationType = currentOperationType.value; try { let result; switch (operationType) { case 'inbound': result = await stockInfoMesApi.inboundInContainer({ palletCode: row.palletCode, stockId: row.id }); break; case 'outbound': result = await stockInfoMesApi.outboundInContainer({ palletCode: row.palletCode, stockId: row.id }); break; } if (result.data.status) { ElMessage.success(result.data.message || '操作成功'); onSuccess(); // 刷新列表 proxy.$refs.grid.load(); } else { onError(result.data.message || '操作失败'); } } catch (error) { onError(error.response?.data?.message || error.message || '网络错误,请稍后重试'); } }; // 检查按钮是否应该显示 const shouldShowButton = (buttonType, row) => { const status = row.status; // 0=待入库, 1=在库, 2=出库中, 3=锁定 switch (buttonType) { case 'inbound': return status === 0; // 仅待入库显示进站 case 'outbound': return status === 1 || status === 2; // 在库或出库中显示出站 default: return false; } }; ``` - [ ] **Step 2: 在columns中添加操作列** 在现有的columns ref定义中添加操作列(在最后添加): ```javascript const columns = ref([ // ... 现有列定义 ... { field: "actions", title: "操作", width: 200, fixed: "right", align: "center", formatter: (row) => { const buttons = []; if (shouldShowButton('inbound', row)) { buttons.push( `进站` ); } if (shouldShowButton('outbound', row)) { buttons.push( `出站` ); } return buttons.join(' '); } } ]); ``` - [ ] **Step 3: 在模板中添加对话框组件** 在template中的view-grid标签后添加: ```vue ``` - [ ] **Step 4: 添加全局方法用于HTML字符串中的onclick** 在setup的最后添加: ```javascript // 挂载到window供HTML字符串中的onclick使用 window.handleInbound = (id) => { const row = proxy.$refs.grid.tableData.find(item => item.id === id); if (row) openMesDialog('inbound', row); }; window.handleOutbound = (id) => { const row = proxy.$refs.grid.tableData.find(item => item.id === id); if (row) openMesDialog('outbound', row); }; ``` - [ ] **Step 5: 提交** ```bash git add WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue git commit -m "feat(MES): 库存信息页面添加进站/出站操作列 - 添加操作列,根据库存状态动态显示按钮 - 待入库状态显示进站按钮 - 在库/出库中状态显示出站按钮 - 集成MES确认对话框 Co-Authored-By: Claude Opus 4.6 " ``` --- ## Task 9: 修改stockInfoDetail.vue添加操作列 **Files:** - Modify: `WMS/WIDESEA_WMSClient/src/views/stock/stockInfoDetail.vue` - [ ] **Step 1: 在script中添加组件引入和方法** 在现有的script setup中添加: ```javascript import MesConfirmDialog from '@/components/MesConfirmDialog.vue'; import { stockDetailMesApi } from '@/api/mes'; import { ElMessage } from 'element-plus'; import { ref } from 'vue'; // MES对话框状态 const mesDialogVisible = ref(false); const currentOperationType = ref(''); const currentDetailRow = ref(null); const mesLoading = ref(false); // 打开MES确认对话框 const openMesDialog = (operationType, row) => { currentOperationType.value = operationType; currentDetailRow.value = row; mesDialogVisible.value = true; }; // 处理MES确认 const handleMesConfirm = async ({ onSuccess, onError }) => { const row = currentDetailRow.value; const operationType = currentOperationType.value; try { let result; switch (operationType) { case 'bind': result = await stockDetailMesApi.bindContainer({ palletCode: row.palletCode || 'P001', // 需要从库存信息获取 sfcList: [row.serialNumber], location: row.location || '', operationType: 1 }); break; case 'unbind': result = await stockDetailMesApi.unbindContainer({ palletCode: row.palletCode || 'P001', sfcList: [row.serialNumber] }); break; case 'ngReport': result = await stockDetailMesApi.containerNgReport({ palletCode: row.palletCode || 'P001', ngSfcList: [{ sfc: row.serialNumber, ngCode: 'NG001', ngEquipmentCode: 'WCS_001', ngResourceCode: 'RESOURCE_001' }] }); break; } if (result.data.status) { ElMessage.success(result.data.message || '操作成功'); onSuccess(); proxy.$refs.grid.load(); } else { onError(result.data.message || '操作失败'); } } catch (error) { onError(error.response?.data?.message || error.message || '网络错误,请稍后重试'); } }; // 检查按钮是否应该显示 const shouldShowButton = (row) => { const status = row.status; // 1=正常, 2=异常, 99=已锁定 return status !== 99; // 非"已锁定"状态显示所有按钮 }; ``` - [ ] **Step 2: 在columns中添加操作列** 在现有的columns ref定义中添加操作列(在最后添加): ```javascript const columns = ref([ // ... 现有列定义 ... { field: "actions", title: "操作", width: 280, fixed: "right", align: "center", formatter: (row) => { if (!shouldShowButton(row)) { return '暂无可执行操作'; } return ` 绑定 解绑 NG上报 `; } } ]); ``` - [ ] **Step 3: 在模板中添加对话框组件** 在template中的view-grid标签后添加: ```vue ``` - [ ] **Step 4: 添加全局方法** 在setup的最后添加: ```javascript window.handleBind = (id) => { const row = proxy.$refs.grid.tableData.find(item => item.id === id); if (row) openMesDialog('bind', row); }; window.handleUnbind = (id) => { const row = proxy.$refs.grid.tableData.find(item => item.id === id); if (row) openMesDialog('unbind', row); }; window.handleNgReport = (id) => { const row = proxy.$refs.grid.tableData.find(item => item.id === id); if (row) openMesDialog('ngReport', row); }; ``` - [ ] **Step 5: 提交** ```bash git add WMS/WIDESEA_WMSClient/src/views/stock/stockInfoDetail.vue git commit -m "feat(MES): 库存明细页面添加绑定/解绑/NG上报操作列 - 添加操作列,根据电芯状态动态显示按钮 - 非锁定状态显示绑定、解绑、NG上报按钮 - 集成MES确认对话框 Co-Authored-By: Claude Opus 4.6 " ``` --- ## 测试检查清单 完成以上所有任务后,进行以下测试: ### 后端测试 - [ ] 验证数据库表 Dt_MesApiLog 已创建 - [ ] 验证系统配置已插入 - [ ] 测试进站接口(Postman或Swagger) - [ ] 测试出站接口 - [ ] 测试绑定接口 - [ ] 测试解绑接口 - [ ] 测试NG上报接口 - [ ] 验证日志记录正确 ### 前端测试 - [ ] 库存信息页面操作列正常显示 - [ ] 按钮根据状态正确显示/隐藏 - [ ] 点击按钮弹出确认对话框 - [ ] 确认后成功调用接口 - [ ] 成功后刷新列表 - [ ] 失败后显示错误提示 - [ ] 库存明细页面操作列正常显示 - [ ] 所有按钮功能正常 ### 集成测试 - [ ] 完整流程测试(点击→确认→执行→结果) - [ ] 网络异常处理 - [ ] MES服务异常处理 - [ ] 权限控制测试 --- ## 备注 1. **状态值映射**:代码中使用的状态值(0=待入库, 1=在库等)需要根据实际枚举值调整 2. **托盘码获取**:库存明细页面中需要通过stockId关联获取托盘码 3. **权限配置**:需要在系统中添加MES相关权限项 4. **MES接口地址**:在系统配置中设置正确的MES接口地址 5. **错误处理**:根据实际MES返回的错误码调整提示信息