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

MES 接口调用日志页面实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.

目标: 在 WMS 系统中添加 MES 接口调用日志查看页面,提供综合性的日志查询、统计和管理功能。

架构: 后端使用 C# / ASP.NET Core,前端使用 Vue 3 + Element Plus。复用现有 view-grid 模式,添加自定义统计卡片。

技术栈: .NET 8, SqlSugar, Vue 3, Element Plus 2.2.14, Vite

版本: v0.3 (修订版 - 修复审查问题)


设计文档

参考: docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md


文件结构

后端新建文件

WMS/WIDESEA_WMSServer/
├── WIDESEA_DTO/MES/
│   ├── MesLogQueryDto.cs           # 查询请求 DTO
│   ├── MesLogStatisticsDto.cs       # 统计数据 DTO
│   ├── MesLogListItemDto.cs         # 列表项 DTO
│   └── MesLogDetailDto.cs           # 详情 DTO
├── WIDESEA_IMesService/
│   └── IMesLogService.cs            # 扩展服务接口
├── WIDESEA_MesService/
│   └── MesLogService.cs             # 服务实现
└── WIDESEA_WMSServer/Controllers/Mes/
    └── MesLogController.cs          # API 控制器

前端新建文件

WMS/WIDESEA_WMSClient/src/
├── components/
│   └── MesLogStatistics.vue         # 统计卡片组件
├── views/system/
│   └── Mes_Log.vue                  # 日志页面
└── extension/system/
    └── Mes_Log.jsx                  # 业务扩展逻辑

修改文件

WMS/WIDESEA_WMSClient/src/
└── router/viewGird.js               # 添加路由配置

Task 1: 创建后端 DTO 文件

Files:
- Create: WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogQueryDto.cs
- Create: WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogStatisticsDto.cs
- Create: WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogListItemDto.cs
- Create: WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogDetailDto.cs

Step 1: 创建 MesLogQueryDto.cs

using System;

namespace WIDESEA_DTO.MES
{
    /// <summary>
    /// MES日志查询请求DTO
    /// </summary>
    public class MesLogQueryDto
    {
        /// <summary>
        /// 接口类型: BindContainer, UnBindContainer, ContainerNgReport, InboundInContainer, OutboundInContainer
        /// </summary>
        public string ApiType { get; set; }

        /// <summary>
        /// 成功状态: null-全部, true-成功, false-失败
        /// </summary>
        public bool? IsSuccess { get; set; }

        /// <summary>
        /// 开始时间(前端 dateRange 字段映射:dateRange[0] → StartTime)
        /// </summary>
        public DateTime? StartTime { get; set; }

        /// <summary>
        /// 结束时间(前端 dateRange 字段映射:dateRange[1] → EndTime)
        /// </summary>
        public DateTime? EndTime { get; set; }

        /// <summary>
        /// 创建人(操作人)
        /// </summary>
        public string Creator { get; set; }

        /// <summary>
        /// 最小耗时(毫秒)(前端 elapsedRange 字段映射:elapsedRange[0] → MinElapsedMs)
        /// </summary>
        public int? MinElapsedMs { get; set; }

        /// <summary>
        /// 最大耗时(毫秒)(前端 elapsedRange 字段映射:elapsedRange[1] → MaxElapsedMs)
        /// </summary>
        public int? MaxElapsedMs { get; set; }

        /// <summary>
        /// 错误信息关键字
        /// </summary>
        public string ErrorKeyword { get; set; }

        /// <summary>
        /// 请求JSON关键字
        /// </summary>
        public string JsonRequestKeyword { get; set; }

        /// <summary>
        /// 响应JSON关键字
        /// </summary>
        public string JsonResponseKeyword { get; set; }
    }
}

Step 2: 创建 MesLogStatisticsDto.cs

using System;
using System.Collections.Generic;

namespace WIDESEA_DTO.MES
{
    /// <summary>
    /// MES日志统计数据DTO
    /// </summary>
    public class MesLogStatisticsDto
    {
        /// <summary>
        /// 总调用次数
        /// </summary>
        public int TotalCount { get; set; }

        /// <summary>
        /// 成功次数
        /// </summary>
        public int SuccessCount { get; set; }

        /// <summary>
        /// 成功率(百分比)
        /// </summary>
        public double SuccessRate { get; set; }

        /// <summary>
        /// 失败次数
        /// </summary>
        public int FailedCount { get; set; }

        /// <summary>
        /// 平均耗时(毫秒)
        /// </summary>
        public double AvgElapsedMs { get; set; }

