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 # 新增:日志表创建脚本
Files:
- Create: WMS/WIDESEA_WMSServer/Database/Scripts/20260412_MesApiLog.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
Run: 在SQL Server Management Studio中执行该脚本
Expected: 表创建成功,配置插入成功
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 <noreply@anthropic.com>"
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
using SqlSugar;
using System;
namespace WIDESEA_Model.Models.Mes
{
/// <summary>
/// MES接口调用日志实体
/// </summary>
[SugarTable("Dt_MesApiLog")]
public class Dt_MesApiLog
{
/// <summary>
/// 主键ID
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 接口类型:InboundInContainer, OutboundInContainer, BindContainer, UnbindContainer, ContainerNgReport
/// </summary>
[SugarColumn(Length = 50, IsNullable = false)]
public string ApiType { get; set; }
/// <summary>
/// 请求JSON
/// </summary>
[SugarColumn(ColumnDataType = "nvarchar(max)", IsNullable = true)]
public string RequestJson { get; set; }
/// <summary>
/// 响应JSON
/// </summary>
[SugarColumn(ColumnDataType = "nvarchar(max)", IsNullable = true)]
public string ResponseJson { get; set; }
/// <summary>
/// 是否成功
/// </summary>
[SugarColumn(IsNullable = false)]
public bool IsSuccess { get; set; }
/// <summary>
/// 错误信息
/// </summary>
[SugarColumn(Length = 500, IsNullable = true)]
public string ErrorMessage { get; set; }
/// <summary>
/// 耗时(毫秒)
/// </summary>
[SugarColumn(IsNullable = false)]
public int ElapsedMs { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(IsNullable = false)]
public DateTime CreateDate { get; set; }
/// <summary>
/// 创建人
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string Creator { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public DateTime? ModifyDate { get; set; }
/// <summary>
/// 修改人
/// </summary>
[SugarColumn(Length = 50, IsNullable = true)]
public string Modifier { get; set; }
}
}
namespace WIDESEA_DTO.Mes
{
/// <summary>
/// MES接口日志DTO
/// </summary>
public class MesApiLogDto
{
/// <summary>
/// 接口类型
/// </summary>
public string ApiType { get; set; }
/// <summary>
/// 请求JSON
/// </summary>
public string RequestJson { get; set; }
/// <summary>
/// 响应JSON
/// </summary>
public string ResponseJson { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// 耗时(毫秒)
/// </summary>
public int ElapsedMs { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string Creator { get; set; }
}
}
namespace WIDESEA_DTO.Mes
{
/// <summary>
/// 托盘进站请求DTO
/// </summary>
public class InboundInContainerRequestDto
{
/// <summary>
/// 托盘编号
/// </summary>
public string PalletCode { get; set; }
/// <summary>
/// 库存ID
/// </summary>
public long StockId { get; set; }
}
}
using System.Collections.Generic;
namespace WIDESEA_DTO.Mes
{
/// <summary>
/// 托盘出站请求DTO
/// </summary>
public class OutboundInContainerRequestDto
{
/// <summary>
/// 托盘编号
/// </summary>
public string PalletCode { get; set; }
/// <summary>
/// 库存ID
/// </summary>
public long StockId { get; set; }
/// <summary>
/// 产品参数列表(可选)
/// </summary>
public List<ParamItemDto> ParamList { get; set; }
/// <summary>
/// 参数项DTO
/// </summary>
public class ParamItemDto
{
/// <summary>
/// 参数编码
/// </summary>
public string ParamCode { get; set; }
/// <summary>
/// 参数值
/// </summary>
public string ParamValue { get; set; }
/// <summary>
/// 采集时间
/// </summary>
public string CollectionTime { get; set; }
}
}
}
using System.Collections.Generic;
namespace WIDESEA_DTO.Mes
{
/// <summary>
/// 托盘电芯绑定请求DTO
/// </summary>
public class BindContainerRequestDto
{
/// <summary>
/// 托盘编号
/// </summary>
public string PalletCode { get; set; }
/// <summary>
/// 电芯码列表
/// </summary>
public List<string> SfcList { get; set; }
/// <summary>
/// 位置信息
/// </summary>
public string Location { get; set; }
/// <summary>
/// 操作类型:0-默认 1-进站 2-出站 3-进出站
/// </summary>
public int OperationType { get; set; } = 1;
}
}
using System.Collections.Generic;
namespace WIDESEA_DTO.Mes
{
/// <summary>
/// 托盘电芯解绑请求DTO
/// </summary>
public class UnbindContainerRequestDto
{
/// <summary>
/// 托盘编号
/// </summary>
public string PalletCode { get; set; }
/// <summary>
/// 电芯码列表
/// </summary>
public List<string> SfcList { get; set; }
}
}
using System.Collections.Generic;
namespace WIDESEA_DTO.Mes
{
/// <summary>
/// 托盘NG电芯上报请求DTO
/// </summary>
public class ContainerNgReportRequestDto
{
/// <summary>
/// 托盘编号
/// </summary>
public string PalletCode { get; set; }
/// <summary>
/// NG电芯列表
/// </summary>
public List<NgSfcItemDto> NgSfcList { get; set; }
/// <summary>
/// NG电芯项DTO
/// </summary>
public class NgSfcItemDto
{
/// <summary>
/// 产品条码
/// </summary>
public string Sfc { get; set; }
/// <summary>
/// NG代码
/// </summary>
public string NgCode { get; set; }
/// <summary>
/// NG设备
/// </summary>
public string NgEquipmentCode { get; set; }
/// <summary>
/// NG资源
/// </summary>
public string NgResourceCode { get; set; }
}
}
}
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 <noreply@anthropic.com>"
Files:
- Create: WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs
- Create: WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_DTO.Mes;
namespace WIDESEA_IMesService
{
/// <summary>
/// MES接口日志服务接口
/// </summary>
public interface IMesLogService : IDependency
{
/// <summary>
/// 记录MES接口调用日志
/// </summary>
/// <param name="log">日志DTO</param>
/// <returns>是否记录成功</returns>
Task<bool> LogAsync(MesApiLogDto log);
/// <summary>
/// 获取最近的MES接口调用记录
/// </summary>
/// <param name="apiType">接口类型</param>
/// <param name="count">记录数量</param>
/// <returns>日志列表</returns>
Task<List<MesApiLogDto>> GetRecentLogsAsync(string apiType, int count = 50);
}
}
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
{
/// <summary>
/// MES接口日志服务实现
/// </summary>
public class MesLogService : IMesLogService
{
private readonly ISqlSugarClient _db;
/// <summary>
/// 构造函数
/// </summary>
public MesLogService(ISqlSugarClient db)
{
_db = db;
}
/// <summary>
/// 记录MES接口调用日志
/// </summary>
public async Task<bool> 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;
}
}
/// <summary>
/// 获取最近的MES接口调用记录
/// </summary>
public async Task<List<MesApiLogDto>> GetRecentLogsAsync(string apiType, int count = 50)
{
try
{
var entities = await _db.Queryable<Dt_MesApiLog>()
.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<MesApiLogDto>();
}
}
}
}
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 <noreply@anthropic.com>"
Files:
- Modify: WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs
在现有StockInfoController类中添加以下方法:
/// <summary>
/// MES日志服务
/// </summary>
private readonly IMesLogService _mesLogService;
/// <summary>
/// MES服务(已在项目中定义)
/// </summary>
private readonly IMesService _mesService;
/// <summary>
/// 系统配置服务
/// </summary>
private readonly ISystemConfigService _configService;
// 在构造函数中注入这些服务(如果尚未注入)
添加进站接口:
/// <summary>
/// 托盘进站 - 调用MES接口
/// </summary>
/// <param name="dto">进站请求DTO</param>
/// <returns>操作结果</returns>
[HttpPost("inboundInContainer")]
[Permission("MES_INBOUND")]
public async Task<WebResponseContent> 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}");
}
}
添加出站接口:
/// <summary>
/// 托盘出站 - 调用MES接口
/// </summary>
/// <param name="dto">出站请求DTO</param>
/// <returns>操作结果</returns>
[HttpPost("outboundInContainer")]
[Permission("MES_OUTBOUND")]
public async Task<WebResponseContent> 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}");
}
}
在文件顶部添加:
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Mes;
using WIDESEA_IMesService;
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 <noreply@anthropic.com>"
Files:
- Modify: WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs
在现有StockInfoDetailController类中添加依赖注入和接口方法:
/// <summary>
/// MES日志服务
/// </summary>
private readonly IMesLogService _mesLogService;
/// <summary>
/// MES服务
/// </summary>
private readonly IMesService _mesService;
/// <summary>
/// 系统配置服务
/// </summary>
private readonly ISystemConfigService _configService;
添加绑定接口:
/// <summary>
/// 托盘电芯绑定 - 调用MES接口
/// </summary>
/// <param name="dto">绑定请求DTO</param>
/// <returns>操作结果</returns>
[HttpPost("bindContainer")]
[Permission("MES_BIND")]
public async Task<WebResponseContent> 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}");
}
}
添加解绑接口:
/// <summary>
/// 托盘电芯解绑 - 调用MES接口
/// </summary>
/// <param name="dto">解绑请求DTO</param>
/// <returns>操作结果</returns>
[HttpPost("unbindContainer")]
[Permission("MES_UNBIND")]
public async Task<WebResponseContent> 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上报接口:
/// <summary>
/// 托盘NG电芯上报 - 调用MES接口
/// </summary>
/// <param name="dto">NG上报请求DTO</param>
/// <returns>操作结果</returns>
[HttpPost("containerNgReport")]
[Permission("MES_NG_REPORT")]
public async Task<WebResponseContent> 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}");
}
}
在文件顶部添加:
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Mes;
using WIDESEA_IMesService;
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 <noreply@anthropic.com>"
Files:
- Create: WMS/WIDESEA_WMSClient/src/api/mes.js
/**
* 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
};
git add WMS/WIDESEA_WMSClient/src/api/mes.js
git commit -m "feat(MES): 添加前端MES API调用模块
- 封装库存信息进站/出站接口
- 封装库存明细绑定/解绑/NG上报接口
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
Files:
- Create: WMS/WIDESEA_WMSClient/src/components/MesConfirmDialog.vue
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="500px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="mes-confirm-content">
<p class="operation-text">{{ operationText }}</p>
<div class="info-section">
<div class="info-row" v-for="(item, index) in displayInfo" :key="index">
<span class="info-label">{{ item.label }}:</span>
<span class="info-value">{{ item.value }}</span>
</div>
</div>
<div v-if="errorMessage" class="error-message">
<el-icon><Warning /></el-icon>
<span>{{ errorMessage }}</span>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button
type="primary"
:loading="loading"
@click="handleConfirm"
>
确认执行
</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { ref, computed } from 'vue';
import { Warning } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
export default {
name: 'MesConfirmDialog',
components: {
Warning
},
props: {
modelValue: {
type: Boolean,
default: false
},
operationType: {
type: String,
required: true
},
palletCode: {
type: String,
required: true
},
stockInfo: {
type: Object,
default: null
},
detailInfo: {
type: Object,
default: null
}
},
emits: ['update:modelValue', 'confirm'],
setup(props, { emit }) {
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
});
const loading = ref(false);
const errorMessage = ref('');
const operationConfig = {
inbound: { title: '托盘进站', text: '您即将执行托盘进站操作' },
outbound: { title: '托盘出站', text: '您即将执行托盘出站操作' },
bind: { title: '电芯绑定', text: '您即将执行电芯绑定操作' },
unbind: { title: '电芯解绑', text: '您即将执行电芯解绑操作' },
ngReport: { title: 'NG上报', text: '您即将执行NG电芯上报操作' }
};
const dialogTitle = computed(() => {
return operationConfig[props.operationType]?.title || '确认操作';
});
const operationText = computed(() => {
return operationConfig[props.operationType]?.text || '';
});
const displayInfo = computed(() => {
const info = [
{ label: '托盘码', value: props.palletCode }
];
if (props.detailInfo) {
info.push({ label: '电芯数量', value: props.detailInfo.sfcCount || '-' });
}
return info;
});
const handleClose = () => {
visible.value = false;
errorMessage.value = '';
};
const handleConfirm = async () => {
loading.value = true;
errorMessage.value = '';
try {
emit('confirm', {
operationType: props.operationType,
palletCode: props.palletCode,
stockInfo: props.stockInfo,
detailInfo: props.detailInfo,
onSuccess: () => {
visible.value = false;
loading.value = false;
},
onError: (error) => {
errorMessage.value = error;
loading.value = false;
}
});
} catch (error) {
errorMessage.value = error.message || '操作失败';
loading.value = false;
}
};
return {
visible,
loading,
errorMessage,
dialogTitle,
operationText,
displayInfo,
handleClose,
handleConfirm
};
}
};
</script>
<style scoped>
.mes-confirm-content {
padding: 10px 0;
}
.operation-text {
font-size: 14px;
color: #303133;
margin-bottom: 20px;
font-weight: 500;
}
.info-section {
background: #f8fafc;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.info-row {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
color: #909399;
width: 80px;
flex-shrink: 0;
}
.info-value {
color: #303133;
font-weight: 500;
}
.error-message {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: #fef0f0;
border: 1px solid #fde2e2;
border-radius: 6px;
color: #f56c6c;
font-size: 14px;
}
.error-message .el-icon {
font-size: 18px;
}
</style>
git add WMS/WIDESEA_WMSClient/src/components/MesConfirmDialog.vue
git commit -m "feat(MES): 添加MES操作确认对话框组件
- 支持多种操作类型(进站、出站、绑定、解绑、NG上报)
- 显示关键信息(托盘码、电芯数量等)
- 错误状态展示
- 确认/取消操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
Files:
- Modify: WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue
在现有的script setup中添加:
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;
}
};
在现有的columns ref定义中添加操作列(在最后添加):
const columns = ref([
// ... 现有列定义 ...
{
field: "actions",
title: "操作",
width: 200,
fixed: "right",
align: "center",
formatter: (row) => {
const buttons = [];
if (shouldShowButton('inbound', row)) {
buttons.push(
`<el-button type="primary" size="small" onclick="window.handleInbound(${row.id})">进站</el-button>`
);
}
if (shouldShowButton('outbound', row)) {
buttons.push(
`<el-button type="success" size="small" onclick="window.handleOutbound(${row.id})">出站</el-button>`
);
}
return buttons.join(' ');
}
}
]);
在template中的view-grid标签后添加:
<template>
<view-grid ... >
</view-grid>
<!-- MES确认对话框 -->
<MesConfirmDialog
v-model="mesDialogVisible"
:operation-type="currentOperationType"
:pallet-code="currentStockRow?.palletCode"
:stock-info="currentStockRow"
@confirm="handleMesConfirm"
/>
</template>
在setup的最后添加:
// 挂载到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);
};
git add WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue
git commit -m "feat(MES): 库存信息页面添加进站/出站操作列
- 添加操作列,根据库存状态动态显示按钮
- 待入库状态显示进站按钮
- 在库/出库中状态显示出站按钮
- 集成MES确认对话框
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
Files:
- Modify: WMS/WIDESEA_WMSClient/src/views/stock/stockInfoDetail.vue
在现有的script setup中添加:
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; // 非"已锁定"状态显示所有按钮
};
在现有的columns ref定义中添加操作列(在最后添加):
const columns = ref([
// ... 现有列定义 ...
{
field: "actions",
title: "操作",
width: 280,
fixed: "right",
align: "center",
formatter: (row) => {
if (!shouldShowButton(row)) {
return '<span class="text-muted">暂无可执行操作</span>';
}
return `
<el-button type="primary" size="small" onclick="window.handleBind(${row.id})">绑定</el-button>
<el-button type="warning" size="small" onclick="window.handleUnbind(${row.id})">解绑</el-button>
<el-button type="danger" size="small" onclick="window.handleNgReport(${row.id})">NG上报</el-button>
`;
}
}
]);
在template中的view-grid标签后添加:
<template>
<view-grid ... >
</view-grid>
<!-- MES确认对话框 -->
<MesConfirmDialog
v-model="mesDialogVisible"
:operation-type="currentOperationType"
:pallet-code="currentDetailRow?.palletCode"
:detail-info="{ sfcCount: 1 }"
@confirm="handleMesConfirm"
/>
</template>
在setup的最后添加:
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);
};
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 <noreply@anthropic.com>"
完成以上所有任务后,进行以下测试: