编辑 | blame | 历史 | 原始文档

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脚本
-- =============================================
-- 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: 提交
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>"

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日志实体
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; }
    }
}
  • [ ] Step 2: 创建MES日志DTO
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; }
    }
}
  • [ ] Step 3: 创建进站请求DTO
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; }
    }
}
  • [ ] Step 4: 创建出站请求DTO
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; }
        }
    }
}
  • [ ] Step 5: 创建绑定请求DTO
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;
    }
}
  • [ ] Step 6: 创建解绑请求DTO
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; }
    }
}
  • [ ] Step 7: 创建NG上报请求DTO
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; }
        }
    }
}
  • [ ] Step 8: 提交
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>"

Task 3: 创建MES日志服务

Files:
- Create: WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs
- Create: WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs

  • [ ] Step 1: 创建MES日志服务接口
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);
    }
}
  • [ ] Step 2: 创建MES日志服务实现
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>();
            }
        }
    }
}
  • [ ] Step 3: 提交
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>"

Task 4: 扩展StockInfoController添加进站/出站接口

Files:
- Modify: WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs

  • [ ] Step 1: 在StockInfoController中添加MES接口方法

在现有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}");
    }
}
  • [ ] Step 2: 添加必要的using语句

在文件顶部添加:

using WIDESEA_DTO.MES;
using WIDESEA_DTO.Mes;
using WIDESEA_IMesService;
  • [ ] Step 3: 提交
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>"

Task 5: 扩展StockInfoDetailController添加绑定/解绑/NG上报接口

Files:
- Modify: WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs

  • [ ] Step 1: 在StockInfoDetailController中添加MES接口方法

在现有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}");
    }
}
  • [ ] Step 2: 添加必要的using语句

在文件顶部添加:

using WIDESEA_DTO.MES;
using WIDESEA_DTO.Mes;
using WIDESEA_IMesService;
  • [ ] Step 3: 提交
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>"

Task 6: 创建前端MES API调用模块

Files:
- Create: WMS/WIDESEA_WMSClient/src/api/mes.js

  • [ ] Step 1: 创建MES API模块
/**
 * 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: 提交
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>"

Task 7: 创建MES确认对话框组件

Files:
- Create: WMS/WIDESEA_WMSClient/src/components/MesConfirmDialog.vue

  • [ ] Step 1: 创建确认对话框组件
<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>
  • [ ] Step 2: 提交
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>"

Task 8: 修改stockInfo.vue添加操作列

Files:
- Modify: WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue

  • [ ] Step 1: 在script中添加组件引入和方法

在现有的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;
  }
};
  • [ ] Step 2: 在columns中添加操作列

在现有的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(' ');
    }
  }
]);
  • [ ] Step 3: 在模板中添加对话框组件

在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>
  • [ ] Step 4: 添加全局方法用于HTML字符串中的onclick

在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);
};
  • [ ] Step 5: 提交
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>"

Task 9: 修改stockInfoDetail.vue添加操作列

Files:
- Modify: WMS/WIDESEA_WMSClient/src/views/stock/stockInfoDetail.vue

  • [ ] Step 1: 在script中添加组件引入和方法

在现有的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; // 非"已锁定"状态显示所有按钮
};
  • [ ] Step 2: 在columns中添加操作列

在现有的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>
      `;
    }
  }
]);
  • [ ] Step 3: 在模板中添加对话框组件

在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>
  • [ ] Step 4: 添加全局方法

在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);
};
  • [ ] Step 5: 提交
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>"

测试检查清单

完成以上所有任务后,进行以下测试:

后端测试

  • [ ] 验证数据库表 Dt_MesApiLog 已创建
  • [ ] 验证系统配置已插入
  • [ ] 测试进站接口(Postman或Swagger)
  • [ ] 测试出站接口
  • [ ] 测试绑定接口
  • [ ] 测试解绑接口
  • [ ] 测试NG上报接口
  • [ ] 验证日志记录正确

前端测试

  • [ ] 库存信息页面操作列正常显示
  • [ ] 按钮根据状态正确显示/隐藏
  • [ ] 点击按钮弹出确认对话框
  • [ ] 确认后成功调用接口
  • [ ] 成功后刷新列表
  • [ ] 失败后显示错误提示
  • [ ] 库存明细页面操作列正常显示
  • [ ] 所有按钮功能正常

集成测试

  • [ ] 完整流程测试(点击→确认→执行→结果)
  • [ ] 网络异常处理
  • [ ] MES服务异常处理
  • [ ] 权限控制测试

备注

  1. 状态值映射:代码中使用的状态值(0=待入库, 1=在库等)需要根据实际枚举值调整
  2. 托盘码获取:库存明细页面中需要通过stockId关联获取托盘码
  3. 权限配置:需要在系统中添加MES相关权限项
  4. MES接口地址:在系统配置中设置正确的MES接口地址
  5. 错误处理:根据实际MES返回的错误码调整提示信息