        /// <summary>
        /// 最大耗时(毫秒)
        /// </summary>
        public int MaxElapsedMs { get; set; }

        /// <summary>
        /// 今日调用次数
        /// </summary>
        public int TodayCount { get; set; }

        /// <summary>
        /// 各接口类型调用次数统计
        /// </summary>
        public Dictionary<string, int> ApiTypeCounts { get; set; }
    }
}

Step 3: 创建 MesLogListItemDto.cs

using System;

namespace WIDESEA_DTO.MES
{
    /// <summary>
    /// MES日志列表项DTO
    /// </summary>
    public class MesLogListItemDto
    {
        /// <summary>
        /// 主键ID
        /// </summary>
        public long Id { get; set; }

        /// <summary>
        /// 接口类型
        /// </summary>
        public string ApiType { get; set; }

        /// <summary>
        /// 是否成功
        /// </summary>
        public bool IsSuccess { get; set; }

        /// <summary>
        /// 请求JSON预览(前200字符)
        /// </summary>
        public string RequestJsonPreview { get; set; }

        /// <summary>
        /// 响应JSON预览(前200字符)
        /// </summary>
        public string ResponseJsonPreview { get; set; }

        /// <summary>
        /// 错误信息
        /// </summary>
        public string ErrorMessage { get; set; }

        /// <summary>
        /// 耗时(毫秒)
        /// </summary>
        public int ElapsedMs { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateDate { get; set; }

        /// <summary>
        /// 创建人
        /// </summary>
        public string Creator { get; set; }
    }
}

Step 4: 创建 MesLogDetailDto.cs

using System;

namespace WIDESEA_DTO.MES
{
    /// <summary>
    /// MES日志详情DTO
    /// </summary>
    public class MesLogDetailDto : MesLogListItemDto
    {
        /// <summary>
        /// 完整请求JSON
        /// </summary>
        public string RequestJson { get; set; }

        /// <summary>
        /// 完整响应JSON
        /// </summary>
        public string ResponseJson { get; set; }

        /// <summary>
        /// 修改时间
        /// </summary>
        public DateTime? ModifyDate { get; set; }

        /// <summary>
        /// 修改人
        /// </summary>
        public string Modifier { get; set; }
    }
}

Step 5: 提交 DTO 文件

git add WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLog*.cs
git commit -m "feat(MES): 添加MES日志查询DTO文件

- MesLogQueryDto: 查询请求参数
- MesLogStatisticsDto: 统计数据
- MesLogListItemDto: 列表项
- MesLogDetailDto: 详情

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 2: 扩展 IMesLogService 接口

Files:
- Modify: WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs

Step 1: 读取现有接口

cat WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs

Step 2: 添加新方法到接口

在现有接口中添加以下方法:

/// <summary>
/// 分页查询MES日志
/// </summary>
/// <param name="query">查询条件</param>
/// <param name="page">页码</param>
/// <param name="pageSize">每页数量</param>
/// <returns>日志列表和总数</returns>
Task<(List<MesLogListItemDto> items, int total)> GetPageAsync(MesLogQueryDto query, int page, int pageSize);

/// <summary>
/// 获取单条日志详情
/// </summary>
/// <param name="id">日志ID</param>
/// <returns>日志详情</returns>
Task<MesLogDetailDto> GetDetailAsync(long id);

/// <summary>
/// 获取统计数据
/// </summary>
/// <param name="query">查询条件</param>
/// <returns>统计数据</returns>
Task<MesLogStatisticsDto> GetStatisticsAsync(MesLogQueryDto query);

/// <summary>
/// 导出日志数据
/// </summary>
/// <param name="query">查询条件</param>
/// <returns>Excel文件字节数组</returns>
Task<byte[]> ExportAsync(MesLogQueryDto query);

Step 3: 添加 using 语句

确保文件顶部有:
csharp using WIDESEA_DTO.MES;

Step 4: 提交接口扩展

git add WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs
git commit -m "feat(MES): 扩展IMesLogService接口

- 添加分页查询方法 GetPageAsync
- 添加详情查询方法 GetDetailAsync
- 添加统计查询方法 GetStatisticsAsync
- 添加导出方法 ExportAsync

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 3: 实现 MesLogService 方法

Files:
- Modify: WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs

Step 1: 读取现有服务实现

cat WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs

Step 2: 添加 GetPageAsync 实现

/// <summary>
/// 分页查询MES日志
/// </summary>
public async Task<(List<MesLogListItemDto> items, int total)> GetPageAsync(MesLogQueryDto query, int page, int pageSize)
{
    var dbQuery = _db.Queryable<WIDESEA_Model.Models.Mes.Dt_MesApiLog>();

    // 动态条件筛选
    if (!string.IsNullOrEmpty(query.ApiType))
    {
        dbQuery = dbQuery.Where(x => x.ApiType == query.ApiType);
    }

    if (query.IsSuccess.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.IsSuccess == query.IsSuccess.Value);
    }

