ce1292c9cf37195b6abd2699dfc5d6cb3e143c9b..5e851678cc02257bbbd179446de36082430ca5bc
2 天以前 wanshenmean
feat(MES): 添加Mes_Log扩展逻辑
5e8516 对比 | 目录
2 天以前 wanshenmean
feat: 更新依赖版本并优化MES接口调用
727826 对比 | 目录
已添加15个文件
已删除4个文件
已修改18个文件
4247 ■■■■■ 文件已修改
Code/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/agent-replay-e24754b1-493a-48fb-9409-1ba4fbbf8fb3.jsonl 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/idle-notif-cooldown.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/last-tool-error.json 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/mission-state.json 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/subagent-tracking.json 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/WIDESEAWCS_RedisService.csproj 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/pnpm-lock.yaml 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/components/MesLogStatistics.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/system/Mes_Log.jsx 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/system/Sys_Log.jsx 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/router/viewGird.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/system/Mes_Log.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/system/Sys_Log.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/Database/Scripts/20260413_MesLogPage_Config.sql 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/MesLogService.cs 298 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/MesService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogDetailDto.cs 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogListItemDto.cs 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogQueryDto.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogStatisticsDto.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMesLogService.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IMesService/WIDESEA_IMesService.csproj 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_MesService/WIDESEA_MesService.csproj 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MesApiLog.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Mes/MesLogController.cs 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/WIDESEA_WMSServer.csproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/plans/2026-04-13-mes-api-log-page-plan.md 1430 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md 896 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.gitignore
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
.worktrees/
Code/.omc/state/agent-replay-e24754b1-493a-48fb-9409-1ba4fbbf8fb3.jsonl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:brainstorming"}
{"t":0,"agent":"a8f801a","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a8f801a","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":50944}
{"t":0,"agent":"a51d0d4","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a51d0d4","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":27507}
{"t":0,"agent":"a05bbbd","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a05bbbd","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":9125}
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:writing-plans"}
{"t":0,"agent":"a8e49fa","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a8e49fa","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":31352}
{"t":0,"agent":"adb1029","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"adb1029","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":9005}
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:subagent-driven-development"}
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:using-git-worktrees"}
Code/.omc/state/idle-notif-cooldown.json
@@ -1,3 +1,3 @@
{
  "lastSentAt": "2026-04-12T12:35:52.350Z"
  "lastSentAt": "2026-04-13T06:33:17.906Z"
}
Code/.omc/state/last-tool-error.json
@@ -1,7 +1,7 @@
{
  "tool_name": "Read",
  "tool_input_preview": "{\"file_path\":\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WMS\\\\WIDESEA_WMSServer\\\\WIDESEA_WMSServer\\\\Controllers\\\\BatteryCellController.cs\"}",
  "error": "File does not exist. Note: your current working directory is D:\\Git\\ShanMeiXinNengYuan\\Code.",
  "timestamp": "2026-04-12T13:48:48.578Z",
  "retry_count": 1
  "tool_name": "Bash",
  "tool_input_preview": "{\"command\":\"ls \\\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WMS\\\\WIDESEA_WMSClient\\\\src\\\\api\\\\\\\" 2>/dev/null | head -20\",\"description\":\"Check api directory structure\"}",
  "error": "Exit code 2\n/usr/bin/bash: eval: line 1: unexpected EOF while looking for matching `\"'",
  "timestamp": "2026-04-13T03:27:59.200Z",
  "retry_count": 6
}
Code/.omc/state/mission-state.json
@@ -1,5 +1,5 @@
{
  "updatedAt": "2026-04-12T13:54:45.455Z",
  "updatedAt": "2026-04-13T02:47:35.467Z",
  "missions": [
    {
      "id": "session:9007b9ea-1eb6-4d24-8fe7-2c3a949eac88:none",
@@ -324,6 +324,110 @@
          "sourceKey": "session-stop:a0ff37729a29ea04f"
        }
      ]
    },
    {
      "id": "session:e24754b1-493a-48fb-9409-1ba4fbbf8fb3:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-13T02:37:40.066Z",
      "updatedAt": "2026-04-13T02:47:35.467Z",
      "status": "done",
      "workerCount": 5,
      "taskCounts": {
        "total": 5,
        "pending": 0,
        "blocked": 0,
        "inProgress": 0,
        "completed": 5,
        "failed": 0
      },
      "agents": [
        {
          "name": "code-reviewer:a8f801a",
          "role": "code-reviewer",
          "ownership": "a8f801a79ff5dda6f",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-13T02:38:31.010Z"
        },
        {
          "name": "code-reviewer:a51d0d4",
          "role": "code-reviewer",
          "ownership": "a51d0d447d3482f88",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-13T02:40:32.731Z"
        },
        {
          "name": "code-reviewer:a05bbbd",
          "role": "code-reviewer",
          "ownership": "a05bbbd6e272317db",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-13T02:41:24.944Z"
        },
        {
          "name": "code-reviewer:a8e49fa",
          "role": "code-reviewer",
          "ownership": "a8e49fa8974f48c7e",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-13T02:44:54.211Z"
        },
        {
          "name": "code-reviewer:adb1029",
          "role": "code-reviewer",
          "ownership": "adb1029c24100db60",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-13T02:47:35.467Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:a8e49fa8974f48c7e:2026-04-13T02:44:22.859Z",
          "at": "2026-04-13T02:44:22.859Z",
          "kind": "update",
          "agent": "code-reviewer:a8e49fa",
          "detail": "started code-reviewer:a8e49fa",
          "sourceKey": "session-start:a8e49fa8974f48c7e"
        },
        {
          "id": "session-stop:a8e49fa8974f48c7e:2026-04-13T02:44:54.211Z",
          "at": "2026-04-13T02:44:54.211Z",
          "kind": "completion",
          "agent": "code-reviewer:a8e49fa",
          "detail": "completed",
          "sourceKey": "session-stop:a8e49fa8974f48c7e"
        },
        {
          "id": "session-start:adb1029c24100db60:2026-04-13T02:47:26.462Z",
          "at": "2026-04-13T02:47:26.462Z",
          "kind": "update",
          "agent": "code-reviewer:adb1029",
          "detail": "started code-reviewer:adb1029",
          "sourceKey": "session-start:adb1029c24100db60"
        },
        {
          "id": "session-stop:adb1029c24100db60:2026-04-13T02:47:35.467Z",
          "at": "2026-04-13T02:47:35.467Z",
          "kind": "completion",
          "agent": "code-reviewer:adb1029",
          "detail": "completed",
          "sourceKey": "session-stop:adb1029c24100db60"
        }
      ]
    }
  ]
}
Code/.omc/state/subagent-tracking.json
@@ -242,10 +242,55 @@
      "status": "completed",
      "completed_at": "2026-04-12T13:54:45.455Z",
      "duration_ms": 7226
    },
    {
      "agent_id": "a8f801a79ff5dda6f",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-13T02:37:40.066Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-13T02:38:31.010Z",
      "duration_ms": 50944
    },
    {
      "agent_id": "a51d0d447d3482f88",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-13T02:40:05.224Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-13T02:40:32.731Z",
      "duration_ms": 27507
    },
    {
      "agent_id": "a05bbbd6e272317db",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-13T02:41:15.819Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-13T02:41:24.944Z",
      "duration_ms": 9125
    },
    {
      "agent_id": "a8e49fa8974f48c7e",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-13T02:44:22.859Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-13T02:44:54.211Z",
      "duration_ms": 31352
    },
    {
      "agent_id": "adb1029c24100db60",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-13T02:47:26.462Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-13T02:47:35.467Z",
      "duration_ms": 9005
    }
  ],
  "total_spawned": 27,
  "total_completed": 27,
  "total_spawned": 32,
  "total_completed": 32,
  "total_failed": 0,
  "last_updated": "2026-04-12T13:54:45.571Z"
  "last_updated": "2026-04-13T02:47:35.573Z"
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj
@@ -57,10 +57,11 @@
        <PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
        <PackageReference Include="Magicodes.IE.EPPlus" Version="2.7.5.1" />
        <PackageReference Include="Magicodes.IE.Excel" Version="2.7.5.1" />
        <PackageReference Include="Masuit.Tools.Core" Version="2026.1.3" />
        <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.29" />
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
        <PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.3.8" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
        <PackageReference Include="OfficeOpenXml.Core.ExcelPackage" Version="1.0.0" />
        <PackageReference Include="Serilog" Version="4.3.1" />
        <PackageReference Include="Serilog.AspNetCore" Version="6.0.0" />
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/WIDESEAWCS_RedisService.csproj
@@ -8,8 +8,8 @@
    <ItemGroup>
        <PackageReference Include="StackExchange.Redis" Version="2.7.33" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
        <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
        <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.5" />
        <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
        <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
    </ItemGroup>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -167,6 +167,7 @@
                                continue;
                            }
                            #region æ£€æµ‹æ˜¯å¦éœ€è¦ç©ºæ‰˜ç›˜
                            // ========== æ£€æŸ¥ç‰¹å®šä½ç½®æ˜¯å¦æœ‰æ‰˜ç›˜ ==========
                            // ä»Žé…ç½®ä¸­è¯»å–需要检查托盘的位置列表
@@ -206,6 +207,8 @@
                                }
                            }
                            #endregion
                            // ========== æ£€æŸ¥ PLC_STB æ ‡å¿— ==========
                            // åªæœ‰å½“ PLC_STB ä¸º 1 æ—¶æ‰å¤„理任务
                            if (command.PLC_STB != 1)
