From 5e851678cc02257bbbd179446de36082430ca5bc Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期一, 13 四月 2026 15:12:04 +0800
Subject: [PATCH] feat(MES): 添加Mes_Log扩展逻辑
---
Code/docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md | 896 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 896 insertions(+), 0 deletions(-)
diff --git a/Code/docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md b/Code/docs/superpowers/specs/2026-04-13-mes-api-log-page-design.md
new file mode 100644
index 0000000..81592ec
--- /dev/null
+++ b/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 鑼冨洿
+
+- 鍚庣锛欰PI 鎺ュ彛銆佹湇鍔″眰鎵╁睍
+- 鍓嶇锛氭棩蹇楀垪琛ㄩ〉闈€�佺粺璁″崱鐗囥�丣SON 璇︽儏鏌ョ湅鍣�
+- 鏁版嵁搴擄細鑿滃崟閰嶇疆銆佹暟鎹瓧鍏�
+
+---
+
+## 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 瀛楁鏄犲皠锛歞ateRange[0] 鈫� StartTime锛�
+ /// </summary>
+ public DateTime? StartTime { get; set; }
+
+ /// <summary>
+ /// 缁撴潫鏃堕棿锛堝墠绔� dateRange 瀛楁鏄犲皠锛歞ateRange[1] 鈫� EndTime锛�
+ /// </summary>
+ public DateTime? EndTime { get; set; }
+
+ /// <summary>
+ /// 鍒涘缓浜猴紙鎿嶄綔浜猴級
+ /// </summary>
+ public string Creator { get; set; }
+
+ /// <summary>
+ /// 鏈�灏忚�楁椂锛堟绉掞級锛堝墠绔� elapsedRange 瀛楁鏄犲皠锛歟lapsedRange[0] 鈫� MinElapsedMs锛�
+ /// </summary>
+ public int? MinElapsedMs { get; set; }
+
+ /// <summary>
+ /// 鏈�澶ц�楁椂锛堟绉掞級锛堝墠绔� elapsedRange 瀛楁鏄犲皠锛歟lapsedRange[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>
+ /// 鍚勬帴鍙g被鍨嬭皟鐢ㄦ鏁扮粺璁�
+ /// </summary>
+ public Dictionary<string, int> ApiTypeCounts { get; set; }
+ }
+}
+```
+
+#### 2.2.3 鍒嗛〉鍝嶅簲 DTO
+
+```csharp
+namespace WIDESEA_DTO.MES
+{
+ /// <summary>
+ /// MES鏃ュ織鍒楄〃椤笵TO
+ /// </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 | 鐢佃姱瑙g粦 | 鎵樼洏鐢佃姱瑙g粦鎿嶄綔 |
+| 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. **鑷姩鍒锋柊** - 楠岃瘉瀹氭椂鍒锋柊鍔熻兘姝e父宸ヤ綔
+5. **瀵煎嚭鍔熻兘** - 楠岃瘉 Excel 鏂囦欢鍐呭姝g‘
+6. **鏉冮檺鎺у埗** - 楠岃瘉鏃犳潈闄愮敤鎴锋棤娉曡闂�
+
+---
+
+## 9. 鍚庣画浼樺寲寤鸿
+
+1. **鏃ュ織褰掓。** - 鑰冭檻瀹氭湡褰掓。鏃ф棩蹇楋紝淇濇寔涓昏〃鎬ц兘
+2. **瀹炴椂鐩戞帶** - 闆嗘垚 SignalR 瀹炵幇瀹炴椂鏃ュ織鎺ㄩ��
+3. **寮傚父鍛婅** - 澶辫触鐜囪秴杩囬槇鍊兼椂鍙戦�佸憡璀�
+4. **鎬ц兘鍒嗘瀽** - 娣诲姞鑰楁椂瓒嬪娍鍥捐〃
+5. **鎺ュ彛瀵规瘮** - 鏀寔鍚岀被鎺ュ彛璋冪敤鐨勫姣斿垎鏋�
--
Gitblit v1.9.3