    if (query.StartTime.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.CreateDate >= query.StartTime.Value);
    }

    if (query.EndTime.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.CreateDate <= query.EndTime.Value);
    }

    if (!string.IsNullOrEmpty(query.Creator))
    {
        dbQuery = dbQuery.Where(x => x.Creator.Contains(query.Creator));
    }

    if (query.MinElapsedMs.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.ElapsedMs >= query.MinElapsedMs.Value);
    }

    if (query.MaxElapsedMs.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.ElapsedMs <= query.MaxElapsedMs.Value);
    }

    if (!string.IsNullOrEmpty(query.ErrorKeyword))
    {
        dbQuery = dbQuery.Where(x => x.ErrorMessage.Contains(query.ErrorKeyword));
    }

    if (!string.IsNullOrEmpty(query.JsonRequestKeyword))
    {
        dbQuery = dbQuery.Where(x => x.RequestJson.Contains(query.JsonRequestKeyword));
    }

    if (!string.IsNullOrEmpty(query.JsonResponseKeyword))
    {
        dbQuery = dbQuery.Where(x => x.ResponseJson.Contains(query.JsonResponseKeyword));
    }

    // 获取总数
    var total = await dbQuery.CountAsync();

    // 分页查询
    var entities = await dbQuery
        .OrderByDescending(x => x.CreateDate)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    // 映射到 DTO
    var items = entities.Select(e => new MesLogListItemDto
    {
        Id = e.Id,
        ApiType = e.ApiType,
        IsSuccess = e.IsSuccess,
        RequestJsonPreview = e.RequestJson?.Length > 200 ? e.RequestJson.Substring(0, 200) + "..." : e.RequestJson,
        ResponseJsonPreview = e.ResponseJson?.Length > 200 ? e.ResponseJson.Substring(0, 200) + "..." : e.ResponseJson,
        ErrorMessage = e.ErrorMessage,
        ElapsedMs = e.ElapsedMs,
        CreateDate = e.CreateDate,
        Creator = e.Creator
    }).ToList();

    return (items, total);
}

Step 3: 添加 GetDetailAsync 实现

/// <summary>
/// 获取单条日志详情
/// </summary>
public async Task<MesLogDetailDto> GetDetailAsync(long id)
{
    var entity = await _db.Queryable<WIDESEA_Model.Models.Mes.Dt_MesApiLog>()
        .FirstAsync(x => x.Id == id);

    if (entity == null)
    {
        return null;
    }

    return new MesLogDetailDto
    {
        Id = entity.Id,
        ApiType = entity.ApiType,
        IsSuccess = entity.IsSuccess,
        RequestJson = entity.RequestJson,
        ResponseJson = entity.ResponseJson,
        RequestJsonPreview = entity.RequestJson?.Length > 200 ? entity.RequestJson.Substring(0, 200) + "..." : entity.RequestJson,
        ResponseJsonPreview = entity.ResponseJson?.Length > 200 ? entity.ResponseJson.Substring(0, 200) + "..." : entity.ResponseJson,
        ErrorMessage = entity.ErrorMessage,
        ElapsedMs = entity.ElapsedMs,
        CreateDate = entity.CreateDate,
        Creator = entity.Creator,
        ModifyDate = entity.ModifyDate,
        Modifier = entity.Modifier
    };
}

Step 4: 添加 GetStatisticsAsync 实现