Code/WMS/WIDESEA_WMSClient/package.json
@@ -26,6 +26,7 @@
    "vue-draggable-next": "^2.0.1",
    "vue-qrcode": "^2.2.2",
    "vue-router": "^4.0.0-0",
    "vue3-json-viewer": "^2.4.1",
    "vuex": "^4.0.0-0"
  },
  "devDependencies": {
Code/WMS/WIDESEA_WMSClient/pnpm-lock.yaml
@@ -56,6 +56,9 @@
      vue-router:
        specifier: ^4.0.0-0
        version: 4.6.4(vue@3.5.30)
      vue3-json-viewer:
        specifier: ^2.4.1
        version: 2.4.1(vue@3.5.30)
      vuex:
        specifier: ^4.0.0-0
        version: 4.1.0(vue@3.5.30)
@@ -943,6 +946,9 @@
    resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
    engines: {node: '>=6.0'}
  clipboard@2.0.11:
    resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
  cliui@6.0.0:
    resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
@@ -1042,6 +1048,9 @@
  default-user-agent@1.0.0:
    resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==}
    engines: {node: '>= 0.10.0'}
  delegate@3.2.0:
    resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
  destroy@1.2.0:
    resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
@@ -1252,6 +1261,9 @@
  glob@7.2.3:
    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
  good-listener@1.2.2:
    resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
  gopd@1.2.0:
    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
@@ -1745,6 +1757,9 @@
  sdk-base@2.0.1:
    resolution: {integrity: sha512-eeG26wRwhtwYuKGCDM3LixCaxY27Pa/5lK4rLKhQa7HBjJ3U3Y+f81MMZQRsDw/8SC2Dao/83yJTXJ8aULuN8Q==}
  select@1.1.2:
    resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
  semver@5.7.2:
    resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
    hasBin: true
@@ -1896,6 +1911,9 @@
  through@2.3.8:
    resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
  tiny-emitter@2.1.0:
    resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
  to-arraybuffer@1.0.1:
    resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==}
@@ -2015,6 +2033,11 @@
    resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
    peerDependencies:
      vue: ^3.5.0
  vue3-json-viewer@2.4.1:
    resolution: {integrity: sha512-Z1sunvS58lJ3ZcpNhl3jYQapBVw2wjnXbemigfMWm3QnjCeg3CPMq8R6pxHUYahxMfPKLvrbGve6mUXqhWyLaQ==}
    peerDependencies:
      vue: ^3.5.16
  vue@3.5.30:
    resolution: {integrity: sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==}
@@ -2967,6 +2990,12 @@
  chrome-trace-event@1.0.4: {}
  clipboard@2.0.11:
    dependencies:
      good-listener: 1.2.2
      select: 1.1.2
      tiny-emitter: 2.1.0
  cliui@6.0.0:
    dependencies:
      string-width: 4.2.3
@@ -3050,6 +3079,8 @@
  default-user-agent@1.0.0:
    dependencies:
      os-name: 1.0.3
  delegate@3.2.0: {}
  destroy@1.2.0: {}
@@ -3275,6 +3306,10 @@
      minimatch: 3.1.5
      once: 1.4.0
      path-is-absolute: 1.0.1
  good-listener@1.2.2:
    dependencies:
      delegate: 3.2.0
  gopd@1.2.0: {}
@@ -3718,6 +3753,8 @@
    dependencies:
      get-ready: 1.0.0
  select@1.1.2: {}
  semver@5.7.2: {}
  semver@6.3.1: {}
@@ -3874,6 +3911,8 @@
  through@2.3.8: {}
  tiny-emitter@2.1.0: {}
  to-arraybuffer@1.0.1: {}
  tough-cookie@4.1.4:
@@ -3978,6 +4017,11 @@
      '@vue/devtools-api': 6.6.4
      vue: 3.5.30
  vue3-json-viewer@2.4.1(vue@3.5.30):
    dependencies:
      clipboard: 2.0.11
      vue: 3.5.30
  vue@3.5.30:
    dependencies:
      '@vue/compiler-dom': 3.5.30
Code/WMS/WIDESEA_WMSClient/src/components/MesLogStatistics.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
<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/http.js";
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>
Code/WMS/WIDESEA_WMSClient/src/extension/system/Mes_Log.jsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
import { h, createApp } from 'vue';
import { ElDrawer, ElIcon } from 'element-plus';
let extension = {
  components: {
    // åŠ¨æ€æ‰©å……ç»„ä»¶æˆ–ç»„ä»¶è·¯å¾„
    gridHeader: "",
    gridBody: '',
    gridFooter: "",
    modelHeader: "",
    modelBody: "",
    modelFooter: ""
  },
  buttons: [], // æ‰©å±•的按钮
  data: {
    jsonDrawerVisible: false,
    currentJson: '',
    currentJsonTitle: ''
  },
  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' && row.requestJson) {
        this.showJsonDetail(row, 'request');
      } else if (column.property === 'responseJson' && row.responseJson) {
        this.showJsonDetail(row, 'response');
      }
    },
    // æ˜¾ç¤º JSON è¯¦æƒ…抽屉
    showJsonDetail(row, type = 'request') {
      const jsonContent = type === 'request' ? row.requestJson : row.responseJson;
      const title = type === 'request' ? '📋 è¯·æ±‚ JSON' : '📥 å“åº” JSON';
      // æ ¼å¼åŒ– JSON
      let formattedJson = '';
      try {
        const obj = typeof jsonContent === 'string' ? JSON.parse(jsonContent) : jsonContent;
        formattedJson = JSON.stringify(obj, null, 2);
      } catch (e) {
        formattedJson = String(jsonContent);
      }
      // åˆ›å»ºä¸´æ—¶å®¹å™¨æ¸²æŸ“抽屉
      const container = document.createElement('div');
      document.body.appendChild(container);
      const app = createApp({
        render() {
          return h('div', [
            h(ElDrawer, {
              modelValue: true,
              'onUpdate:modelValue': (val) => {
                if (!val) {
                  app.unmount();
                  document.body.removeChild(container);
                }
              },
              title: title,
              size: '30%',
              destroyOnClose: true,
              closeOnClickModal: true
            }, {
              default: () => h('div', {
                style: {
                  height: '100%',
                  backgroundColor: '#f5f5f5',
                  padding: '16px',
                  borderRadius: '4px'
                }
              }, [
                h('pre', {
                  style: {
                    margin: '0',
                    fontSize: '14px',
                    lineHeight: '1.5',
                    fontFamily: 'Consolas, Monaco, "Courier New", monospace',
                    whiteSpace: 'pre-wrap',
                    wordBreak: 'break-all'
                  }
                }, formattedJson)
              ])
            })
          ]);
        }
      });
      app.use(window.ElementPlus);
      app.mount(container);
    },
  }
};
export default extension;
Code/WMS/WIDESEA_WMSClient/src/extension/system/Sys_Log.jsx
@@ -1,4 +1,6 @@
import { h, resolveComponent } from 'vue';
import { h, createApp } from 'vue';
import { ElDrawer } from 'element-plus';
let extension = {
  components: {
    //动态扩充组件或组件路径
@@ -20,6 +22,78 @@
    },
    onInited() {
      this.height = this.height - 170;
    },
    // è¡Œç‚¹å‡»äº‹ä»¶ - æ˜¾ç¤ºå‚数详情抽屉
    rowClick({ row, column }) {
      // å¦‚果点击的是请求参数或响应参数列,显示详情抽屉
      if (column.property === 'requestParam' && row.requestParam) {
        this.showJsonDetail(row, 'request');
      } else if (column.property === 'responseParam' && row.responseParam) {
        this.showJsonDetail(row, 'response');
      }
    },
    // æ˜¾ç¤º JSON è¯¦æƒ…抽屉
    showJsonDetail(row, type = 'request') {
      const content = type === 'request' ? row.requestParam : row.responseParam;
      const title = type === 'request' ? '📋 è¯·æ±‚参数' : '📥 å“åº”参数';
      // æ ¼å¼åŒ– JSON
      let formattedJson = '';
      try {
        const obj = typeof content === 'string' ? JSON.parse(content) : content;
        formattedJson = JSON.stringify(obj, null, 2);
      } catch (e) {
        formattedJson = String(content);
      }
      // åˆ›å»ºä¸´æ—¶å®¹å™¨æ¸²æŸ“抽屉
      const container = document.createElement('div');
      document.body.appendChild(container);
      const app = createApp({
        render() {
          return h('div', [
            h(ElDrawer, {
              modelValue: true,
              'onUpdate:modelValue': (val) => {
                if (!val) {
                  app.unmount();
                  document.body.removeChild(container);
                }
              },
              title: title,
              size: '30%',
              destroyOnClose: true,
              closeOnClickModal: true
            }, {
              default: () => h('div', {
                style: {
                  height: '30%',
                  backgroundColor: '#f5f5f5',
                  padding: '16px',
                  borderRadius: '4px'
                }
              }, [
                h('pre', {
                  style: {
                    margin: '0',
                    fontSize: '14px',
                    lineHeight: '1.5',
                    fontFamily: 'Consolas, Monaco, "Courier New", monospace',
                    whiteSpace: 'pre-wrap',
                    wordBreak: 'break-all'
                  }
                }, formattedJson)
              ])
            })
          ]);
        }
      });
      app.use(window.ElementPlus);
      app.mount(container);
    }
  }
};
Code/WMS/WIDESEA_WMSClient/src/router/viewGird.js
@@ -6,6 +6,11 @@
    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')