/// <summary>
/// 获取统计数据
/// </summary>
public async Task<MesLogStatisticsDto> GetStatisticsAsync(MesLogQueryDto query)
{
    var dbQuery = _db.Queryable<WIDESEA_Model.Models.Mes.Dt_MesApiLog>();

    // 应用相同的筛选条件(但不分页)
    if (!string.IsNullOrEmpty(query.ApiType))
    {
        dbQuery = dbQuery.Where(x => x.ApiType == query.ApiType);
    }

    if (query.IsSuccess.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.IsSuccess == query.IsSuccess.Value);
    }

    if (query.StartTime.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.CreateDate >= query.StartTime.Value);
    }

    if (query.EndTime.HasValue)
    {
        dbQuery = dbQuery.Where(x => x.CreateDate <= query.EndTime.Value);
    }

    var allData = await dbQuery.ToListAsync();

    // 今日数据
    var today = DateTime.Today;
    var todayData = allData.Where(x => x.CreateDate >= today).ToList();

    // 计算统计数据
    var totalCount = allData.Count;
    var successCount = allData.Count(x => x.IsSuccess);
    var failedCount = totalCount - successCount;
    var successRate = totalCount > 0 ? (successCount * 100.0 / totalCount) : 0;

    return new MesLogStatisticsDto
    {
        TotalCount = totalCount,
        SuccessCount = successCount,
        FailedCount = failedCount,
        SuccessRate = Math.Round(successRate, 2),
        AvgElapsedMs = totalCount > 0 ? allData.Average(x => x.ElapsedMs) : 0,
        MaxElapsedMs = totalCount > 0 ? allData.Max(x => x.ElapsedMs) : 0,
        TodayCount = todayData.Count,
        ApiTypeCounts = allData.GroupBy(x => x.ApiType)
            .ToDictionary(g => g.Key, g => g.Count())
    };
}

Step 5: 添加 ExportAsync 实现

/// <summary>
/// 导出日志数据
/// </summary>
public async Task<byte[]> ExportAsync(MesLogQueryDto query)
{
    // 获取所有符合条件的数据(不分页)
    var (items, _) = await GetPageAsync(query, 1, int.MaxValue);

    // 使用框架内置的 Excel 导出功能
    // 注意:这里需要根据项目实际使用的导出库调整
    // 参考 ServiceBase.Export() 的实现方式

    // 临时方案:使用 CSV 格式
    using var memoryStream = new MemoryStream();
    using var writer = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

    // CSV 头部
    writer.WriteLine("ID,接口类型,状态,耗时(ms),错误信息,创建时间,创建人");

    // CSV 数据行
    foreach (var item in items)
    {
        writer.WriteLine($"{item.Id},{item.ApiType},{(item.IsSuccess ? "成功" : "失败")},{item.ElapsedMs},\"{item.ErrorMessage?.Replace("\"", "\"\"")}\",{item.CreateDate:yyyy-MM-dd HH:mm:ss},{item.Creator}");
    }

    writer.Flush();
    return memoryStream.ToArray();
}

Step 6: 添加 using 语句

确保文件顶部有:
csharp using WIDESEA_DTO.MES;

Step 7: 提交服务实现

git add WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs
git commit -m "feat(MES): 实现MesLogService查询方法

- 实现分页查询 GetPageAsync
- 实现详情查询 GetDetailAsync
- 实现统计查询 GetStatisticsAsync
- 实现导出 ExportAsync (CSV格式)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 4: 创建 MesLogController

Files:
- Create: WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Mes/MesLogController.cs

Step 1: 创建控制器文件

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_DTO.MES;
using WIDESEA_IBasicService;
using WIDESEA_IMesService;

namespace WIDESEA_WMSServer.Controllers.Mes
{
    /// <summary>
    /// MES接口日志控制器
    /// </summary>
    [Route("api/MesLog")]
    [ApiController]
    [Authorize]
    public class MesLogController : ControllerBase
    {
        private readonly IMesLogService _mesLogService;

        /// <summary>
        /// 构造函数
        /// </summary>
        public MesLogController(IMesLogService mesLogService)
        {
            _mesLogService = mesLogService;
        }