Code/WMS/WIDESEA_WMSClient/src/views/system/Mes_Log.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,149 @@
<!--
*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: "/MesLog/",
      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,
        formatter: (row) => {
          if (!row.requestJson) return '-';
          const preview = row.requestJson.length > 50
            ? row.requestJson.substring(0, 50) + '...'
            : row.requestJson;
          return `<span style="color: #409EFF; cursor: pointer;">${preview}</span>`;
        }
      },
      {
        field: "responseJson",
        title: "响应内容",
        width: 200,
        formatter: (row) => {
          if (!row.responseJson) return '-';
          const preview = row.responseJson.length > 50
            ? row.responseJson.substring(0, 50) + '...'
            : row.responseJson;
          return `<span style="color: #409EFF; cursor: pointer;">${preview}</span>`;
        }
      },
      { 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",dataKey: "mesApiType", data: [] },
        { field: "isSuccess", title: "状态", type: "select" ,dataKey: "mesApiStatus", data: []},
        { 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>
Code/WMS/WIDESEA_WMSClient/src/views/system/Sys_Log.vue
@@ -86,15 +86,29 @@
        field: "requestParam",
        title: "请求参数",
        type: "string",
        width: 100,
        width: 150,
        align: "left",
        formatter: (row) => {
          if (!row.requestParam) return '-';
          const preview = row.requestParam.length > 50
            ? row.requestParam.substring(0, 50) + '...'
            : row.requestParam;
          return `<span style="color: #409EFF; cursor: pointer;">${preview}</span>`;
        }
      },
      {
        field: "responseParam",
        title: "响应参数",
        type: "string",
        width: 100,
        width: 150,
        align: "left",
        formatter: (row) => {
          if (!row.responseParam) return '-';
          const preview = row.responseParam.length > 50
            ? row.responseParam.substring(0, 50) + '...'
            : row.responseParam;
          return `<span style="color: #409EFF; cursor: pointer;">${preview}</span>`;
        }
      },
      {
        field: "url",
Code/WMS/WIDESEA_WMSServer/Database/Scripts/20260413_MesLogPage_Config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
-- =============================================
-- MES æŽ¥å£æ—¥å¿—页面数据库配置脚本
-- åˆ›å»ºæ—¥æœŸ: 2026-04-13
-- è¯´æ˜Ž: åˆ›å»ºæ€§èƒ½ç´¢å¼•、菜单记录、数据字典
-- =============================================
-- =============================================
-- Step 1: åˆ›å»ºæ€§èƒ½ç´¢å¼•
-- =============================================
-- æŽ¥å£ç±»åž‹ç´¢å¼•
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);
    PRINT '索引 IX_MesApiLog_ApiType å·²åˆ›å»º';
END
ELSE
    PRINT '索引 IX_MesApiLog_ApiType å·²å­˜åœ¨';
-- åˆ›å»ºæ—¶é—´ç´¢å¼•
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);
    PRINT '索引 IX_MesApiLog_CreateDate å·²åˆ›å»º';
END
ELSE
    PRINT '索引 IX_MesApiLog_CreateDate å·²å­˜åœ¨';
-- æˆåŠŸçŠ¶æ€ç´¢å¼•
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);
    PRINT '索引 IX_MesApiLog_IsSuccess å·²åˆ›å»º';
END
ELSE
    PRINT '索引 IX_MesApiLog_IsSuccess å·²å­˜åœ¨';
-- åˆ›å»ºäººç´¢å¼•
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);
    PRINT '索引 IX_MesApiLog_Creator å·²åˆ›å»º';
END
ELSE
    PRINT '索引 IX_MesApiLog_Creator å·²å­˜åœ¨';
PRINT '=====================================';
PRINT '索引创建完成';
PRINT '=====================================';
GO
-- =============================================
-- Step 2: æ·»åŠ èœå•è®°å½•
-- =============================================
-- æŸ¥è¯¢ç³»ç»Ÿç®¡ç†èœå•çš„ ID
DECLARE @SystemMenuId INT;
SELECT TOP 1 @SystemMenuId = Id FROM Dt_Menu WHERE MenuName LIKE '%系统%' OR MenuName LIKE '%System%';
IF @SystemMenuId IS NULL
BEGIN
    PRINT '警告: æœªæ‰¾åˆ°ç³»ç»Ÿç®¡ç†èœå•,请手动确认菜单 ID';
END
ELSE
BEGIN
    PRINT '系统管理菜单 ID: ' + CAST(@SystemMenuId AS VARCHAR(10));
    -- æ£€æŸ¥ MES æŽ¥å£æ—¥å¿—菜单是否已存在
    IF NOT EXISTS (SELECT 1 FROM Dt_Menu WHERE Url = '/Mes_Log')
    BEGIN
        INSERT INTO Dt_Menu (ParentId, MenuName, Url, Component, Permission, Sort, Icon, CreateDate, Modifier)
        VALUES (
            @SystemMenuId,
            'MES接口日志',
            '/Mes_Log',
            'views/system/Mes_Log',
            'Mes_Log:view',
            (SELECT ISNULL(MAX(Sort), 0) + 1 FROM Dt_Menu WHERE ParentId = @SystemMenuId),
            'el-icon-document',
            GETDATE(),
            'System'
        );
        PRINT 'MES接口日志菜单已添加,ID: ' + CAST(SCOPE_IDENTITY() AS VARCHAR(10));
    END
    ELSE
    BEGIN
        PRINT 'MES接口日志菜单已存在';
    END
END
GO
-- =============================================
-- 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();
    PRINT 'MES接口类型字典已创建,ID: ' + CAST(@DictId AS VARCHAR(10));
END
ELSE
BEGIN
    PRINT 'MES接口类型字典已存在,ID: ' + CAST(@DictId AS VARCHAR(10));
END
-- æ·»åŠ æŽ¥å£ç±»åž‹é€‰é¡¹
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'BindContainer')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@DictId, '电芯绑定', 'BindContainer', 1, GETDATE(), 'System');
    PRINT '接口类型选项已添加: BindContainer (电芯绑定)';
END
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'UnBindContainer')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@DictId, '电芯解绑', 'UnBindContainer', 2, GETDATE(), 'System');
    PRINT '接口类型选项已添加: UnBindContainer (电芯解绑)';
END
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'ContainerNgReport')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@DictId, 'NG上报', 'ContainerNgReport', 3, GETDATE(), 'System');
    PRINT '接口类型选项已添加: ContainerNgReport (NG上报)';
END
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'InboundInContainer')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@DictId, '托盘进站', 'InboundInContainer', 4, GETDATE(), 'System');
    PRINT '接口类型选项已添加: InboundInContainer (托盘进站)';
END
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @DictId AND [Key] = 'OutboundInContainer')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@DictId, '托盘出站', 'OutboundInContainer', 5, GETDATE(), 'System');
    PRINT '接口类型选项已添加: OutboundInContainer (托盘出站)';
END
-- è°ƒç”¨çŠ¶æ€å­—å…¸
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();
    PRINT 'MES接口状态字典已创建,ID: ' + CAST(@StatusDictId AS VARCHAR(10));
END
ELSE
BEGIN
    PRINT 'MES接口状态字典已存在,ID: ' + CAST(@StatusDictId AS VARCHAR(10));
END
-- æ·»åŠ çŠ¶æ€é€‰é¡¹
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @StatusDictId AND [Key] = 'true')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@StatusDictId, '成功', 'true', 1, GETDATE(), 'System');
    PRINT '状态选项已添加: true (成功)';
END
IF NOT EXISTS (SELECT 1 FROM Dt_DictionaryList WHERE DictId = @StatusDictId AND [Key] = 'false')
BEGIN
    INSERT INTO Dt_DictionaryList (DictId, Value, [Key], DisplayOrder, CreateDate, Modifier)
    VALUES (@StatusDictId, '失败', 'false', 2, GETDATE(), 'System');
    PRINT '状态选项已添加: false (失败)';