        /// <summary>
        /// 分页查询MES日志
        /// </summary>
        /// <param name="query">查询条件</param>
        /// <param name="page">页码,默认1</param>
        /// <param name="pageSize">每页数量,默认20</param>
        /// <returns>分页结果</returns>
        [HttpPost("page")]
        public async Task<WebResponseContent> GetPage([FromBody] MesLogQueryDto query, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
        {
            var response = new WebResponseContent();
            try
            {
                var (items, total) = await _mesLogService.GetPageAsync(query, page, pageSize);
                return response.OK(null, new { rows = items, total = total });
            }
            catch (Exception ex)
            {
                return response.Error($"查询失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 获取日志详情
        /// </summary>
        /// <param name="id">日志ID</param>
        /// <returns>日志详情</returns>
        [HttpGet("{id}")]
        public async Task<WebResponseContent> GetDetail(long id)
        {
            var response = new WebResponseContent();
            try
            {
                var detail = await _mesLogService.GetDetailAsync(id);
                if (detail == null)
                {
                    return response.Error("日志不存在");
                }
                return response.OK(null, detail);
            }
            catch (Exception ex)
            {
                return response.Error($"查询失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 获取统计数据
        /// </summary>
        /// <param name="query">查询条件(可选)</param>
        /// <returns>统计数据</returns>
        [HttpGet("statistics")]
        public async Task<WebResponseContent> GetStatistics([FromQuery] MesLogQueryDto query)
        {
            var response = new WebResponseContent();
            try
            {
                var statistics = await _mesLogService.GetStatisticsAsync(query ?? new MesLogQueryDto());
                return response.OK(null, statistics);
            }
            catch (Exception ex)
            {
                return response.Error($"查询失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 导出日志
        /// </summary>
        /// <param name="query">查询条件</param>
        /// <returns>Excel文件</returns>
        [HttpPost("export")]
        public async Task<IActionResult> Export([FromBody] MesLogQueryDto query)
        {
            try
            {
                var data = await _mesLogService.ExportAsync(query ?? new MesLogQueryDto());
                var fileName = $"MES接口日志_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
                return File(data, "text/csv; charset=utf-8", fileName);
            }
            catch (Exception ex)
            {
                return BadRequest(new { error = ex.Message });
            }
        }
    }
}

Step 2: 提交控制器

git add WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Mes/MesLogController.cs
git commit -m "feat(MES): 添加MesLogController控制器

- POST /api/MesLog/page - 分页查询
- GET /api/MesLog/{id} - 详情查询
- GET /api/MesLog/statistics - 统计数据
- POST /api/MesLog/export - 导出CSV

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 5: 创建前端统计卡片组件

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

Step 1: 创建组件文件

<template>
  <div class="mes-log-statistics">
    <el-row :gutter="16">
      <el-col :span="6">
        <el-card shadow="hover" class="stat-card stat-primary">
          <div class="stat-content">
            <div class="stat-label">总调用次数</div>
            <div class="stat-value">{{ statistics.totalCount }}</div>
            <div class="stat-unit">次</div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card shadow="hover" class="stat-card" :class="statistics.successRate >= 90 ? 'stat-success' : 'stat-warning'">
          <div class="stat-content">
            <div class="stat-label">成功率</div>
            <div class="stat-value">{{ statistics.successRate }}%</div>
            <div class="stat-sub">
              成功: {{ statistics.successCount }} / 失败: {{ statistics.failedCount }}
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card shadow="hover" class="stat-card stat-info">
          <div class="stat-content">
            <div class="stat-label">平均耗时</div>
            <div class="stat-value">{{ Math.round(statistics.avgElapsedMs) }}</div>
            <div class="stat-unit">ms</div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card shadow="hover" class="stat-card stat-secondary">
          <div class="stat-content">
            <div class="stat-label">今日调用</div>
            <div class="stat-value">{{ statistics.todayCount }}</div>
            <div class="stat-unit">次</div>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>

<script>
import { ref, onMounted } from "vue";
import http from "@/api/axios";

export default {
  name: "MesLogStatistics",
  emits: ["refresh"],
  setup(props, { emit }) {
    const statistics = ref({
      totalCount: 0,
      successCount: 0,
      failedCount: 0,
      successRate: 0,
      avgElapsedMs: 0,
      todayCount: 0
    });

    const fetchStatistics = async () => {
      try {
        const res = await http.get("/api/MesLog/statistics");
        if (res.status) {
          statistics.value = res.data;
          emit("refresh");
        }
      } catch (error) {
        console.error("获取统计数据失败:", error);
      }
    };

    onMounted(() => {
      fetchStatistics();
    });

    return {
      statistics,
      fetchStatistics
    };
  }
};
</script>

<style scoped>
.mes-log-statistics {
  margin-bottom: 16px;
}

.stat-card {
  text-align: center;
}

.stat-content {
  padding: 8px 0;
}

.stat-label {
  font-size: 14px;
  color: #909399;
  margin-bottom: 8px;
}

.stat-value {
  font-size: 28px;
  font-weight: bold;
  margin-bottom: 4px;
}

.stat-unit {
  font-size: 12px;
  color: #909399;
}

.stat-sub {
  font-size: 12px;
  color: #606266;
  margin-top: 4px;
}

.stat-primary .stat-value { color: #409EFF; }
.stat-success .stat-value { color: #67C23A; }
.stat-warning .stat-value { color: #E6A23C; }
.stat-info .stat-value { color: #909399; }
.stat-secondary .stat-value { color: #909399; }
</style>

Step 2: 提交组件

git add WMS/WIDESEA_WMSClient/src/components/MesLogStatistics.vue
git commit -m "feat(MES): 添加MesLogStatistics统计卡片组件

- 显示总调用次数、成功率、平均耗时、今日调用
- 使用 el-card 自定义实现(兼容 Element Plus 2.2.14)
- 颜色标识:蓝色/绿色/橙色/灰色

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 7: 创建前端扩展逻辑

Files:
- Create: WMS/WIDESEA_WMSClient/src/extension/system/Mes_Log.jsx

Step 1: 创建扩展文件

import { h, resolveComponent } from 'vue';

let extension = {
  components: {
    // 动态扩充组件或组件路径
    gridHeader: "",
    gridBody: '',
    gridFooter: "",
    modelHeader: "",
    modelBody: "",
    modelFooter: ""
  },
  buttons: [], // 扩展的按钮
  methods: {
    // 事件扩展
    onInit() {
      console.log("mes_log init");
      this.setFiexdSearchForm(true);
    },

    onInited() {
      this.height = this.height - 240; // 为统计卡片预留空间

      // 添加预览方法
      this.previewJson = (jsonStr) => {
        if (!jsonStr) return '-';
        try {
          const obj = JSON.parse(jsonStr);
          return JSON.stringify(obj, null, 2).substring(0, 200) + '...';
        } catch {
          return String(jsonStr).substring(0, 200) + '...';
        }
      };
    },

    // 行点击事件 - 显示 JSON 详情
    rowClick({ row, column }) {
      // 如果点击的是请求或响应列,显示详情弹窗
      if (column.property === 'requestJson' || column.property === 'responseJson') {
        this.showJsonDetail(row);
      }
    },

    // 显示 JSON 详情弹窗
    showJsonDetail(row) {
      const elDialog = resolveComponent('el-dialog');

      // 创建临时弹窗
      this.$alert('', 'JSON 详情', {
        message: h('div', { class: 'json-detail-dialog' }, [
          h('el-tabs', { modelValue: 'request' }, [
            h('el-tab-pane', { label: '请求', name: 'request' }, [
              h('pre', { class: 'json-content' }, this.formatJson(row.requestJson))
            ]),
            h('el-tab-pane', { label: '响应', name: 'response' }, [
              h('pre', { class: 'json-content' }, this.formatJson(row.responseJson))
            ])
          ])
        ]),
        customClass: 'mes-json-detail-dialog',
        dangerouslyUseHTMLString: false
      });
    },

    // 格式化 JSON
    formatJson(jsonStr) {
      if (!jsonStr) return '{}';
      try {
        const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr;
        return JSON.stringify(obj, null, 2);
      } catch {
        return String(jsonStr);
      }
    }
  }
};

export default extension;

Step 2: 提交扩展文件

git add WMS/WIDESEA_WMSClient/src/extension/system/Mes_Log.jsx
git commit -m "feat(MES): 添加Mes_Log扩展逻辑

- 添加 previewJson 辅助函数
- 添加行点击事件处理
- 添加 JSON 详情显示功能
- 调整高度为统计卡片预留空间

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 8: 创建前端页面

Files:
- Create: WMS/WIDESEA_WMSClient/src/views/system/Mes_Log.vue

Step 1: 创建页面文件

<!--
*Author:System
 *Contact:-
 *代码由框架生成,任何更改都可能导致被代码生成器覆盖
 *业务请在@/extension/system/Mes_Log.jsx此处编写
 -->
<template>
  <div class="mes-log-page">
    <!-- 统计卡片区域 -->
    <mes-log-statistics ref="statistics" />

    <!-- 日志列表 -->
    <view-grid
      ref="grid"
      :columns="columns"
      :detail="detail"
      :editFormFields="editFormFields"
      :editFormOptions="editFormOptions"
      :searchFormFields="searchFormFields"
      :searchFormOptions="searchFormOptions"
      :table="table"
      :extend="extend"
    />
  </div>
</template>

<script>
import extend from "@/extension/system/Mes_Log.jsx";
import MesLogStatistics from "@/components/MesLogStatistics.vue";
import { ref, defineComponent } from "vue";

export default defineComponent({
  name: "Mes_Log",
  components: {
    MesLogStatistics
  },
  setup() {
    const table = ref({
      key: "Id",
      cnName: "MES接口日志",
      name: "Mes_Log",
      url: "/Mes_Log/",
      sortName: "Id"
    });

    const columns = ref([
      { field: "id", title: "ID", width: 80, hidden: true },
      {
        field: "apiType",
        title: "接口类型",
        width: 130,
        bind: { key: "mesApiType", data: [] }
      },
      {
        field: "isSuccess",
        title: "状态",
        width: 80,
        bind: { key: "mesApiStatus", data: [] }
      },
      {
        field: "requestJson",
        title: "请求内容",
        width: 200,
        link: true
      },
      {
        field: "responseJson",
        title: "响应内容",
        width: 200,
        link: true
      },
      { field: "errorMessage", title: "错误信息", width: 250 },
      { field: "elapsedMs", title: "耗时(ms)", width: 100, sortable: true },
      { field: "createDate", title: "调用时间", width: 160, sortable: true },
      { field: "creator", title: "操作人", width: 100 }
    ]);

    const detail = ref({
      cnName: "MES日志详情",
      columns: [],
      sortName: "Id",
      key: "Id"
    });

    const editFormFields = ref({});
    const editFormOptions = ref([]);

    const searchFormFields = ref({
      apiType: "",
      isSuccess: "",
      dateRange: "",
      creator: "",
      elapsedRange: "",
      errorKeyword: "",
      jsonKeyword: ""
    });

    const searchFormOptions = ref([
      [
        { field: "apiType", title: "接口类型", type: "select" },
        { field: "isSuccess", title: "状态", type: "select" },
        { field: "dateRange", title: "时间范围", type: "datetimeRange" }
      ],
      [
        { field: "creator", title: "操作人", type: "text" },
        {
          field: "elapsedRange",
          title: "耗时范围(ms)",
          type: "numberRange",
          placeholder: ["最小", "最大"]
        }
      ],
      [
        { field: "errorKeyword", title: "错误关键字", type: "text" },
        { field: "jsonKeyword", title: "JSON内容关键字", type: "text" }
      ]
    ]);

    return {
      table,
      columns,
      detail,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      extend
    };
  }
});
</script>

<style scoped>
.mes-log-page {
  padding: 16px;
}
</style>

Step 2: 提交页面文件

git add WMS/WIDESEA_WMSClient/src/views/system/Mes_Log.vue
git commit -m "feat(MES): 添加Mes_Log日志页面

- 使用 view-grid 组件
- 集成统计卡片组件
- 配置搜索表单和列表列
- 支持多维度筛选

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 9: 添加路由配置

Files:
- Modify: WMS/WIDESEA_WMSClient/src/router/viewGird.js

Step 1: 读取路由文件

cat WMS/WIDESEA_WMSClient/src/router/viewGird.js | head -50

Step 2: 在 Sys_Log 路由后添加 Mes_Log 路由

viewGird.js 的路由数组中,找到 path: '/Sys_Log' 配置块(大约在文件开头),在其后添加:

  {
    path: '/Sys_Log',
    name: 'sys_Log',
    component: () => import('@/views/system/Sys_Log.vue')
  },
  {
    path: '/Mes_Log',           // ← 添加此块
    name: 'mes_Log',
    component: () => import('@/views/system/Mes_Log.vue')
  },
  {
    path: '/Sys_User',          // 下一个路由
    name: 'Sys_User',
    component: () => import('@/views/system/Sys_User.vue')
  },

插入位置:在 Sys_Log 路由块之后,Sys_User 路由块之前

Step 3: 提交路由配置

git add WMS/WIDESEA_WMSClient/src/router/viewGird.js
git commit -m "feat(MES): 添加Mes_Log路由配置

- 添加 /Mes_Log 路由
- 指向 views/system/Mes_Log.vue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 10: 数据库配置

Step 1: 执行索引创建脚本

-- 在 SQL Server Management Studio 中执行

-- 接口类型索引
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_MesApiLog_ApiType' AND object_id = OBJECT_ID('Dt_MesApiLog'))
BEGIN
    CREATE INDEX IX_MesApiLog_ApiType ON Dt_MesApiLog(ApiType);
END

-- 创建时间索引
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_MesApiLog_CreateDate' AND object_id = OBJECT_ID('Dt_MesApiLog'))
BEGIN
    CREATE INDEX IX_MesApiLog_CreateDate ON Dt_MesApiLog(CreateDate DESC);
END

-- 成功状态索引
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_MesApiLog_IsSuccess' AND object_id = OBJECT_ID('Dt_MesApiLog'))
BEGIN
    CREATE INDEX IX_MesApiLog_IsSuccess ON Dt_MesApiLog(IsSuccess);
END

-- 创建人索引
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_MesApiLog_Creator' AND object_id = OBJECT_ID('Dt_MesApiLog'))
BEGIN
    CREATE INDEX IX_MesApiLog_Creator ON Dt_MesApiLog(Creator);
END

Step 2: 添加菜单记录

-- 先查询系统管理菜单的 ID
SELECT Id, MenuName FROM Dt_Menu WHERE MenuName LIKE '%系统%' OR MenuName LIKE '%System%';

-- 假设系统管理菜单 ID 为 XXX,插入 MES 接口日志菜单
INSERT INTO Dt_Menu (ParentId, MenuName, Url, Component, Permission, Sort, Icon, CreateDate, Modifier)
VALUES (
    XXX,  -- 替换为实际的系统管理菜单 ID
    'MES接口日志',
    '/Mes_Log',
    'views/system/Mes_Log',
    'Mes_Log:view',
    (SELECT ISNULL(MAX(Sort), 0) + 1 FROM Dt_Menu WHERE ParentId = XXX),
    'el-icon-document',
    GETDATE(),
    'System'
);

Step 3: 添加数据字典记录

-- 接口类型字典
DECLARE @DictId INT;
SELECT @DictId = Id FROM Dt_Dictionary WHERE DictKey = 'mesApiType';

IF @DictId IS NULL
BEGIN
    INSERT INTO Dt_Dictionary (DictKey, DictValue, CreateDate, Modifier)
    VALUES ('mesApiType', 'MES接口类型', GETDATE(), 'System');
    SET @DictId = SCOPE_IDENTITY();
END

-- 添加接口类型选项
INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @DictId, '电芯绑定', 'BindContainer', 1, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'BindContainer');

INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @DictId, '电芯解绑', 'UnBindContainer', 2, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'UnBindContainer');

INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @DictId, 'NG上报', 'ContainerNgReport', 3, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'ContainerNgReport');

INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @DictId, '托盘进站', 'InboundInContainer', 4, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'InboundInContainer');

INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @DictId, '托盘出站', 'OutboundInContainer', 5, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'OutboundInContainer');

-- 调用状态字典
DECLARE @StatusDictId INT;
SELECT @StatusDictId = Id FROM Dt_Dictionary WHERE DictKey = 'mesApiStatus';

IF @StatusDictId IS NULL
BEGIN
    INSERT INTO Dt_Dictionary (DictKey, DictValue, CreateDate, Modifier)
    VALUES ('mesApiStatus', 'MES接口状态', GETDATE(), 'System');
    SET @StatusDictId = SCOPE_IDENTITY();
END

-- 添加状态选项
INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @StatusDictId, '成功', 'true', 1, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @StatusDictId AND [Key] = 'true');

INSERT INTO Dt_DictionaryList (DictId, Value, Key, DisplayOrder, CreateDate, Modifier)
SELECT @StatusDictId, '失败', 'false', 2, GETDATE(), 'System'
WHERE NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @StatusDictId AND [Key] = 'false');

Step 4: 保存数据库脚本

# 将上述 SQL 脚本保存到文件
cat > WMS/WIDESEA_WMSServer/Database/Scripts/20260413_MesLogPage_Config.sql << 'EOF'
-- 这里粘贴上面的 SQL 脚本
EOF

Step 5: 提交数据库脚本

git add WMS/WIDESEA_WMSServer/Database/Scripts/20260413_MesLogPage_Config.sql
git commit -m "feat(MES): 添加MES日志页面数据库配置

- 创建性能索引
- 添加菜单记录
- 添加数据字典(接口类型、状态)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"

Task 11: 后端编译测试

Step 1: 编译后端项目

cd WMS/WIDESEA_WMSServer
dotnet build WIDESEA_WMSServer.sln

Step 2: 检查编译结果

预期输出:编译成功,无错误

Step 3: 如果有错误,修复并重新编译


Task 12: 前端编译测试

Step 1: 安装依赖(如需要)

cd WMS/WIDESEA_WMSClient
npm install

Step 2: 编译前端项目

npm run build

Step 3: 检查编译结果

预期输出:编译成功,无错误


Task 13: 手动功能测试

测试清单

  • [ ] 分页查询: 能正常显示日志列表
  • [ ] 筛选功能: 接口类型、状态、时间范围筛选正常
  • [ ] 统计卡片: 显示正确的统计数据
  • [ ] JSON 详情: 点击请求/响应列能查看完整 JSON
  • [ ] 导出功能: 能导出 CSV 文件

Task 14: 代码审查与最终提交

Step 1: 查看所有变更

git status
git log --oneline -10

Step 2: 最终确认

确认所有任务已完成,功能测试通过


附录: 参考文档

  • 设计文档: docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md
  • 现有系统日志: WMS/WIDESEA_WMSClient/src/views/system/Sys_Log.vue
  • 现有扩展: WMS/WIDESEA_WMSClient/src/extension/system/Sys_Log.jsx
  • MES 实体: WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MesApiLog.cs