END
PRINT '=====================================';
PRINT '数据字典配置完成';
PRINT '=====================================';
GO
-- =============================================
-- æ‰§è¡Œå®Œæ¯•
-- =============================================
PRINT '=====================================';
PRINT 'MES æŽ¥å£æ—¥å¿—页面数据库配置执行完毕';
PRINT '=====================================';
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/MesLogService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SqlSugar;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.MES;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
namespace WIDESEA_BasicService
{
    /// <summary>
    /// MES接口日志服务实现
    /// </summary>
    public class MesLogService : ServiceBase<Dt_MesApiLog, IRepository<Dt_MesApiLog>>, IMesLogService
    {
        private readonly ISqlSugarClient _db;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="db">数据库客户端</param>
        public MesLogService(ISqlSugarClient db, IRepository<Dt_MesApiLog> repository) : base(repository)
        {
            _db = db;
        }
        /// <summary>
        /// è®°å½•MES接口调用日志
        /// </summary>
        /// <param name="log">日志DTO</param>
        /// <returns>是否记录成功</returns>
        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)
            {
                // TODO: ä½¿ç”¨é¡¹ç›®æ—¥å¿—框架记录错误
                Console.WriteLine($"记录MES日志失败: {ex.Message}");
                return false;
            }
        }
        /// <summary>
        /// èŽ·å–æœ€è¿‘çš„MES接口调用记录
        /// </summary>
        /// <param name="apiType">接口类型</param>
        /// <param name="count">记录数量</param>
        /// <returns>日志列表</returns>
        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)
            {
                // TODO: ä½¿ç”¨é¡¹ç›®æ—¥å¿—框架记录错误
                Console.WriteLine($"获取MES日志失败: {ex.Message}");
                return new List<MesApiLogDto>();
            }
        }
        /// <summary>
        /// åˆ†é¡µæŸ¥è¯¢MES日志
        /// </summary>
        public async Task<(List<MesLogListItemDto> items, int total)> GetPageAsync(MesLogQueryDto query, int page, int pageSize)
        {
            var dbQuery = _db.Queryable<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 != null && 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 != null && x.ErrorMessage.Contains(query.ErrorKeyword));
            }
            if (!string.IsNullOrEmpty(query.JsonRequestKeyword))
            {
                dbQuery = dbQuery.Where(x => x.RequestJson != null && x.RequestJson.Contains(query.JsonRequestKeyword));
            }
            if (!string.IsNullOrEmpty(query.JsonResponseKeyword))
            {
                dbQuery = dbQuery.Where(x => x.ResponseJson != null && x.ResponseJson.Contains(query.JsonResponseKeyword));
            }
            // èŽ·å–æ€»æ•°
            var total = await dbQuery.CountAsync();
            // åˆ†é¡µæŸ¥è¯¢
            var entities = await dbQuery
                .OrderByDescending(x => x.CreateDate)
                .ToPageListAsync(page, pageSize);
            var items = entities.Select(e => new MesLogListItemDto
            {
                Id = e.Id,
                ApiType = e.ApiType,
                IsSuccess = e.IsSuccess,
                RequestJson = e.RequestJson?.Length > 200 ? e.RequestJson.Substring(0, 200) + "..." : e.RequestJson,
                ResponseJson = 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);
        }
        /// <summary>
        /// èŽ·å–å•æ¡æ—¥å¿—è¯¦æƒ…
        /// </summary>
        public async Task<MesLogDetailDto?> GetDetailAsync(long id)
        {
            var entity = await _db.Queryable<Dt_MesApiLog>()
                .Where(x => x.Id == id)
                .FirstAsync();
            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
            };
        }
        /// <summary>
        /// èŽ·å–ç»Ÿè®¡æ•°æ®
        /// </summary>
        public async Task<MesLogStatisticsDto> GetStatisticsAsync(MesLogQueryDto query)
        {
            var dbQuery = _db.Queryable<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 totalCount = await dbQuery.CountAsync();
            var successCount = await dbQuery.Where(x => x.IsSuccess).CountAsync();
            var failedCount = totalCount - successCount;
            var successRate = totalCount > 0 ? (successCount * 100.0 / totalCount) : 0;
            // èŽ·å–æ‰€æœ‰æ•°æ®æ¥è®¡ç®—å¹³å‡å€¼å’Œæœ€å¤§å€¼ï¼ˆSqlSugar é™åˆ¶ï¼‰
            var allData = await dbQuery.Select(x => x.ElapsedMs).ToListAsync();
            var avgElapsed = allData.Count > 0 ? allData.Average() : 0;
            var maxElapsed = allData.Count > 0 ? allData.Max() : 0;
            // ä»Šæ—¥æ•°æ®
            var today = DateTime.Today;
            var todayCount = await dbQuery.Where(x => x.CreateDate >= today).CountAsync();
            return new MesLogStatisticsDto
            {
                TotalCount = totalCount,
                SuccessCount = successCount,
                FailedCount = failedCount,
                SuccessRate = Math.Round(successRate, 2),
                AvgElapsedMs = avgElapsed,
                MaxElapsedMs = maxElapsed,
                TodayCount = todayCount
            };
        }
        /// <summary>
        /// å¯¼å‡ºæ—¥å¿—数据
        /// </summary>
        public async Task<byte[]> ExportAsync(MesLogQueryDto query)
        {
            // èŽ·å–æ‰€æœ‰ç¬¦åˆæ¡ä»¶çš„æ•°æ®ï¼ˆé™åˆ¶50000行)
            var (items, _) = await GetPageAsync(query, 1, 50000);
            using var memoryStream = new MemoryStream();
            using var writer = new StreamWriter(memoryStream, Encoding.UTF8);
            // å†™å…¥ UTF-8 BOM,使 Excel èƒ½æ­£ç¡®è¯†åˆ«ä¸­æ–‡
            writer.Write('\uFEFF');
            // CSV å¤´éƒ¨
            writer.WriteLine("ID,接口类型,状态,耗时(ms),错误信息,创建时间,创建人");
            // CSV æ•°æ®è¡Œ
            foreach (var item in items)
            {
                var error = item.ErrorMessage?.Replace("\"", "\"\"") ?? "";
                var status = item.IsSuccess ? "成功" : "失败";
                writer.WriteLine($"{item.Id},{item.ApiType},{status},{item.ElapsedMs},\"{error}\",{item.CreateDate:yyyy-MM-dd HH:mm:ss},{item.Creator ?? ""}");
            }
            writer.Flush();
            return memoryStream.ToArray();
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/MesService.cs
@@ -10,7 +10,7 @@
    /// <summary>
    /// MES服务实现 - é™•西顷刻能源科技MES系统对接
    /// </summary>
    public class MesService : IMesService
    public class MesService : IMesService
    {
        private readonly HttpClientHelper _httpClient;
        private readonly string _baseUrl;
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogDetailDto.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
using System;
namespace WIDESEA_DTO.MES
{
    /// <summary>
    /// MES日志详情DTO
    /// </summary>
    public class MesLogDetailDto
    {
        /// <summary>
        /// æ—¥å¿—ID
        /// </summary>
        public long Id { get; set; }
        /// <summary>
        /// æŽ¥å£ç±»åž‹
        /// </summary>
        public string ApiType { get; set; } = string.Empty;
        /// <summary>
        /// æ˜¯å¦æˆåŠŸ
        /// </summary>
        public bool IsSuccess { get; set; }
        /// <summary>
        /// è¯·æ±‚JSON(完整)
        /// </summary>
        public string? RequestJson { get; set; }
        /// <summary>
        /// å“åº”JSON(完整)
        /// </summary>
        public string? ResponseJson { get; set; }
        /// <summary>
        /// è¯·æ±‚JSON预览
        /// </summary>
        public string? RequestJsonPreview { get; set; }
        /// <summary>
        /// å“åº”JSON预览
        /// </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; }
        /// <summary>
        /// ä¿®æ”¹æ—¶é—´
        /// </summary>
        public DateTime? ModifyDate { get; set; }
        /// <summary>
        /// ä¿®æ”¹äºº
        /// </summary>
        public string? Modifier { get; set; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogListItemDto.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
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; } = string.Empty;
        /// <summary>
        /// æ˜¯å¦æˆåŠŸ
        /// </summary>
        public bool IsSuccess { get; set; }
        /// <summary>
        /// è¯·æ±‚JSON(预览)
        /// </summary>
        public string? RequestJson { get; set; }
        /// <summary>
        /// å“åº”JSON(预览)
        /// </summary>
        public string? ResponseJson { 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; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogQueryDto.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
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; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesLogStatisticsDto.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
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 int FailedCount { get; set; }
        /// <summary>
        /// æˆåŠŸçŽ‡ï¼ˆç™¾åˆ†æ¯”ï¼‰
        /// </summary>
        public double SuccessRate { 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; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMesLogService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.MES;
using WIDESEA_Model.Models;
namespace WIDESEA_IBasicService
{
    /// <summary>
    /// MES接口日志服务接口
    /// </summary>
    public interface IMesLogService : IService<Dt_MesApiLog>
    {
        /// <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);
        /// <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>CSV字节数组</returns>
        Task<byte[]> ExportAsync(MesLogQueryDto query);
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs
ÎļþÒÑɾ³ý
Code/WMS/WIDESEA_WMSServer/WIDESEA_IMesService/WIDESEA_IMesService.csproj
ÎļþÒÑɾ³ý
Code/WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs
ÎļþÒÑɾ³ý
Code/WMS/WIDESEA_WMSServer/WIDESEA_MesService/WIDESEA_MesService.csproj
ÎļþÒÑɾ³ý
Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MesApiLog.cs
@@ -1,7 +1,7 @@
using SqlSugar;
using System;
namespace WIDESEA_Model.Models.Mes
namespace WIDESEA_Model.Models
{
    /// <summary>
    /// MES接口调用日志实体
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs
@@ -4,6 +4,7 @@
using WIDESEA_Common.TaskEnum;
using WIDESEA_Common.WareHouseEnum;
using WIDESEA_Core;
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Task;
using WIDESEA_Model.Models;
@@ -150,18 +151,18 @@
                    if (!updateLocationResult || !updateStockResult)
                        return WebResponseContent.Instance.Error("任务完成失败");
                    // è°ƒç”¨MES托盘进站
                    //var inboundRequest = new InboundInContainerRequest
                    //{
                    //    EquipmentCode = "STK-GROUP-001",
                    //    ResourceCode = "STK-GROUP-001",
                    //    LocalTime = DateTime.Now,
                    //    ContainerCode = taskDto.PalletCode
                    //};
                    //var inboundResult = _mesService.InboundInContainer(inboundRequest);
                    //if (inboundResult == null || inboundResult.Data == null || !inboundResult.Data.IsSuccess)
                    //{
                    //    return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
                    //}
                    var inboundRequest = new InboundInContainerRequest
                    {
                        EquipmentCode = "STK-GROUP-001",
                        ResourceCode = "STK-GROUP-001",
                        LocalTime = DateTime.Now,
                        ContainerCode = taskDto.PalletCode
                    };
                    var inboundResult = _mesService.InboundInContainer(inboundRequest);
                    if (inboundResult == null || inboundResult.Data == null || !inboundResult.Data.IsSuccess)
                    {
                        return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
                    }
                    return await CompleteTaskAsync(task, "入库完成");
                });
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Mes/MesLogController.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
using Autofac.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.MES;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
namespace WIDESEA_WMSServer.Controllers.Mes
{
    /// <summary>
    /// MES接口日志控制器
    /// </summary>
    [Route("api/MesLog")]
    [ApiController]
    [Authorize]
    public class MesLogController : ApiBaseController<IMesLogService, Dt_MesApiLog>
    {
        private readonly IMesLogService _mesLogService;
        /// <summary>
        /// æž„造函数
        /// </summary>
        public MesLogController(IMesLogService mesLogService) : base(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>CSV文件</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 });
            }
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs
@@ -7,7 +7,6 @@
using WIDESEA_DTO.MES;
using WIDESEA_IStockService;
using WIDESEA_IBasicService;
using WIDESEA_IMesService;
using WIDESEA_ISystemService;
using WIDESEA_Model.Models;
using WIDESEA_Common.StockEnum;
@@ -82,28 +81,22 @@
                    return response.Error($"当前库存状态不允许进站操作,当前状态:{stockInfo.StockStatus}");
                }
                // 4. èŽ·å–ç³»ç»Ÿé…ç½® - ç›´æŽ¥ä»Žæ•°æ®åº“查询
                var configs = _sysDictionaryService.GetVueDictionary(new[] { "MES_EquipmentCode", "MES_ResourceCode" });
                string equipmentCode = GetConfigValue(configs, "MES_EquipmentCode", "WCS_001");
                string resourceCode = GetConfigValue(configs, "MES_ResourceCode", "RESOURCE_001");
                // 5. æž„造MES请求
                // 4. æž„造MES请求
                var mesRequest = new InboundInContainerRequest
                {
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    LocalTime = DateTime.Now,
                    ContainerCode = dto.PalletCode
                };
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                // 6. è°ƒç”¨MES接口(同步方法)
                // 5. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.InboundInContainer(mesRequest);
                stopwatch.Stop();
                // 7. è®°å½•日志
                // 6. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "InboundInContainer",
@@ -115,7 +108,7 @@
                    Creator = App.User.UserName
                });
                // 8. è¿”回结果
                // 7. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("托盘进站成功");
@@ -182,17 +175,11 @@
                    return response.Error($"当前库存状态不允许出站操作,当前状态:{stockInfo.StockStatus}");
                }
                // 4. èŽ·å–ç³»ç»Ÿé…ç½®
                var configs = _sysDictionaryService.GetVueDictionary(new[] { "MES_EquipmentCode", "MES_ResourceCode" });
                string equipmentCode = GetConfigValue(configs, "MES_EquipmentCode", "WCS_001");
                string resourceCode = GetConfigValue(configs, "MES_ResourceCode", "RESOURCE_001");
                // 5. æž„造MES请求
                // 4. æž„造MES请求
                var mesRequest = new OutboundInContainerRequest
                {
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    LocalTime = DateTime.Now,
                    ContainerCode = dto.PalletCode,
                    ParamList = dto.ParamList?.Select(p => new ParamItem
@@ -205,11 +192,11 @@
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                // 6. è°ƒç”¨MES接口(同步方法)
                // 5. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.OutboundInContainer(mesRequest);
                stopwatch.Stop();
                // 7. è®°å½•日志
                // 6. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "OutboundInContainer",
@@ -221,7 +208,7 @@
                    Creator = App.User.UserName
                });
                // 8. è¿”回结果
                // 7. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("托盘出站成功");
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs
@@ -5,7 +5,6 @@
using WIDESEA_DTO.MES;
using WIDESEA_IStockService;
using WIDESEA_IBasicService;
using WIDESEA_IMesService;
using WIDESEA_ISystemService;
using WIDESEA_Model.Models;
using System.Diagnostics;
@@ -22,16 +21,19 @@
        private readonly IMesLogService _mesLogService;
        private readonly IMesService _mesService;
        private readonly ISys_DictionaryService _sysDictionaryService;
        private readonly IStockInfoService _stockInfoService;
        public StockInfoDetailController(
            IStockInfoDetailService service,
            IMesLogService mesLogService,
            IMesService mesService,
            ISys_DictionaryService sysDictionaryService) : base(service)
            ISys_DictionaryService sysDictionaryService,
            IStockInfoService stockInfoService) : base(service)
        {
            _mesLogService = mesLogService;
            _mesService = mesService;
            _sysDictionaryService = sysDictionaryService;
            _stockInfoService = stockInfoService;
        }
        /// <summary>
@@ -48,36 +50,26 @@
            try
            {
                // 1. å‚数验证
                if (string.IsNullOrWhiteSpace(dto.PalletCode))
                {
                    return response.Error("托盘编号不能为空");
                }
                if (dto.SfcList == null || !dto.SfcList.Any())
                {
                    return response.Error("电芯码列表不能为空");
                }
                // 2. éªŒè¯ç”µèŠ¯çŠ¶æ€ï¼ˆéž'已锁定'状态允许绑定)
                var stockDetails = await Service.Repository.QueryDataAsync(x => dto.SfcList.Contains(x.SerialNumber));
                if (stockDetails != null && stockDetails.Any(d => d.Status == 99))
                var stockDetail = await Service.Repository.QueryFirstAsync(x => dto.SfcList.Contains(x.SerialNumber));
                if (stockDetail != null && stockDetail.Status == 99)
                {
                    return response.Error("当前库存明细包含已锁定状态,不允许执行绑定操作");
                }
                // 3. èŽ·å–ç³»ç»Ÿé…ç½®
                var configs = _sysDictionaryService.GetVueDictionary(new[] { "MES_EquipmentCode", "MES_ResourceCode" });
                string equipmentCode = GetConfigValue(configs, "MES_EquipmentCode", "WCS_001");
                string resourceCode = GetConfigValue(configs, "MES_ResourceCode", "RESOURCE_001");
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => stockDetail.StockId == x.Id);
                // 3. æž„造MES请求 - å°†ç”µèŠ¯åˆ—è¡¨è½¬æ¢ä¸ºContainerSfcItem格式
                var mesRequest = new BindContainerRequest
                {
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    LocalTime = DateTime.Now,
                    ContainerCode = dto.PalletCode,
                    ContainerCode = stockInfo.PalletCode,
                    ContainerSfcList = dto.SfcList.Select(sfc => new ContainerSfcItem
                    {
                        Sfc = sfc,
@@ -146,36 +138,26 @@
            try
            {
                // 1. å‚数验证
                if (string.IsNullOrWhiteSpace(dto.PalletCode))
                {
                    return response.Error("托盘编号不能为空");
                }
                if (dto.SfcList == null || !dto.SfcList.Any())
                {
                    return response.Error("电芯码列表不能为空");
                }
                // 2. éªŒè¯ç”µèŠ¯çŠ¶æ€ï¼ˆéž'已锁定'状态允许解绑)
                var stockDetails = await Service.Repository.QueryDataAsync(x => dto.SfcList.Contains(x.SerialNumber));
                if (stockDetails != null && stockDetails.Any(d => d.Status == 99))
                var stockDetail = await Service.Repository.QueryFirstAsync(x => dto.SfcList.Contains(x.SerialNumber));
                if (stockDetail != null && stockDetail.Status == 99)
                {
                    return response.Error("当前库存明细包含已锁定状态,不允许执行解绑操作");
                }
                // 3. èŽ·å–ç³»ç»Ÿé…ç½®
                var configs = _sysDictionaryService.GetVueDictionary(new[] { "MES_EquipmentCode", "MES_ResourceCode" });
                string equipmentCode = GetConfigValue(configs, "MES_EquipmentCode", "WCS_001");
                string resourceCode = GetConfigValue(configs, "MES_ResourceCode", "RESOURCE_001");
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => stockDetail.StockId == x.Id);
                // 3. æž„造MES请求
                var mesRequest = new UnBindContainerRequest
                {
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    LocalTime = DateTime.Now,
                    ContainCode = dto.PalletCode,
                    ContainCode = stockInfo.PalletCode,
                    SfcList = dto.SfcList
                };
@@ -251,25 +233,20 @@
                // 2. éªŒè¯ç”µèŠ¯çŠ¶æ€ï¼ˆéž'已锁定'状态允许NG上报)
                var sfcList = dto.NgSfcList.Select(x => x.Sfc).ToList();
                var stockDetails = await Service.Repository.QueryDataAsync(x => sfcList.Contains(x.SerialNumber));
                if (stockDetails != null && stockDetails.Any(d => d.Status == 99))
                var stockDetail = await Service.Repository.QueryFirstAsync(x => sfcList.Contains(x.SerialNumber));
                if (stockDetail != null && stockDetail.Status == 99)
                {
                    return response.Error("当前库存明细包含已锁定状态,不允许执行NG上报操作");
                }
                // 3. èŽ·å–ç³»ç»Ÿé…ç½®
                var configs = _sysDictionaryService.GetVueDictionary(new[] { "MES_EquipmentCode", "MES_ResourceCode" });
                string equipmentCode = GetConfigValue(configs, "MES_EquipmentCode", "WCS_001");
                string resourceCode = GetConfigValue(configs, "MES_ResourceCode", "RESOURCE_001");
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => stockDetail.StockId == x.Id);
                // 3. æž„造MES请求 - å°†DTO格式转换为MES请求格式
                var mesRequest = new ContainerNgReportRequest
                {
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "RESOURCE-001",
                    LocalTime = DateTime.Now,
                    ContainerCode = dto.PalletCode,
                    ContainerCode = stockInfo.PalletCode,
                    NgSfcList = dto.NgSfcList.Select(ng => new NgSfcItem
                    {
                        Sfc = ng.Sfc,
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/WIDESEA_WMSServer.csproj
@@ -59,7 +59,6 @@
      <ProjectReference Include="..\WIDESEA_BasicService\WIDESEA_BasicService.csproj" />
      <ProjectReference Include="..\WIDESEA_CheckService\WIDESEA_CheckService.csproj" />
      <ProjectReference Include="..\WIDESEA_InboundService\WIDESEA_InboundService.csproj" />
      <ProjectReference Include="..\WIDESEA_MesService\WIDESEA_MesService.csproj" />
      <ProjectReference Include="..\WIDESEA_OutboundService\WIDESEA_OutboundService.csproj" />
      <ProjectReference Include="..\WIDESEA_RecordService\WIDESEA_RecordService.csproj" />
      <ProjectReference Include="..\WIDESEA_StockService\WIDESEA_StockService.csproj" />
Code/docs/superpowers/plans/2026-04-13-mes-api-log-page-plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1430 @@
# 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
```csharp
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
```csharp
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
```csharp
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
```csharp
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 æ–‡ä»¶
```bash
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: è¯»å–现有接口
```bash
cat WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs
```
### Step 2: æ·»åŠ æ–°æ–¹æ³•åˆ°æŽ¥å£
在现有接口中添加以下方法:
```csharp
/// <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: æäº¤æŽ¥å£æ‰©å±•
```bash
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: è¯»å–现有服务实现
```bash
cat WMS/WIDESEA_WMSServer/WIDESEA_MesService/MesLogService.cs
```
### Step 2: æ·»åŠ  GetPageAsync å®žçް
```csharp
/// <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 å®žçް
```csharp
/// <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 å®žçް
```csharp
/// <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 å®žçް
```csharp
/// <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: æäº¤æœåŠ¡å®žçŽ°
```bash
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: åˆ›å»ºæŽ§åˆ¶å™¨æ–‡ä»¶
```csharp
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: æäº¤æŽ§åˆ¶å™¨
```bash
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: åˆ›å»ºç»„件文件
```vue
<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: æäº¤ç»„ä»¶
```bash
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: åˆ›å»ºæ‰©å±•文件
```jsx
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: æäº¤æ‰©å±•文件
```bash
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: åˆ›å»ºé¡µé¢æ–‡ä»¶
```vue
<!--
*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: æäº¤é¡µé¢æ–‡ä»¶
```bash
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: è¯»å–路由文件
```bash
cat WMS/WIDESEA_WMSClient/src/router/viewGird.js | head -50
```
### Step 2: åœ¨ Sys_Log è·¯ç”±åŽæ·»åŠ  Mes_Log è·¯ç”±
在 `viewGird.js` çš„路由数组中,找到 `path: '/Sys_Log'` é…ç½®å—(大约在文件开头),在其后添加:
```javascript
  {
    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: æäº¤è·¯ç”±é…ç½®
```bash
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
-- åœ¨ 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: æ·»åŠ èœå•è®°å½•
```sql
-- å…ˆæŸ¥è¯¢ç³»ç»Ÿç®¡ç†èœå•çš„ 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: æ·»åŠ æ•°æ®å­—å…¸è®°å½•
```sql
-- æŽ¥å£ç±»åž‹å­—å…¸
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: ä¿å­˜æ•°æ®åº“脚本
```bash
# å°†ä¸Šè¿° SQL è„šæœ¬ä¿å­˜åˆ°æ–‡ä»¶
cat > WMS/WIDESEA_WMSServer/Database/Scripts/20260413_MesLogPage_Config.sql << 'EOF'
-- è¿™é‡Œç²˜è´´ä¸Šé¢çš„ SQL è„šæœ¬
EOF
```
### Step 5: æäº¤æ•°æ®åº“脚本
```bash
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: ç¼–译后端项目
```bash
cd WMS/WIDESEA_WMSServer
dotnet build WIDESEA_WMSServer.sln
```
### Step 2: æ£€æŸ¥ç¼–译结果
预期输出:编译成功,无错误
### Step 3: å¦‚果有错误,修复并重新编译
---
## Task 12: å‰ç«¯ç¼–译测试
### Step 1: å®‰è£…依赖(如需要)
```bash
cd WMS/WIDESEA_WMSClient
npm install
```
### Step 2: ç¼–译前端项目
```bash
npm run build
```
### Step 3: æ£€æŸ¥ç¼–译结果
预期输出:编译成功,无错误
---
## Task 13: æ‰‹åŠ¨åŠŸèƒ½æµ‹è¯•
### æµ‹è¯•清单
- [ ] **分页查询**: èƒ½æ­£å¸¸æ˜¾ç¤ºæ—¥å¿—列表
- [ ] **筛选功能**: æŽ¥å£ç±»åž‹ã€çŠ¶æ€ã€æ—¶é—´èŒƒå›´ç­›é€‰æ­£å¸¸
- [ ] **统计卡片**: æ˜¾ç¤ºæ­£ç¡®çš„统计数据
- [ ] **JSON è¯¦æƒ…**: ç‚¹å‡»è¯·æ±‚/响应列能查看完整 JSON
- [ ] **导出功能**: èƒ½å¯¼å‡º CSV æ–‡ä»¶
---
## Task 14: ä»£ç å®¡æŸ¥ä¸Žæœ€ç»ˆæäº¤
### Step 1: æŸ¥çœ‹æ‰€æœ‰å˜æ›´
```bash
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`
Code/docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,896 @@
# MES æŽ¥å£è°ƒç”¨æ—¥å¿—页面设计文档
**日期:** 2026-04-13
**作者:** Claude
**版本:** v0.3
**状态:** å·²æ‰¹å‡†
---
## 1. æ¦‚è¿°
### 1.1 ç›®æ ‡
在 WMS ç³»ç»Ÿä¸­æ·»åŠ  MES æŽ¥å£è°ƒç”¨æ—¥å¿—查看页面,提供综合性的日志查询、统计和管理功能。
### 1.2 èŒƒå›´
- åŽç«¯ï¼šAPI æŽ¥å£ã€æœåŠ¡å±‚æ‰©å±•
- å‰ç«¯ï¼šæ—¥å¿—列表页面、统计卡片、JSON è¯¦æƒ…查看器
- æ•°æ®åº“:菜单配置、数据字典
---
## 2. åŽç«¯è®¾è®¡
### 2.1 Controller æŽ¥å£
**文件路径:** `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Mes/MesLogController.cs`
**注意:** æ­¤æŽ§åˆ¶å™¨ä¸ç»§æ‰¿ `ApiBaseController`,因为 MES æ—¥å¿—是只读记录,不需要完整的 CRUD æ“ä½œã€‚仅提供查询、统计和导出功能。
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/api/MesLog/page` | åˆ†é¡µæŸ¥è¯¢ï¼Œæ”¯æŒæ‰€æœ‰ç­›é€‰æ¡ä»¶ |
| GET | `/api/MesLog/{id}` | èŽ·å–å•æ¡æ—¥å¿—å®Œæ•´ä¿¡æ¯ |
| GET | `/api/MesLog/statistics` | èŽ·å–ç»Ÿè®¡æ•°æ® |
| POST | `/api/MesLog/export` | å¯¼å‡ºæŸ¥è¯¢ç»“果为 Excel (使用框架内置导出) |
### 2.2 æ•°æ®ä¼ è¾“对象
#### 2.2.1 æŸ¥è¯¢è¯·æ±‚ DTO
```csharp
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; }
    }
}
```
#### 2.2.2 ç»Ÿè®¡æ•°æ® DTO
```csharp
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; }
    }
}
```
#### 2.2.3 åˆ†é¡µå“åº” DTO
```csharp
namespace WIDESEA_DTO.MES
{
    /// <summary>
    /// MES日志列表项DTO
    /// </summary>
    public class MesLogListItemDto
    {
        public long Id { get; set; }
        public string ApiType { get; set; }
        public bool IsSuccess { get; set; }
        public string RequestJsonPreview { get; set; }  // å‰200字符预览
        public string ResponseJsonPreview { get; set; } // å‰200字符预览
        public string ErrorMessage { get; set; }
        public int ElapsedMs { get; set; }
        public DateTime CreateDate { get; set; }
        public string Creator { get; set; }
    }
    /// <summary>
    /// MES日志详情DTO
    /// </summary>
    public class MesLogDetailDto : MesLogListItemDto
    {
        public string RequestJson { get; set; }   // å®Œæ•´JSON
        public string ResponseJson { get; set; }  // å®Œæ•´JSON
        public DateTime? ModifyDate { get; set; }
        public string Modifier { get; set; }
    }
}
```
### 2.3 Service æŽ¥å£æ‰©å±•
**文件路径:** `WMS/WIDESEA_WMSServer/WIDESEA_IMesService/IMesLogService.cs`
```csharp
namespace WIDESEA_IMesService
{
    public interface IMesLogService : IDependency
    {
        // çŽ°æœ‰æ–¹æ³•
        Task<bool> LogAsync(MesApiLogDto log);
        Task<List<MesApiLogDto>> GetRecentLogsAsync(string apiType, int count = 50);
        // æ–°å¢žæ–¹æ³•
        /// <summary>
        /// åˆ†é¡µæŸ¥è¯¢MES日志
        /// </summary>
        Task<(List<MesLogListItemDto> items, int total)> GetPageAsync(MesLogQueryDto query, int page, int pageSize);
        /// <summary>
        /// èŽ·å–å•æ¡æ—¥å¿—è¯¦æƒ…
        /// </summary>
        Task<MesLogDetailDto> GetDetailAsync(long id);
        /// <summary>
        /// èŽ·å–ç»Ÿè®¡æ•°æ®
        /// </summary>
        Task<MesLogStatisticsDto> GetStatisticsAsync(MesLogQueryDto query);
        /// <summary>
        /// å¯¼å‡ºæ—¥å¿—数据
        /// </summary>
        Task<byte[]> ExportAsync(MesLogQueryDto query);
    }
}
```
### 2.4 Controller å®žçŽ°æ¡†æž¶
```csharp
namespace WIDESEA_WMSServer.Controllers.Mes
{
    [Route("api/MesLog")]
    [ApiController]
    public class MesLogController : ControllerBase
    {
        private readonly IMesLogService _mesLogService;
        public MesLogController(IMesLogService mesLogService)
        {
            _mesLogService = mesLogService;
        }
        /// <summary>
        /// åˆ†é¡µæŸ¥è¯¢MES日志
        /// </summary>
        [HttpPost("page")]
        public async Task<WebResponseContent> GetPage([FromBody] MesLogQueryDto query, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
        {
            // å®žçŽ°åˆ†é¡µæŸ¥è¯¢
        }
        /// <summary>
        /// èŽ·å–æ—¥å¿—è¯¦æƒ…
        /// </summary>
        [HttpGet("{id}")]
        public async Task<WebResponseContent> GetDetail(long id)
        {
            // å®žçŽ°è¯¦æƒ…æŸ¥è¯¢
        }
        /// <summary>
        /// èŽ·å–ç»Ÿè®¡æ•°æ®
        /// </summary>
        [HttpGet("statistics")]
        public async Task<WebResponseContent> GetStatistics([FromQuery] MesLogQueryDto query)
        {
            // å®žçŽ°ç»Ÿè®¡æŸ¥è¯¢
        }
        /// <summary>
        /// å¯¼å‡ºæ—¥å¿— - ä½¿ç”¨æ¡†æž¶å†…ç½® ServiceBase.Export() æ–¹æ³•
        /// é€šè¿‡ HttpHelper.Post() è°ƒç”¨æ¡†æž¶çš„通用导出接口
        /// </summary>
        [HttpPost("export")]
        public async Task<IActionResult> Export([FromBody] MesLogQueryDto query)
        {
            // è°ƒç”¨ _mesLogService.ExportAsync() ç”Ÿæˆæ•°æ®
            // ä½¿ç”¨æ¡†æž¶å†…置的 ExcelExporter å¯¼å‡º
        }
    }
}
```
### 2.5 æœåŠ¡æ³¨å†Œ
**注意:** `IMesLogService` å·²å®žçް `IDependency` æŽ¥å£ï¼Œæ— éœ€æ‰‹åŠ¨æ³¨å†Œã€‚æ¡†æž¶ä¼šé€šè¿‡ `AutofacModuleRegister` è‡ªåŠ¨æ³¨å†Œæ‰€æœ‰ `IDependency` å®žçŽ°ã€‚
---
## 3. å‰ç«¯è®¾è®¡
### 3.1 æ–‡ä»¶ç»“æž„
```
src/
├── views/
│   â””── system/
│       â””── Mes_Log.vue              # ä¸»é¡µé¢
├── extension/
│   â””── system/
│       â””── Mes_Log.jsx              # ä¸šåŠ¡æ‰©å±•é€»è¾‘
├── components/
│   â””── MesJsonViewer.vue            # JSON è¯¦æƒ…查看器组件
└── router/
    â””── viewGird.js                  # æ·»åŠ è·¯ç”±é…ç½®
```
### 3.2 Mes_Log.vue é…ç½®
**注意:** `view-grid` ç»„件不支持 `toolbar` æ’槽,统计卡片需要放在 `view-grid` å¤–部。
```vue
<template>
  <div class="mes-log-page">
    <!-- ç»Ÿè®¡å¡ç‰‡åŒºåŸŸï¼ˆä½äºŽ view-grid ä¸Šæ–¹ï¼‰ -->
    <mes-log-statistics ref="statistics" @refresh="onStatsRefresh" />
    <!-- æ—¥å¿—列表 -->
    <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 { ref, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const table = ref({
      key: "Id",
      cnName: "MES接口日志",
      name: "Mes_Log",
      url: "/api/MesLog/",
      sortName: "Id DESC",
    });
    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,
        formatter: (row) => previewJson(row.requestJson)
      },
      {
        field: "responseJson",
        title: "响应内容",
        width: 200,
        link: true,
        formatter: (row) => previewJson(row.responseJson)
      },
      { field: "errorMessage", title: "错误信息", width: 200 },
      { field: "elapsedMs", title: "耗时(ms)", width: 100, sortable: true },
      { field: "createDate", title: "调用时间", width: 160, sortable: true },
      { field: "creator", title: "操作人", width: 100 }
    ]);
    // JSON å†…容预览辅助函数(在 Mes_Log.jsx ä¸­å®žçŽ°ï¼‰
    const 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) + '...';
      }
    };
    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, searchFormOptions, extend, /* ... */ };
  }
});
</script>
```
### 3.3 MesJsonViewer.vue ç»„ä»¶
**注意:** ä¸ä½¿ç”¨å¤–部 JSON æŸ¥çœ‹å™¨åº“,采用原生 `<pre>` æ ‡ç­¾ + `JSON.stringify()` æ ¼å¼åŒ–显示。
```vue
<template>
  <el-dialog
    v-model="visible"
    :title="title"
    width="800px"
    :close-on-click-modal="false"
  >
    <el-tabs v-model="activeTab">
      <el-tab-pane label="请求" name="request">
        <div class="json-container">
          <el-button
            size="small"
            class="copy-btn"
            @click="copyToClipboard(formattedRequest)"
          >
            å¤åˆ¶
          </el-button>
          <pre class="json-content">{{ formattedRequest }}</pre>
        </div>
      </el-tab-pane>
      <el-tab-pane label="响应" name="response">
        <div class="json-container">
          <el-button
            size="small"
            class="copy-btn"
            @click="copyToClipboard(formattedResponse)"
          >
            å¤åˆ¶
          </el-button>
          <pre class="json-content">{{ formattedResponse }}</pre>
        </div>
      </el-tab-pane>
    </el-tabs>
    <template #footer>
      <el-button @click="visible = false">关闭</el-button>
    </template>
  </el-dialog>
</template>
<script>
import { computed, ref } from 'vue';
export default {
  name: "MesJsonViewer",
  props: {
    modelValue: Boolean,
    title: String,
    requestJson: [String, Object],
    responseJson: [String, Object]
  },
  emits: ["update:modelValue"],
  setup(props) {
    const activeTab = ref('request');
    const formattedRequest = computed(() => {
      try {
        const obj = typeof props.requestJson === 'string'
          ? JSON.parse(props.requestJson)
          : props.requestJson;
        return JSON.stringify(obj, null, 2);
      } catch {
        return props.requestJson || '{}';
      }
    });
    const formattedResponse = computed(() => {
      try {
        const obj = typeof props.responseJson === 'string'
          ? JSON.parse(props.responseJson)
          : props.responseJson;
        return JSON.stringify(obj, null, 2);
      } catch {
        return props.responseJson || '{}';
      }
    });
    const copyToClipboard = (text) => {
      // ä¼˜å…ˆä½¿ç”¨çް代 Clipboard API(需要 HTTPS æˆ– localhost)
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).catch(() => {
          // å¦‚果失败,使用降级方案
          fallbackCopy(text);
        });
      } else {
        // é™çº§æ–¹æ¡ˆï¼šå…¼å®¹ HTTP çŽ¯å¢ƒå’Œæ—§æµè§ˆå™¨
        fallbackCopy(text);
      }
    };
    const fallbackCopy = (text) => {
      const textarea = document.createElement('textarea');
      textarea.value = text;
      textarea.style.position = 'fixed';
      textarea.style.opacity = '0';
      document.body.appendChild(textarea);
      textarea.select();
      try {
        document.execCommand('copy');
      } catch (err) {
        console.error('复制失败:', err);
      }
      document.body.removeChild(textarea);
    };
    return {
      visible: props.modelValue,
      activeTab,
      formattedRequest,
      formattedResponse,
      copyToClipboard
    };
  }
};
</script>
<style scoped>
.json-container {
  position: relative;
}
.copy-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 10;
}
.json-content {
  background: #f5f7fa;
  padding: 16px;
  border-radius: 4px;
  max-height: 500px;
  overflow: auto;
  font-size: 12px;
  line-height: 1.5;
}
</style>
```
### 3.4 ç»Ÿè®¡å¡ç‰‡ç»„ä»¶
**文件路径:** `src/components/MesLogStatistics.vue`
**注意:** å½“前项目 Element Plus ç‰ˆæœ¬ä¸º 2.2.14,不支持 `el-statistic` ç»„件(需要 2.3+)。采用自定义卡片实现。
```vue
<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 axios from '@/api/axios';
export default {
  name: "MesLogStatistics",
  setup() {
    const statistics = ref({
      totalCount: 0,
      successCount: 0,
      failedCount: 0,
      successRate: 0,
      avgElapsedMs: 0,
      todayCount: 0
    });
    const fetchStatistics = async () => {
      const res = await axios.post('/api/MesLog/statistics', {});
      if (res.status) {
        statistics.value = res.data;
      }
    };
    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>
```
### 3.5 è‡ªåŠ¨åˆ·æ–°åŠŸèƒ½
**UX è¯´æ˜Ž:**
- åˆ·æ–°æŽ§åˆ¶ä½äºŽé¡µé¢å³ä¸Šè§’,与查询条件并列
- å½“用户打开筛选面板、编辑查询条件或查看详情时,自动刷新暂停
- ç”¨æˆ·æ‰‹åŠ¨ç‚¹å‡»æŸ¥è¯¢/刷新按钮后,自动刷新重新计时
```javascript
// åœ¨ Mes_Log.jsx ä¸­å®žçް
const refreshOptions = [
  { label: "关闭", value: 0 },
  { label: "10秒", value: 10 },
  { label: "30秒", value: 30 },
  { label: "1分钟", value: 60 },
  { label: "5分钟", value: 300 }
];
let refreshTimer = null;
const setAutoRefresh = (interval) => {
  if (refreshTimer) {
    clearInterval(refreshTimer);
    refreshTimer = null;
  }
  if (interval > 0) {
    refreshTimer = setInterval(() => {
      // ä»…在用户未交互时刷新列表数据
      if (!isUserInteracting) {
        refresh();
      }
    }, interval * 1000);
  }
};
// ç”¨æˆ·äº¤äº’时暂停刷新
const onUserInteractionStart = () => {
  isUserInteracting = true;
};
const onUserInteractionEnd = () => {
  isUserInteracting = false;
};
```
---
## 4. æ•°æ®å­—典配置
### 4.1 æŽ¥å£ç±»åž‹å­—å…¸ (mesApiType)
| å€¼ | æ˜¾ç¤ºæ–‡æœ¬ | è¯´æ˜Ž |
|---|---------|------|
| BindContainer | ç”µèŠ¯ç»‘å®š | æ‰˜ç›˜ç”µèŠ¯ç»‘å®šæ“ä½œ |
| UnBindContainer | ç”µèŠ¯è§£ç»‘ | æ‰˜ç›˜ç”µèŠ¯è§£ç»‘æ“ä½œ |
| ContainerNgReport | NG上报 | æ‰˜ç›˜NG电芯上报 |
| InboundInContainer | æ‰˜ç›˜è¿›ç«™ | æ‰˜ç›˜è¿›ç«™æ“ä½œ |
| OutboundInContainer | æ‰˜ç›˜å‡ºç«™ | æ‰˜ç›˜å‡ºç«™æ“ä½œ |
### 4.2 è°ƒç”¨çŠ¶æ€å­—å…¸ (mesApiStatus)
| å€¼ | æ˜¾ç¤ºæ–‡æœ¬ | é¢œè‰² |
|---|---------|------|
| true | æˆåŠŸ | ç»¿è‰² |
| false | å¤±è´¥ | çº¢è‰² |
---
## 5. æƒé™ä¸Žèœå•
### 5.1 èœå•配置
在 `Dt_Menu` è¡¨ä¸­æ·»åŠ ï¼š
| å­—段 | å€¼ |
|------|-----|
| ParentId | (系统管理菜单的 ID) |
| MenuName | MES接口日志 |
| Url | /Mes_Log |
| Component | views/system/Mes_Log |
| Permission | Mes_Log:view |
| Sort | (排在 Sys_Log ä¹‹åŽ) |
| Icon | el-icon-document |
### 5.2 æƒé™ç‚¹
- `Mes_Log:view` - æŸ¥çœ‹æ—¥å¿—列表
- `Mes_Log:detail` - æŸ¥çœ‹æ—¥å¿—详情
- `Mes_Log:export` - å¯¼å‡ºæ—¥å¿—
---
## 6. å®žçŽ°æ­¥éª¤
### 6.1 åŽç«¯å®žçް
1. æ‰©å±• `IMesLogService` æŽ¥å£ï¼ˆæ·»åŠ åˆ†é¡µã€ç»Ÿè®¡ã€å¯¼å‡ºæ–¹æ³•ï¼‰
2. å®žçް `MesLogService` çš„æ‰©å±•方法
3. åˆ›å»º DTO æ–‡ä»¶ï¼ˆ`MesLogQueryDto.cs`, `MesLogStatisticsDto.cs`, `MesLogListItemDto.cs`)
4. åˆ›å»º `MesLogController.cs`
5. **注意:** æœåŠ¡æ³¨å†Œè‡ªåŠ¨å®Œæˆï¼ˆ`IMesLogService` å·²å®žçް `IDependency`)
### 6.2 å‰ç«¯å®žçް
1. åˆ›å»º `MesJsonViewer.vue` ç»„件(使用原生 `<pre>` æ ‡ç­¾ï¼Œæ— å¤–部依赖)
2. åˆ›å»º `MesLogStatistics.vue` ç»„件(使用 `el-card` è‡ªå®šä¹‰å®žçŽ°ï¼‰
3. åˆ›å»º `Mes_Log.jsx` æ‰©å±•逻辑
4. åˆ›å»º `Mes_Log.vue` é¡µé¢
5. åœ¨ `viewGird.js` æ·»åŠ è·¯ç”±
6. åœ¨ `extension` ç›®å½•添加扩展文件
### 6.3 æ•°æ®åº“配置
1. ç¡®è®¤ `Dt_MesApiLog` è¡¨å·²å­˜åœ¨ï¼ˆå‚见 `Database/Scripts/20260412_MesApiLog.sql`)
2. åˆ›å»ºæ•°æ®åº“索引(参见 6.4 èŠ‚ï¼‰
3. æ’入菜单记录到 `Dt_Menu` è¡¨
4. æ’入数据字典记录到 `Dt_Dictionary` è¡¨
5. åˆ†é…æƒé™ç»™è§’色
### 6.4 æ•°æ®åº“索引
执行以下 SQL åˆ›å»ºç´¢å¼•以优化查询性能:
```sql
-- æŽ¥å£ç±»åž‹ç´¢å¼•(用于按类型筛选)
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
```
---
## 7. æŠ€æœ¯è¦ç‚¹
### 7.1 JSON å†…容搜索
由于 SQL Server å¯¹ JSON å­—段支持有限,采用 `LIKE` æœç´¢ï¼š
- é€‚用于关键字搜索
- æ³¨æ„æ€§èƒ½å½±å“ï¼Œå»ºè®®é…åˆæ—¶é—´èŒƒå›´ç­›é€‰
- æœç´¢å­—段:`RequestJson`、`ResponseJson`、`ErrorMessage`
### 7.2 åˆ†é¡µæ€§èƒ½
- åˆ©ç”¨ç´¢å¼•(参见 6.4 èŠ‚æ•°æ®åº“ç´¢å¼•ï¼‰
- å¤§æ•°æ®é‡æ—¶å»ºè®®å¢žåŠ æ—¶é—´èŒƒå›´é™åˆ¶ï¼ˆé»˜è®¤æ˜¾ç¤ºæœ€è¿‘ 7 å¤©ï¼‰
- è€ƒè™‘添加 `TOP 1000` é™åˆ¶é˜²æ­¢å…¨è¡¨æ‰«æ
### 7.3 å¯¼å‡ºåŠŸèƒ½
- ä½¿ç”¨æ¡†æž¶å†…置的 `ServiceBase.Export()` æ–¹æ³•
- é€šè¿‡ `Magicodes.ExporterAndImporter.Excel` åº“实现
- å¯¼å‡ºæ–‡ä»¶å‘½åï¼š`MES接口日志_YYYYMMDD_HHMMSS.xlsx`
- æ”¯æŒä¸Žå½“前查询条件一致的导出
### 7.4 å‰ç«¯å­—段映射
| å‰ç«¯å­—段 | åŽç«¯ DTO å­—段 | è¯´æ˜Ž |
|---------|--------------|------|
| dateRange[0] | StartTime | å¼€å§‹æ—¶é—´ |
| dateRange[1] | EndTime | ç»“束时间 |
| elapsedRange[0] | MinElapsedMs | æœ€å°è€—æ—¶ |
| elapsedRange[1] | MaxElapsedMs | æœ€å¤§è€—æ—¶ |
| jsonKeyword | JsonRequestKeyword, JsonResponseKeyword | åŒæ—¶æœç´¢è¯·æ±‚和响应 |
---
## 8. æµ‹è¯•要点
1. **分页查询** - éªŒè¯å„种筛选条件的组合
2. **统计准确性** - éªŒè¯ç»Ÿè®¡æ•°æ®ä¸Žå®žé™…数据一致
3. **JSON展示** - éªŒè¯æ ¼å¼åŒ–、折叠、复制功能
4. **自动刷新** - éªŒè¯å®šæ—¶åˆ·æ–°åŠŸèƒ½æ­£å¸¸å·¥ä½œ
5. **导出功能** - éªŒè¯ Excel æ–‡ä»¶å†…容正确
6. **权限控制** - éªŒè¯æ— æƒé™ç”¨æˆ·æ— æ³•访问
---
## 9. åŽç»­ä¼˜åŒ–建议
1. **日志归档** - è€ƒè™‘定期归档旧日志,保持主表性能
2. **实时监控** - é›†æˆ SignalR å®žçŽ°å®žæ—¶æ—¥å¿—æŽ¨é€
3. **异常告警** - å¤±è´¥çŽ‡è¶…è¿‡é˜ˆå€¼æ—¶å‘é€å‘Šè­¦
4. **性能分析** - æ·»åŠ è€—æ—¶è¶‹åŠ¿å›¾è¡¨
5. **接口对比** - æ”¯æŒåŒç±»æŽ¥å£è°ƒç”¨çš„对比分析