From 035f2a81a59532ac9f892dab9ade44304847b4fb Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期一, 06 四月 2026 11:11:36 +0800
Subject: [PATCH] 重构输送线选择器;添加表格展开功能

---
 项目资料/设备协议/拘束机对接协议/拘束机对接协议.xlsx                                                                      |    0 
 Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js                                   |   30 
 .omc/project-memory.json                                                                            |   63 --
 Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue                                        |   37 +
 Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue                                            |  628 ++++++++++++++++++++++-------
 Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs |  500 +++++++++++++++++------
 Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue                               |    1 
 7 files changed, 896 insertions(+), 363 deletions(-)

diff --git a/.omc/project-memory.json b/.omc/project-memory.json
index a09d592..199e998 100644
--- a/.omc/project-memory.json
+++ b/.omc/project-memory.json
@@ -1,6 +1,6 @@
 {
   "version": "1.0.0",
-  "lastScanned": 1774488038530,
+  "lastScanned": 1775442733141,
   "projectRoot": "D:\\Git\\ShanMeiXinNengYuan",
   "techStack": {
     "languages": [],
@@ -36,72 +36,17 @@
       "path": "Code",
       "purpose": null,
       "fileCount": 0,
-      "lastAccessed": 1774488038511,
+      "lastAccessed": 1775442733121,
       "keyFiles": []
     },
     "椤圭洰璧勬枡": {
       "path": "椤圭洰璧勬枡",
       "purpose": null,
       "fileCount": 0,
-      "lastAccessed": 1774488038512,
+      "lastAccessed": 1775442733121,
       "keyFiles": []
     }
   },
-  "hotPaths": [
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockSerivce.cs",
-      "accessCount": 14,
-      "lastAccessed": 1774492937086,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\appsettings.json",
-      "accessCount": 5,
-      "lastAccessed": 1774490361799,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\Program.cs",
-      "accessCount": 3,
-      "lastAccessed": 1774488093580,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj",
-      "accessCount": 1,
-      "lastAccessed": 1774488065335,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockViewService.cs",
-      "accessCount": 1,
-      "lastAccessed": 1774492577060,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockInfoService.cs",
-      "accessCount": 1,
-      "lastAccessed": 1774492577097,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockInfoDetailService.cs",
-      "accessCount": 1,
-      "lastAccessed": 1774492577122,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_Core\\BaseServices\\ServiceBase.cs",
-      "accessCount": 1,
-      "lastAccessed": 1774492577619,
-      "type": "file"
-    },
-    {
-      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_IStockService\\IStockInfoService.cs",
-      "accessCount": 1,
-      "lastAccessed": 1774492782997,
-      "type": "file"
-    }
-  ],
+  "hotPaths": [],
   "userDirectives": []
 }
\ No newline at end of file
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
index 062265a..c4ecdf4 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -20,227 +20,447 @@
     public class ConveyorLineTargetAddressSelector
     {
         /// <summary>
-        /// 鎷樻潫鏈哄悕绉板父閲�
+        /// 璁惧灞傜骇锛堜笂灞�/涓嬪眰锛�
         /// </summary>
-        private const string ConstraintMachineName = "鎷樻潫鏈�";
+        /// <remarks>
+        /// 鐢ㄤ簬鍖哄垎鎷樻潫鏈哄拰鎻掓嫈閽夋満鐨勪笂灞傚伐浣嶅拰涓嬪眰宸ヤ綅銆�
+        /// 鍏ュ簱浠诲姟瀵瑰簲涓婂眰锛圡aterialRequestUpper锛夛紝鍑哄簱浠诲姟瀵瑰簲涓嬪眰锛圡aterialRequestLower锛夈��
+        /// </remarks>
+        private enum Layer
+        {
+            /// <summary>
+            /// 涓婂眰宸ヤ綅
+            /// </summary>
+            Upper,
+
+            /// <summary>
+            /// 涓嬪眰宸ヤ綅
+            /// </summary>
+            Lower
+        }
 
         /// <summary>
-        /// 鎻掓嫈閽夋満鍚嶇О甯搁噺
+        /// 鐩爣璁惧绫诲瀷鏋氫妇
         /// </summary>
-        private const string PinMachineName = "鎻掓嫈閽夋満";
+        /// <remarks>
+        /// 鐢ㄤ簬鏍规嵁鐩爣鍦板潃缂栫爜璇嗗埆闇�瑕佸鎺ョ殑璁惧绫诲瀷銆�
+        /// </remarks>
+        private enum TargetDeviceType
+        {
+            /// <summary>
+            /// 鏃犳湁鏁堣澶囷紙鍦板潃涓嶅湪宸茬煡鑼冨洿鍐咃級
+            /// </summary>
+            None,
+
+            /// <summary>
+            /// 鎷樻潫鏈�
+            /// </summary>
+            /// <remarks>
+            /// 璐熻矗鍥哄畾/绾︽潫鐢垫睜鎵樼洏鐨勮澶囷紝鏈変笂涓嬩袱灞傘��
+            /// </remarks>
+            ConstraintMachine,
+
+            /// <summary>
+            /// 鎻掓嫈閽夋満
+            /// </summary>
+            /// <remarks>
+            /// 璐熻矗鎻掗拡鍜屾嫈閽堟搷浣滅殑璁惧锛屾湁涓婁笅涓ゅ眰銆�
+            /// </remarks>
+            PinMachine
+        }
 
         /// <summary>
-        /// 鎷樻潫鏈哄搴旂殑鐐逛綅缂栫爜鍒楄〃
+        /// 鎷樻潫鏈哄搴旂殑鐐逛綅缂栫爜闆嗗悎
         /// </summary>
         /// <remarks>
         /// 褰撶洰鏍囧湴鍧�鍦ㄨ繖浜涚紪鐮佷腑鏃讹紝琛ㄧず闇�瑕佷笌鎷樻潫鏈轰氦浜掋��
+        /// 浣跨敤 HashSet 淇濊瘉 O(1) 鐨� Contains 鏌ユ壘鎬ц兘銆�
         /// </remarks>
-        private static readonly List<string> ConstraintMachineCodes = new List<string> { "10180", "20090" };
+        private static readonly HashSet<string> ConstraintMachineCodes = new HashSet<string> { "10180", "20090" };
 
         /// <summary>
-        /// 鎻掓嫈閽夋満瀵瑰簲鐨勭偣浣嶇紪鐮佸垪琛�
+        /// 鎻掓嫈閽夋満瀵瑰簲鐨勭偣浣嶇紪鐮侀泦鍚�
         /// </summary>
         /// <remarks>
         /// 褰撶洰鏍囧湴鍧�鍦ㄨ繖浜涚紪鐮佷腑鏃讹紝琛ㄧず闇�瑕佷笌鎻掓嫈閽夋満浜や簰銆�
+        /// 浣跨敤 HashSet 淇濊瘉 O(1) 鐨� Contains 鏌ユ壘鎬ц兘銆�
         /// </remarks>
-        private static readonly List<string> PinMachineCodes = new List<string> { "10190", "20100" };
+        private static readonly HashSet<string> PinMachineCodes = new HashSet<string> { "10190", "20100" };
+
+        /// <summary>
+        /// 鐩爣鍦板潃鍒拌澶囩被鍨嬬殑鏄犲皠
+        /// </summary>
+        /// <remarks>
+        /// 閫氳繃鍗曚竴瀛楀吀瀹炵幇 O(1) 鏌ユ壘锛屾浛浠e師鍏堝垎鍒敤涓や釜 List + Contains + if/else if 鐨勫啓娉曘��
+        /// Key: 鐩爣鍦板潃缂栫爜锛孷alue: 瀵瑰簲鐨勮澶囩被鍨嬨��
+        /// </remarks>
+        private static readonly Dictionary<string, TargetDeviceType> AddressToDeviceType = new Dictionary<string, TargetDeviceType>
+        {
+            // 鎷樻潫鏈虹殑涓や釜鐐逛綅缂栫爜閮芥槧灏勫埌 ConstraintMachine 绫诲瀷
+            { "10180", TargetDeviceType.ConstraintMachine },
+            { "20090", TargetDeviceType.ConstraintMachine },
+            // 鎻掓嫈閽夋満鐨勪袱涓偣浣嶇紪鐮侀兘鏄犲皠鍒� PinMachine 绫诲瀷
+            { "10190", TargetDeviceType.PinMachine },
+            { "20100", TargetDeviceType.PinMachine }
+        };
 
         /// <summary>
         /// 鏃ュ織璁板綍鍣�
         /// </summary>
+        /// <remarks>
+        /// 閫氳繃 Microsoft.Extensions.Logging 鎺ュ彛娉ㄥ叆锛岀敤浜庣粨鏋勫寲鏃ュ織杈撳嚭銆�
+        /// </remarks>
         private readonly ILogger _logger;
 
         /// <summary>
         /// 鏋勯�犲嚱鏁�
         /// </summary>
-        /// <param name="logger">鏃ュ織璁板綍鍣�</param>
+        /// <param name="logger">鏃ュ織璁板綍鍣紝鐢变緷璧栨敞鍏ュ鍣ㄨ嚜鍔ㄦ敞鍏�</param>
         public ConveyorLineTargetAddressSelector(ILogger logger)
         {
-            _logger = logger;
+            _logger = logger; // 淇濆瓨鏃ュ織璁板綍鍣ㄥ疄渚嬶紝渚涘悗缁柟娉曚娇鐢�
         }
 
         /// <summary>
         /// 澶勭悊鍏ュ簱鍦烘櫙鐨勪笅涓�鍦板潃璇锋眰
         /// </summary>
         /// <remarks>
-        /// 褰撳叆搴撲换鍔℃墽琛屽埌鏌愪釜浣嶇疆鏃惰皟鐢ㄦ鏂规硶銆�
-        /// 鍒ゆ柇鐩爣璁惧鏄惁闇�瑕佺墿鏂欐垨鍙互鍑烘枡銆�
+        /// 鍏ュ簱浠诲姟鍒拌揪鏌愪釜浣嶇疆鏃惰皟鐢ㄦ鏂规硶锛屽垽鏂洰鏍囪澶囨槸鍚﹂渶瑕佺墿鏂欍��
+        /// 鍏ュ簱瀵瑰簲涓婂眰宸ヤ綅锛圠ayer.Upper锛夛紝鍥犱负鐗╂枡浠庝笂灞傝繘鍏ヤ粨搴撱��
         /// </remarks>
-        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄</param>
-        /// <param name="nextAddress">涓嬩竴鍦板潃/鐩爣璁惧缂栫爜</param>
-        /// <param name="childDeviceCode">褰撳墠瀛愯澶囩紪鐮�</param>
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛岀敤浜庡啓鍏ョ洰鏍囧湴鍧�鍜� ACK 淇″彿</param>
+        /// <param name="nextAddress">涓嬩竴鍦板潃/鐩爣璁惧缂栫爜锛岀敤浜庤瘑鍒洰鏍囪澶囩被鍨�</param>
+        /// <param name="childDeviceCode">褰撳墠瀛愯澶囩紪鐮侊紝鐢ㄤ簬绮剧‘瀹氫綅鍐欏叆鍝釜瀛愯澶�</param>
         public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
         {
-            _logger.LogDebug("HandleInboundNextAddress锛氬叆搴撲笅涓�鍦板潃锛屽瓙璁惧: {ChildDeviceCode}锛岀洰鏍囧湴鍧�: {NextAddress}", childDeviceCode, nextAddress);
-            QuartzLogger.Debug($"HandleInboundNextAddress锛氬叆搴撲笅涓�鍦板潃锛屽瓙璁惧: {childDeviceCode}锛岀洰鏍囧湴鍧�: {nextAddress}", conveyorLine.DeviceCode);
-            // 璋冪敤閫氱敤澶勭悊鏂规硶锛宨sUpper = true 琛ㄧず澶勭悊涓婂眰
-            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true);
+            // 璁板綍鍏ュ簱鍦烘櫙鐨勮皟璇曟棩蹇楋紝鍖呭惈瀛愯澶囧拰鐩爣鍦板潃淇℃伅
+            WriteDebug(conveyorLine, "鍏ュ簱涓嬩竴鍦板潃", childDeviceCode, nextAddress);
+            // 濮旀墭閫氱敤澶勭悊鏂规硶锛屽叆搴撳搴斾笂灞傦紙isUpper: true锛�
+            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, Layer.Upper);
         }
 
         /// <summary>
         /// 澶勭悊鍑哄簱鍦烘櫙鐨勪笅涓�鍦板潃璇锋眰
         /// </summary>
         /// <remarks>
-        /// 褰撳嚭搴撲换鍔℃墽琛屽埌鏌愪釜浣嶇疆鏃惰皟鐢ㄦ鏂规硶銆�
-        /// 鍒ゆ柇鐩爣璁惧鏄惁闇�瑕佺墿鏂欐垨鍙互鍑烘枡銆�
+        /// 鍑哄簱浠诲姟鍒拌揪鏌愪釜浣嶇疆鏃惰皟鐢ㄦ鏂规硶锛屽垽鏂洰鏍囪澶囨槸鍚﹂渶瑕佸嚭鏂欍��
+        /// 鍑哄簱瀵瑰簲涓嬪眰宸ヤ綅锛圠ayer.Lower锛夛紝鍥犱负鐗╂枡浠庝笅灞傜寮�浠撳簱銆�
         /// </remarks>
-        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄</param>
-        /// <param name="nextAddress">涓嬩竴鍦板潃/鐩爣璁惧缂栫爜</param>
-        /// <param name="childDeviceCode">褰撳墠瀛愯澶囩紪鐮�</param>
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛岀敤浜庡啓鍏ョ洰鏍囧湴鍧�鍜� ACK 淇″彿</param>
+        /// <param name="nextAddress">涓嬩竴鍦板潃/鐩爣璁惧缂栫爜锛岀敤浜庤瘑鍒洰鏍囪澶囩被鍨�</param>
+        /// <param name="childDeviceCode">褰撳墠瀛愯澶囩紪鐮侊紝鐢ㄤ簬绮剧‘瀹氫綅鍐欏叆鍝釜瀛愯澶�</param>
         public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
         {
-            _logger.LogDebug("HandleOutboundNextAddress锛氬嚭搴撲笅涓�鍦板潃锛屽瓙璁惧: {ChildDeviceCode}锛岀洰鏍囧湴鍧�: {NextAddress}", childDeviceCode, nextAddress);
-            QuartzLogger.Debug($"HandleOutboundNextAddress锛氬嚭搴撲笅涓�鍦板潃锛屽瓙璁惧: {childDeviceCode}锛岀洰鏍囧湴鍧�: {nextAddress}", conveyorLine.DeviceCode);
-            // 璋冪敤閫氱敤澶勭悊鏂规硶锛宨sUpper = false 琛ㄧず澶勭悊涓嬪眰
-            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false);
+            // 璁板綍鍑哄簱鍦烘櫙鐨勮皟璇曟棩蹇楋紝鍖呭惈瀛愯澶囧拰鐩爣鍦板潃淇℃伅
+            WriteDebug(conveyorLine, "鍑哄簱涓嬩竴鍦板潃", childDeviceCode, nextAddress);
+            // 濮旀墭閫氱敤澶勭悊鏂规硶锛屽嚭搴撳搴斾笅灞傦紙isUpper: false锛�
+            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, Layer.Lower);
         }
 
         /// <summary>
-        /// 閫氱敤璁惧璇锋眰澶勭悊鏂规硶
+        /// 鏍规嵁鐩爣鍦板潃绫诲瀷鍒嗗彂鍒板搴旇澶囧鐞�
         /// </summary>
         /// <remarks>
-        /// 鏍规嵁鐩爣鍦板潃绫诲瀷锛堟嫎鏉熸満/鎻掓嫈閽夋満锛夎皟鐢ㄧ浉搴旂殑澶勭悊閫昏緫銆�
-        /// 澶勭悊涓婁笅灞傝澶囩殑鐗╂枡璇锋眰鍜屽嚭鏂欏崗璋冦��
+        /// 閫氳繃 AddressToDeviceType 瀛楀吀灏嗙洰鏍囧湴鍧�鏄犲皠鍒拌澶囩被鍨嬶紝
+        /// 鐒跺悗鍒嗗彂鍒板搴旂殑涓撶敤澶勭悊鏂规硶锛圚andleConstraintMachine / HandlePinMachine锛夈��
+        /// 濡傛灉鐩爣鍦板潃涓嶅湪宸茬煡鏄犲皠琛ㄤ腑锛岀洿鎺ヨ繑鍥烇紝涓嶅仛浠讳綍澶勭悊銆�
         /// </remarks>
-        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄</param>
-        /// <param name="nextAddress">涓嬩竴鍦板潃/鐩爣璁惧缂栫爜</param>
-        /// <param name="childDeviceCode">褰撳墠瀛愯澶囩紪鐮�</param>
-        /// <param name="isUpper">鏄惁澶勭悊涓婂眰锛坱rue=涓婂眰锛宖alse=涓嬪眰锛�</param>
-        private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, bool isUpper)
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛屼紶閫掑埌鍏蜂綋璁惧澶勭悊鏂规硶</param>
+        /// <param name="nextAddress">鐩爣璁惧缂栫爜锛岄�氳繃瀛楀吀鏌ユ壘璇嗗埆璁惧绫诲瀷</param>
+        /// <param name="childDeviceCode">瀛愯澶囩紪鐮侊紝浼犻�掑埌鍏蜂綋璁惧澶勭悊鏂规硶</param>
+        /// <param name="layer">璁惧灞傜骇锛堜笂灞傛垨涓嬪眰锛夛紝鍐冲畾璇诲彇鍝粍璇锋眰鏍囧織</param>
+        private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, Layer layer)
         {
-            // 鑾峰彇鍏ㄥ眬璁惧鍒楄〃
-            var devices = Storage.Devices;
-
-            // 鍒ゆ柇鐩爣璁惧绫诲瀷
-            if (ConstraintMachineCodes.Contains(nextAddress))
+            // 閫氳繃瀛楀吀鏌ユ壘鐩爣鍦板潃瀵瑰簲鐨勮澶囩被鍨嬶紝濡傛灉鎵句笉鍒板垯 deviceType 涓� None
+            if (!AddressToDeviceType.TryGetValue(nextAddress, out var deviceType) || deviceType == TargetDeviceType.None)
             {
-                // 鎷樻潫鏈哄鐞嗗垎鏀�
-                // 鏌ユ壘鎷樻潫鏈鸿澶�
-                ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
-                if (constraint == null)
-                {
-                    _logger.LogDebug("HandleDeviceRequest锛氭湭鎵惧埌鎷樻潫鏈鸿澶�");
-                    QuartzLogger.Debug("HandleDeviceRequest锛氭湭鎵惧埌鎷樻潫鏈鸿澶�", conveyorLine.DeviceCode);
-                    // 鏈壘鍒版嫎鏉熸満璁惧锛岀洿鎺ヨ繑鍥�
-                    return;
-                }
-
-                // 澶勭悊鎷樻潫鏈虹殑璇锋眰
-                ProcessDeviceRequest(
-                    conveyorLine,
-                    childDeviceCode,
-                    // 鑾峰彇鐗╂枡璇锋眰鏍囧織锛堜笂灞傛垨涓嬪眰锛�
-                    getMaterialRequest: () => isUpper
-                        ? constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.MaterialRequestUpper) != 0
-                        : constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.MaterialRequestLower) != 0,
-                    // 鑾峰彇鍑烘枡璇锋眰鏍囧織锛堜笂灞傛垨涓嬪眰锛�
-                    getOutputRequest: () => isUpper
-                        ? constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.OutputRequestUpper) != 0
-                        : constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.OutputRequestLower) != 0,
-                    // 璁剧疆杈撳嚭灏辩华鏍囧織锛堜笂灞傛垨涓嬪眰锛�
-                    setOutputReady: outputReq =>
-                    {
-                        if (isUpper)
-                        {
-                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0);
-                        }
-                        else
-                        {
-                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0);
-                        }
-                    },
-                    "鎷樻潫鏈�");
+                // 鐩爣鍦板潃涓嶅湪宸茬煡鏄犲皠琛ㄤ腑锛岀洿鎺ヨ繑鍥烇紙鍙兘鏄叾浠栫被鍨嬭澶囷級
+                return;
             }
-            else if (PinMachineCodes.Contains(nextAddress))
+
+            // 鏍规嵁璇嗗埆鍑虹殑璁惧绫诲瀷鍒嗗彂鍒板搴旂殑澶勭悊鏂规硶
+            switch (deviceType)
             {
-                // 鎻掓嫈閽夋満澶勭悊鍒嗘敮
-                // 鏌ユ壘鎻掓嫈閽夋満璁惧
-                PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
-                if (pinMachine == null)
-                {
-                    _logger.LogDebug("HandleDeviceRequest锛氭湭鎵惧埌鎻掓嫈閽夋満璁惧");
-                    QuartzLogger.Debug("HandleDeviceRequest锛氭湭鎵惧埌鎻掓嫈閽夋満璁惧", conveyorLine.DeviceCode);
-                    return;
-                }
+                case TargetDeviceType.ConstraintMachine:
+                    // 鎷樻潫鏈哄鐞嗗垎鏀細鑾峰彇鎷樻潫鏈哄疄渚嬪苟澶勭悊鍏朵笂涓嬪眰璇锋眰
+                    HandleConstraintMachine(conveyorLine, childDeviceCode, layer);
+                    break; // 澶勭悊瀹屾瘯锛岃烦鍑� switch
 
-                // 澶勭悊鎻掓嫈閽夋満鐨勮姹�
-                ProcessDeviceRequest(
-                    conveyorLine,
-                    childDeviceCode,
-                    // 鑾峰彇鐗╂枡璇锋眰鏍囧織锛堜笂灞傛垨涓嬪眰锛�
-                    getMaterialRequest: () => isUpper
-                        ? pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.MaterialRequestUpper) != 0
-                        : pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.MaterialRequestLower) != 0,
-                    // 鑾峰彇鍑烘枡璇锋眰鏍囧織锛堜笂灞傛垨涓嬪眰锛�
-                    getOutputRequest: () => isUpper
-                        ? pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.OutputRequestUpper) != 0
-                        : pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.OutputRequestLower) != 0,
-                    // 璁剧疆杈撳嚭灏辩华鏍囧織锛堜笂灞傛垨涓嬪眰锛�
-                    setOutputReady: outputReq =>
-                    {
-                        if (isUpper)
-                        {
-                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0);
-                        }
-                        else
-                        {
-                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0);
-                        }
-                    },
-                    "鎻掓嫈閽夋満");
+                case TargetDeviceType.PinMachine:
+                    // 鎻掓嫈閽夋満澶勭悊鍒嗘敮锛氳幏鍙栨彃鎷旈拤鏈哄疄渚嬪苟澶勭悊鍏朵笂涓嬪眰璇锋眰
+                    HandlePinMachine(conveyorLine, childDeviceCode, layer);
+                    break; // 澶勭悊瀹屾瘯锛岃烦鍑� switch
             }
+        }
+
+        /// <summary>
+        /// 澶勭悊鎷樻潫鏈虹殑鐗╂枡/鍑烘枡璇锋眰
+        /// </summary>
+        /// <remarks>
+        /// 鏌ユ壘鎷樻潫鏈鸿澶囷紝鑾峰彇褰撳墠灞傜骇鐨勭墿鏂欒姹傚拰鍑烘枡璇锋眰鐘舵�侊紝
+        /// 鐒跺悗璋冪敤閫氱敤澶勭悊閫昏緫 ProcessDeviceRequest銆�
+        /// </remarks>
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛岀敤浜庨�氱煡鐩爣鍦板潃鍜� ACK</param>
+        /// <param name="childDeviceCode">瀛愯澶囩紪鐮侊紝鐢ㄤ簬绮剧‘瀹氫綅</param>
+        /// <param name="layer">璁惧灞傜骇锛屽喅瀹氳鍙栦笂灞傝繕鏄笅灞傜殑璇锋眰鏍囧織</param>
+        private void HandleConstraintMachine(CommonConveyorLine conveyorLine, string childDeviceCode, Layer layer)
+        {
+            // 浠庡叏灞�璁惧鍒楄〃涓煡鎵惧悕涓�"鎷樻潫鏈�"鐨勬嫎鏉熸満璁惧瀹炰緥
+            var constraint = FindDevice<ConstraintMachine>("鎷樻潫鏈�");
+            if (constraint == null)
+            {
+                // 鏈壘鍒版嫎鏉熸満璁惧锛屽凡鍦� FindDevice 涓褰曟棩蹇楋紝姝ゅ鐩存帴杩斿洖
+                return;
+            }
+
+            // 鑾峰彇褰撳墠灞傜骇锛堜笂灞�/涓嬪眰锛夌殑鐗╂枡璇锋眰鏍囧織锛岄潪闆惰〃绀鸿澶囬渶瑕佺墿鏂�
+            bool materialRequest = GetConstraintFlag(constraint, layer, isMaterial: true);
+            // 鑾峰彇褰撳墠灞傜骇锛堜笂灞�/涓嬪眰锛夌殑鍑烘枡璇锋眰鏍囧織锛岄潪闆惰〃绀鸿澶囨湁璐ц鍑�
+            bool outputRequest = GetConstraintFlag(constraint, layer, isMaterial: false);
+
+            // 鏋勯�犺缃緭鍑哄氨缁爣蹇楃殑濮旀墭锛堟牴鎹眰绾у啓鍏� Upper 鎴� Lower 瀵瑰簲鐨勫瘎瀛樺櫒锛�
+            Action<bool> setOutputReady = outputReady =>
+                SetConstraintOutputReady(constraint, layer, outputReady);
+
+            // 璋冪敤閫氱敤璇锋眰澶勭悊閫昏緫锛屼紶鍏ヨ澶囩被鍨嬫弿杩扮敤浜庢棩蹇楄褰�
+            ProcessDeviceRequest(conveyorLine, childDeviceCode, materialRequest, outputRequest, setOutputReady, "鎷樻潫鏈�");
+        }
+
+        /// <summary>
+        /// 澶勭悊鎻掓嫈閽夋満鐨勭墿鏂�/鍑烘枡璇锋眰
+        /// </summary>
+        /// <remarks>
+        /// 鏌ユ壘鎻掓嫈閽夋満璁惧锛岃幏鍙栧綋鍓嶅眰绾х殑鐗╂枡璇锋眰鍜屽嚭鏂欒姹傜姸鎬侊紝
+        /// 鐒跺悗璋冪敤閫氱敤澶勭悊閫昏緫 ProcessDeviceRequest銆�
+        /// </remarks>
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛岀敤浜庨�氱煡鐩爣鍦板潃鍜� ACK</param>
+        /// <param name="childDeviceCode">瀛愯澶囩紪鐮侊紝鐢ㄤ簬绮剧‘瀹氫綅</param>
+        /// <param name="layer">璁惧灞傜骇锛屽喅瀹氳鍙栦笂灞傝繕鏄笅灞傜殑璇锋眰鏍囧織</param>
+        private void HandlePinMachine(CommonConveyorLine conveyorLine, string childDeviceCode, Layer layer)
+        {
+            // 浠庡叏灞�璁惧鍒楄〃涓煡鎵惧悕涓�"鎻掓嫈閽夋満"鐨勬彃鎷旈拤鏈鸿澶囧疄渚�
+            var pinMachine = FindDevice<PinMachine>("鎻掓嫈閽夋満");
+            if (pinMachine == null)
+            {
+                // 鏈壘鍒版彃鎷旈拤鏈鸿澶囷紝宸插湪 FindDevice 涓褰曟棩蹇楋紝姝ゅ鐩存帴杩斿洖
+                return;
+            }
+
+            // 鑾峰彇褰撳墠灞傜骇锛堜笂灞�/涓嬪眰锛夌殑鐗╂枡璇锋眰鏍囧織锛岄潪闆惰〃绀鸿澶囬渶瑕佺墿鏂�
+            bool materialRequest = GetPinMachineFlag(pinMachine, layer, isMaterial: true);
+            // 鑾峰彇褰撳墠灞傜骇锛堜笂灞�/涓嬪眰锛夌殑鍑烘枡璇锋眰鏍囧織锛岄潪闆惰〃绀鸿澶囨湁璐ц鍑�
+            bool outputRequest = GetPinMachineFlag(pinMachine, layer, isMaterial: false);
+
+            // 鏋勯�犺缃緭鍑哄氨缁爣蹇楃殑濮旀墭锛堟牴鎹眰绾у啓鍏� Upper 鎴� Lower 瀵瑰簲鐨勫瘎瀛樺櫒锛�
+            Action<bool> setOutputReady = outputReady =>
+                SetPinMachineOutputReady(pinMachine, layer, outputReady);
+
+            // 璋冪敤閫氱敤璇锋眰澶勭悊閫昏緫锛屼紶鍏ヨ澶囩被鍨嬫弿杩扮敤浜庢棩蹇楄褰�
+            ProcessDeviceRequest(conveyorLine, childDeviceCode, materialRequest, outputRequest, setOutputReady, "鎻掓嫈閽夋満");
+        }
+
+        /// <summary>
+        /// 鏌ユ壘鎸囧畾鍚嶇О鐨勮澶�
+        /// </summary>
+        /// <remarks>
+        /// 浠庡叏灞�璁惧鍒楄〃 Storage.Devices 涓�氳繃璁惧绫诲瀷鍜屽悕绉版煡鎵捐澶囧疄渚嬨��
+        /// 杩欐槸涓�涓硾鍨嬫柟娉曪紝鍙互閫傜敤浜� ConstraintMachine銆丳inMachine 绛夊绉嶈澶囩被鍨嬨��
+        /// </remarks>
+        /// <typeparam name="T">璁惧绫诲瀷锛屽繀椤绘槸寮曠敤绫诲瀷锛堝 ConstraintMachine銆丳inMachine锛�</typeparam>
+        /// <param name="deviceName">璁惧鍚嶇О锛岀敤浜庣簿纭尮閰嶈澶囩殑 DeviceName 灞炴��</param>
+        /// <returns>鎵惧埌鐨勮澶囧疄渚嬶紝鏈壘鍒板垯杩斿洖 null</returns>
+        private T? FindDevice<T>(string deviceName) where T : class
+        {
+            // OfType<T>() 绛涢�夊嚭鎸囧畾绫诲瀷鐨勮澶囷紝FirstOrDefault 鎸夊悕绉扮簿纭尮閰�
+            var device = Storage.Devices.OfType<T>().FirstOrDefault(d => GetDeviceName(d) == deviceName);
+            if (device == null)
+            {
+                // 璁惧鏈壘鍒版椂璁板綍璋冭瘯鏃ュ織锛屾柟渚挎帓鏌ラ厤缃棶棰�
+                _logger.LogDebug("FindDevice锛氭湭鎵惧埌 {DeviceName}", deviceName);
+            }
+            return device; // 鍙兘涓� null锛岀敱璋冪敤鏂硅礋璐� null 妫�鏌�
+        }
+
+        /// <summary>
+        /// 閫氳繃澶氭�佽幏鍙栦换鎰忚澶囩殑鍚嶇О
+        /// </summary>
+        /// <remarks>
+        /// 浣跨敤 switch 琛ㄨ揪寮忔牴鎹澶囩殑鍏蜂綋绫诲瀷鑾峰彇鍏� DeviceName 灞炴�э紝
+        /// 閬垮厤涓烘瘡绉嶈澶囩被鍨嬬紪鍐欑嫭绔嬬殑鍙嶅皠鎴栧睘鎬ц闂唬鐮併��
+        /// 褰撳墠鏀寔 ConstraintMachine 鍜� PinMachine 涓ょ绫诲瀷銆�
+        /// </remarks>
+        /// <typeparam name="T">璁惧绫诲瀷</typeparam>
+        /// <param name="device">璁惧瀹炰緥锛岄潪绌�</param>
+        /// <returns>璁惧鐨勫悕绉板瓧绗︿覆锛屽鏋滅被鍨嬩笉鍖归厤鍒欒繑鍥炵┖瀛楃涓�</returns>
+        private static string GetDeviceName<T>(T device) where T : class
+        {
+            // 妯″紡鍖归厤锛氭牴鎹澶囩殑鍏蜂綋杩愯鏃剁被鍨嬭繑鍥炲搴旂殑 DeviceName
+            return device switch
+            {
+                ConstraintMachine cm => cm.DeviceName, // 鎷樻潫鏈鸿繑鍥炲叾璁惧鍚嶇О
+                PinMachine pm => pm.DeviceName,       // 鎻掓嫈閽夋満杩斿洖鍏惰澶囧悕绉�
+                _ => string.Empty                     // 鏈煡绫诲瀷杩斿洖绌哄瓧绗︿覆锛堢悊璁轰笂涓嶄細璧板埌杩欓噷锛�
+            };
+        }
+
+        /// <summary>
+        /// 鑾峰彇鎷樻潫鏈虹殑璇锋眰鏍囧織锛堢墿鏂欒姹傛垨鍑烘枡璇锋眰锛�
+        /// </summary>
+        /// <remarks>
+        /// 鏍规嵁 isMaterial 鍙傛暟鍐冲畾璇诲彇鐗╂枡璇锋眰杩樻槸鍑烘枡璇锋眰瀵勫瓨鍣紝
+        /// 鍐嶆牴鎹� layer 鍙傛暟鍐冲畾璇诲彇涓婂眰锛圲pper锛夎繕鏄笅灞傦紙Lower锛夊瘎瀛樺櫒銆�
+        /// 杩斿洖鍊艰〃绀鸿姹傛槸鍚︽湁鏁堬紙闈為浂涓烘湁鏁堬級銆�
+        /// </remarks>
+        /// <param name="constraint">鎷樻潫鏈鸿澶囧疄渚嬶紝鐢ㄤ簬璇诲彇 PLC 瀵勫瓨鍣ㄥ��</param>
+        /// <param name="layer">璁惧灞傜骇锛屽喅瀹氳鍙� Upper 杩樻槸 Lower 瀵勫瓨鍣�</param>
+        /// <param name="isMaterial">true=璇诲彇鐗╂枡璇锋眰鏍囧織锛宖alse=璇诲彇鍑烘枡璇锋眰鏍囧織</param>
+        /// <returns>璇锋眰鏍囧織鏄惁鏈夋晥锛坱rue=鏈夎姹傦紝false=鏃犺姹傦級</returns>
+        private bool GetConstraintFlag(ConstraintMachine constraint, Layer layer, bool isMaterial)
+        {
+            // 鏍规嵁 isMaterial 閫夋嫨瀵瑰簲鐨勫瘎瀛樺櫒鍚嶇О瀵癸紙鐗╂枡璇锋眰鎴栧嚭鏂欒姹傦級
+            var (materialKey, outputKey) = isMaterial
+                ? (ConstraintMachineDBName.MaterialRequestUpper, ConstraintMachineDBName.MaterialRequestLower)   // 鐗╂枡璇锋眰
+                : (ConstraintMachineDBName.OutputRequestUpper, ConstraintMachineDBName.OutputRequestLower);        // 鍑烘枡璇锋眰
+
+            // 鏍规嵁 layer 閫夋嫨鍏蜂綋浣跨敤 Upper 杩樻槸 Lower 鐗堟湰鐨勫瘎瀛樺櫒
+            var key = layer == Layer.Upper ? materialKey : outputKey;
+            // 璇诲彇瀵勫瓨鍣ㄥ�硷紝闈為浂琛ㄧず鏈夎姹傦紝杩斿洖甯冨皵鍊�
+            return constraint.GetValue<ConstraintMachineDBName, short>(key) != 0;
+        }
+
+        /// <summary>
+        /// 鑾峰彇鎻掓嫈閽夋満鐨勮姹傛爣蹇楋紙鐗╂枡璇锋眰鎴栧嚭鏂欒姹傦級
+        /// </summary>
+        /// <remarks>
+        /// 涓� GetConstraintFlag 閫昏緫鐩稿悓锛屼絾鎿嶄綔瀵硅薄鏄彃鎷旈拤鏈猴紙PinMachine锛夈��
+        /// 鏍规嵁 isMaterial 鍙傛暟鍐冲畾璇诲彇鐗╂枡璇锋眰杩樻槸鍑烘枡璇锋眰瀵勫瓨鍣紝
+        /// 鍐嶆牴鎹� layer 鍙傛暟鍐冲畾璇诲彇涓婂眰锛圲pper锛夎繕鏄笅灞傦紙Lower锛夊瘎瀛樺櫒銆�
+        /// </remarks>
+        /// <param name="pinMachine">鎻掓嫈閽夋満璁惧瀹炰緥锛岀敤浜庤鍙� PLC 瀵勫瓨鍣ㄥ��</param>
+        /// <param name="layer">璁惧灞傜骇锛屽喅瀹氳鍙� Upper 杩樻槸 Lower 瀵勫瓨鍣�</param>
+        /// <param name="isMaterial">true=璇诲彇鐗╂枡璇锋眰鏍囧織锛宖alse=璇诲彇鍑烘枡璇锋眰鏍囧織</param>
+        /// <returns>璇锋眰鏍囧織鏄惁鏈夋晥锛坱rue=鏈夎姹傦紝false=鏃犺姹傦級</returns>
+        private bool GetPinMachineFlag(PinMachine pinMachine, Layer layer, bool isMaterial)
+        {
+            // 鏍规嵁 isMaterial 閫夋嫨瀵瑰簲鐨勫瘎瀛樺櫒鍚嶇О瀵癸紙鐗╂枡璇锋眰鎴栧嚭鏂欒姹傦級
+            var (materialKey, outputKey) = isMaterial
+                ? (PinMachineDBName.MaterialRequestUpper, PinMachineDBName.MaterialRequestLower)   // 鐗╂枡璇锋眰
+                : (PinMachineDBName.OutputRequestUpper, PinMachineDBName.OutputRequestLower);       // 鍑烘枡璇锋眰
+
+            // 鏍规嵁 layer 閫夋嫨鍏蜂綋浣跨敤 Upper 杩樻槸 Lower 鐗堟湰鐨勫瘎瀛樺櫒
+            var key = layer == Layer.Upper ? materialKey : outputKey;
+            // 璇诲彇瀵勫瓨鍣ㄥ�硷紝闈為浂琛ㄧず鏈夎姹傦紝杩斿洖甯冨皵鍊�
+            return pinMachine.GetValue<PinMachineDBName, short>(key) != 0;
+        }
+
+        /// <summary>
+        /// 璁剧疆鎷樻潫鏈虹殑杈撳嚭灏辩华鏍囧織
+        /// </summary>
+        /// <remarks>
+        /// 鍚戞嫎鏉熸満鐨� PLC 瀵勫瓨鍣ㄥ啓鍏ヨ緭鍑哄氨缁姸鎬侊紝鍛婄煡鎷樻潫鏈哄彲浠ョ户缁嚭鏂欍��
+        /// 鏍规嵁 layer 鍙傛暟鍐冲畾鍐欏叆涓婂眰锛圕onstraintTrayOutputReadyUpper锛夎繕鏄笅灞傦紙ConstraintTrayOutputReadyLower锛夊瘎瀛樺櫒銆�
+        /// </remarks>
+        /// <param name="constraint">鎷樻潫鏈鸿澶囧疄渚嬶紝鐢ㄤ簬鍐欏叆 PLC 瀵勫瓨鍣�</param>
+        /// <param name="layer">璁惧灞傜骇锛屽喅瀹氬啓鍏� Upper 杩樻槸 Lower 瀵勫瓨鍣�</param>
+        /// <param name="outputReady">杈撳嚭灏辩华鐘舵�侊紝true=鍙互鍑烘枡锛宖alse=涓嶈兘鍑烘枡</param>
+        private void SetConstraintOutputReady(ConstraintMachine constraint, Layer layer, bool outputReady)
+        {
+            // 鏍规嵁 layer 閫夋嫨瀵瑰簲鐨勮緭鍑哄氨缁瘎瀛樺櫒锛圲pper 鎴� Lower锛�
+            var key = layer == Layer.Upper
+                ? ConstraintMachineDBName.ConstraintTrayOutputReadyUpper   // 涓婂眰杈撳嚭灏辩华瀵勫瓨鍣�
+                : ConstraintMachineDBName.ConstraintTrayOutputReadyLower;  // 涓嬪眰杈撳嚭灏辩华瀵勫瓨鍣�
+            // 鍚� PLC 鍐欏叆鍊硷紝outputReady 涓� true 鏃跺啓鍏� 1锛屽惁鍒欏啓鍏� 0
+            constraint.SetValue(key, outputReady ? true : false);
+        }
+
+        /// <summary>
+        /// 璁剧疆鎻掓嫈閽夋満鐨勮緭鍑哄氨缁爣蹇�
+        /// </summary>
+        /// <remarks>
+        /// 鍚戞彃鎷旈拤鏈虹殑 PLC 瀵勫瓨鍣ㄥ啓鍏ヨ緭鍑哄氨缁姸鎬侊紝鍛婄煡鎻掓嫈閽夋満鍙互缁х画鍑烘枡銆�
+        /// 鏍规嵁 layer 鍙傛暟鍐冲畾鍐欏叆涓婂眰锛圥lugPinTrayOutputReadyUpper锛夎繕鏄笅灞傦紙PlugPinTrayOutputReadyLower锛夊瘎瀛樺櫒銆�
+        /// </remarks>
+        /// <param name="pinMachine">鎻掓嫈閽夋満璁惧瀹炰緥锛岀敤浜庡啓鍏� PLC 瀵勫瓨鍣�</param>
+        /// <param name="layer">璁惧灞傜骇锛屽喅瀹氬啓鍏� Upper 杩樻槸 Lower 瀵勫瓨鍣�</param>
+        /// <param name="outputReady">杈撳嚭灏辩华鐘舵�侊紝true=鍙互鍑烘枡锛宖alse=涓嶈兘鍑烘枡</param>
+        private void SetPinMachineOutputReady(PinMachine pinMachine, Layer layer, bool outputReady)
+        {
+            // 鏍规嵁 layer 閫夋嫨瀵瑰簲鐨勮緭鍑哄氨缁瘎瀛樺櫒锛圲pper 鎴� Lower锛�
+            var key = layer == Layer.Upper
+                ? PinMachineDBName.PlugPinTrayOutputReadyUpper   // 涓婂眰杈撳嚭灏辩华瀵勫瓨鍣�
+                : PinMachineDBName.PlugPinTrayOutputReadyLower; // 涓嬪眰杈撳嚭灏辩华瀵勫瓨鍣�
+            // 鍚� PLC 鍐欏叆鍊硷紝outputReady 涓� true 鏃跺啓鍏� 1锛屽惁鍒欏啓鍏� 0
+            pinMachine.SetValue(key, outputReady ? true : false);
         }
 
         /// <summary>
         /// 澶勭悊璁惧璇锋眰鐨勬牳蹇冮�昏緫
         /// </summary>
         /// <remarks>
-        /// 鏍规嵁鐗╂枡璇锋眰鍜屽嚭鏂欒姹傜殑鐘舵�侊細
-        /// - 濡傛灉鏈夌墿鏂欒姹傦紝璁剧疆鐩爣鍦板潃骞跺彂閫� ACK
-        /// - 濡傛灉鏈夊嚭鏂欒姹傦紝璁剧疆璁惧鐨勮緭鍑哄氨缁爣蹇�
+        /// 鏍规嵁鐗╂枡璇锋眰鍜屽嚭鏂欒姹傜殑鐘舵�佸喅瀹氳涓猴細
+        /// - 濡傛灉璁惧闇�瑕佺墿鏂欙紙materialRequest=true锛夛紝璁剧疆杈撻�佺嚎鐨勭洰鏍囧湴鍧�涓� 1锛堟湁鏂欒繘鏉ワ級骞跺洖澶� ACK 纭淇″彿
+        /// - 濡傛灉璁惧涓嶉渶瑕佺墿鏂欙紙materialRequest=false锛夛紝鍒欓�氱煡璁惧鍙互缁х画鍑烘枡锛坰etOutputReady锛�
+        ///
+        /// 杩欐槸鎷樻潫鏈哄拰鎻掓嫈閽夋満鍏辩敤鐨勫鐞嗛�昏緫锛岃澶囩壒鏈夌殑閮ㄥ垎宸插湪姝ゆ柟娉曞灏佽銆�
         /// </remarks>
-        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄</param>
-        /// <param name="childDeviceCode">褰撳墠瀛愯澶囩紪鐮�</param>
-        /// <param name="getMaterialRequest">鑾峰彇鐗╂枡璇锋眰鐘舵�佺殑濮旀墭</param>
-        /// <param name="getOutputRequest">鑾峰彇鍑烘枡璇锋眰鐘舵�佺殑濮旀墭</param>
-        /// <param name="setOutputReady">璁剧疆杈撳嚭灏辩华鏍囧織鐨勫鎵�</param>
-        /// <param name="deviceType">璁惧绫诲瀷鎻忚堪</param>
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛岀敤浜庡啓鍏ョ洰鏍囧湴鍧�锛圱arget锛夊拰 ACK 淇″彿</param>
+        /// <param name="childDeviceCode">瀛愯澶囩紪鐮侊紝鐢ㄤ簬绮剧‘瀹氫綅鍐欏叆鍝釜瀛愯澶�</param>
+        /// <param name="materialRequest">鐗╂枡璇锋眰鏍囧織锛宼rue=璁惧褰撳墠闇�瑕佽ˉ鍏呯墿鏂�</param>
+        /// <param name="outputRequest">鍑烘枡璇锋眰鏍囧織锛宼rue=璁惧褰撳墠鏈夌墿鏂欓渶瑕佽緭鍑猴紙閰嶅悎 materialRequest=false 鏃朵娇鐢級</param>
+        /// <param name="setOutputReady">璁剧疆璁惧杈撳嚭灏辩华鏍囧織鐨勫鎵橈紝鏍规嵁灞傜骇鍐欏叆 Upper 鎴� Lower 瀵勫瓨鍣�</param>
+        /// <param name="deviceType">璁惧绫诲瀷鎻忚堪瀛楃涓诧紝鐢ㄤ簬鏃ュ織杈撳嚭</param>
         private void ProcessDeviceRequest(
             CommonConveyorLine conveyorLine,
             string childDeviceCode,
-            Func<bool> getMaterialRequest,
-            Func<bool> getOutputRequest,
+            bool materialRequest,
+            bool outputRequest,
             Action<bool> setOutputReady,
             string deviceType)
         {
-            // 鑾峰彇鐗╂枡璇锋眰鐘舵��
-            bool materialReq = getMaterialRequest();
-
-            // 鑾峰彇鍑烘枡璇锋眰鐘舵��
-            bool outputReq = getOutputRequest();
-
+            // 璁板綍褰撳墠璇锋眰鐘舵�佺殑璋冭瘯鏃ュ織锛屼緵鎺掓煡闂浣跨敤
             _logger.LogDebug("ProcessDeviceRequest锛歿DeviceType}锛屽瓙璁惧: {ChildDeviceCode}锛岀墿鏂欒姹�: {MaterialReq}锛屽嚭鏂欒姹�: {OutputReq}",
-                deviceType, childDeviceCode, materialReq, outputReq);
-            QuartzLogger.Debug($"ProcessDeviceRequest锛歿deviceType}锛屽瓙璁惧: {childDeviceCode}锛岀墿鏂欒姹�: {materialReq}锛屽嚭鏂欒姹�: {outputReq}", conveyorLine.DeviceCode);
+                deviceType, childDeviceCode, materialRequest, outputRequest);
+            // 鍚屾鍐欏叆 Quartz 鏃ュ織鏂囦欢锛堝弻鍐欏彲杩芥函锛岃繖閲屼繚鐣欎笌鍘熼�昏緫涓�鑷寸殑琛屼负锛�
+            QuartzLogger.Debug($"ProcessDeviceRequest锛歿deviceType}锛屽瓙璁惧: {childDeviceCode}锛岀墿鏂欒姹�: {materialRequest}锛屽嚭鏂欒姹�: {outputRequest}", conveyorLine.DeviceCode);
 
-            // 濡傛灉璁惧闇�瑕佺墿鏂�
-            if (materialReq)
+            // 鍒嗘敮鍒ゆ柇锛氳澶囨槸闇�瑕佺墿鏂欒繕鏄渶瑕佸嚭鏂�
+            if (materialRequest)
             {
-                // 璁剧疆鐩爣鍦板潃涓� 1锛堣〃绀烘湁鏂欒繘鏉ワ級
+                // 璁惧闇�瑕佺墿鏂� -> 閫氱煡杈撻�佺嚎鏈夋枡杩囨潵
+                // 1. 璁剧疆鐩爣鍦板潃涓� 1锛岃〃绀�"鏈夋枡杩涘叆"
                 conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode);
-
-                // 鍥炲 ACK 纭淇″彿
+                // 2. 鍥炲 ACK 纭淇″彿锛屽憡鐭ヨ澶囧凡鏀跺埌璇锋眰
                 conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
-
+                // 3. 璁板綍淇℃伅鏃ュ織锛岃〃鏄庡凡瀹屾垚鐩爣鍦板潃璁剧疆鍜� ACK 鍥炲
                 _logger.LogInformation("ProcessDeviceRequest锛歿DeviceType} 闇�瑕佺墿鏂欙紝宸茶缃洰鏍囧湴鍧�鍜孉CK", deviceType);
                 QuartzLogger.Info($"ProcessDeviceRequest锛歿deviceType} 闇�瑕佺墿鏂欙紝宸茶缃洰鏍囧湴鍧�鍜孉CK", conveyorLine.DeviceCode);
             }
             else
             {
-                // 璁惧涓嶉渶瑕佺墿鏂欙紝璁剧疆杈撳嚭灏辩华鏍囧織
-                // 閫氱煡璁惧鍙互缁х画鍑烘枡
-                setOutputReady(outputReq);
+                // 璁惧涓嶉渶瑕佺墿鏂� -> 閫氱煡璁惧鍙互缁х画鍑烘枡锛堟棤璁哄綋鍓嶆槸鍚︽湁璐ц鍑猴紝閮借閫氱煡锛�
+                // outputRequest 琛ㄧず璁惧褰撳墠鏄惁纭疄鏈夎揣锛屽鏋滄病鏈夎揣鍒� outputReady=false锛岃澶囨敹鍒板悗绛夊緟
+                setOutputReady(outputRequest);
             }
         }
+
+        /// <summary>
+        /// 鍐欏叆璋冭瘯鏃ュ織锛堝悓鏃惰緭鍑哄埌涓や釜鏃ュ織绯荤粺锛�
+        /// </summary>
+        /// <remarks>
+        /// 缁熶竴鍏ュ彛鐐规棩蹇楁牸寮忥紝鍚屾椂鍚� Microsoft.Extensions.Logging 鍜� QuartzLogger 鍐欏叆锛�
+        /// 淇濊瘉鏃ュ織鏃㈣兘鍦ㄦ帶鍒跺彴鏌ョ湅涔熻兘鍦ㄦ枃浠朵腑杩芥函銆�
+        /// </remarks>
+        /// <param name="conveyorLine">杈撻�佺嚎璁惧瀵硅薄锛岀敤浜庤幏鍙栬澶囩紪鐮佸啓鍏� QuartzLogger</param>
+        /// <param name="scenario">鍦烘櫙鎻忚堪锛屽"鍏ュ簱涓嬩竴鍦板潃"鎴�"鍑哄簱涓嬩竴鍦板潃"</param>
+        /// <param name="childDeviceCode">瀛愯澶囩紪鐮�</param>
+        /// <param name="nextAddress">鐩爣璁惧缂栫爜</param>
+        private void WriteDebug(CommonConveyorLine conveyorLine, string scenario, string childDeviceCode, string nextAddress)
+        {
+            // 鍐欏叆缁撴瀯鍖栨棩蹇楋紙鍙 Serilog 绛夋棩蹇楁鏋舵崟鑾凤級
+            _logger.LogDebug("Handle{Scenario}锛氬瓙璁惧: {ChildDeviceCode}锛岀洰鏍囧湴鍧�: {NextAddress}",
+                scenario, childDeviceCode, nextAddress);
+            // 鍐欏叆 Quartz 涓撶敤鏃ュ織鏂囦欢锛堜緵瀹氭椂浠诲姟杞ㄨ抗杩借釜锛�
+            QuartzLogger.Debug($"Handle{scenario}锛氬瓙璁惧: {childDeviceCode}锛岀洰鏍囧湴鍧�: {nextAddress}", conveyorLine.DeviceCode);
+        }
     }
 }
diff --git a/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue b/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue
index dfc84c2..02a7a75 100644
--- a/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue
+++ b/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue
@@ -456,6 +456,7 @@
         <vol-table
           ref="table"
           :single="single"
+          :expand="tableExpand"
           :rowKey="rowKey"
           :loadTreeChildren="loadTreeTableChildren"
           @loadBefore="loadTableBefore"
diff --git a/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js b/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js
index 5d0ebc6..138680b 100644
--- a/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js
+++ b/Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js
@@ -1,55 +1,61 @@
 let props = {
-  columns: {//褰撳墠琛ㄧ殑閰嶇疆淇℃伅
+  columns: {
     type: Array,
     default: () => {
       return [];
     }
   },
-  detail: {//浠庤〃鏄庣粏閰嶇疆
+  detail: {
     type: Object,
     default: () => {
       return {
-        columns: [],//浠庤〃鍒�
-        sortName: ""//浠庤〃鎺掑簭瀛楁
+        columns: [],
+        sortName: ""
       };
     }
   },
-  editFormFields: {//鏂板缓銆佺紪杈戝瓧娈�(key/value)
+  editFormFields: {
     type: Object,
     default: () => {
       return {};
     }
   },
-  editFormOptions: {//鏂板缓銆佺紪杈戦厤缃俊鎭�
+  editFormOptions: {
     type: Array,
     default: () => {
       return [];
     }
   },
-  searchFormFields: {//鏌ヨ瀛楁(key/value)
+  searchFormFields: {
     type: Object,
     default: () => {
       return {};
     }
   },
-  searchFormOptions: {//鏌ヨ閰嶇疆淇℃伅(key/value)
+  searchFormOptions: {
     type: Array,
     default: () => {
       return [];
     }
   },
-  table: {//琛ㄧ殑閰嶇疆淇℃伅锛氫富閿�佹帓搴忕瓑
+  table: {
     type: Object,
     default: () => {
       return {};
     }
   },
-  extend: {//琛ㄧ殑鎵╁睍鏂规硶涓庣粍浠堕兘鍚堝苟鍒版灞炴�т腑
+  tableExpand: {
+    type: Object,
+    default: () => {
+      return {};
+    }
+  },
+  extend: {
     type: Object,
     default: () => {
       return {};
     }
   }
-}
+};
 
-export default props;
\ No newline at end of file
+export default props;
diff --git a/Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue b/Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue
index 6ca1e90..7d4f846 100644
--- a/Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue
+++ b/Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue
@@ -21,6 +21,7 @@
       @select="userSelect"
       @select-all="userSelect"
       @selection-change="selectionChange"
+      @expand-change="expandChange"
       @row-dblclick="rowDbClick"
       @row-click="rowClick"
       @header-click="headerClick"
@@ -38,6 +39,24 @@
       style="width: 100%"
       :scrollbar-always-on="true"
     >
+      <el-table-column
+        v-if="showExpand"
+        type="expand"
+        :fixed="expand.fixed"
+        :width="expand.width || 55"
+      >
+        <template #default="scope">
+          <div class="expand-cell">
+            <table-render
+              v-if="expand.render && typeof expand.render == 'function'"
+              :row="scope.row"
+              :index="scope.$index"
+              :column="expand"
+              :render="expand.render"
+            ></table-render>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column
         v-if="ck"
         type="selection"
@@ -559,6 +578,12 @@
         return 1;
       },
     },
+    expand: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
     pagination: {
       type: Object,
       default: function () {
@@ -834,6 +859,9 @@
     this.defaultLoadPage && this.load();
   },
   computed: {
+    showExpand() {
+      return !!(this.expand && typeof this.expand.render === "function");
+    },
     filterColumns() {
       return this.columns.filter((x, index) => {
         if (!x.field) {
@@ -844,6 +872,12 @@
     },
   },
   methods: {
+    expandChange(row, expandedRows) {
+      if (this.expand && typeof this.expand.onChange === "function") {
+        this.expand.onChange(row, expandedRows, this);
+      }
+      this.$emit("expandChange", { row, expandedRows });
+    },
     watchRowSelectChange(newLen, oldLen) {
       if (newLen < oldLen && this.selectRows.length) {
         this.selectRows = [];
@@ -1784,6 +1818,9 @@
   border-bottom: 1px solid;
   padding-bottom: 2px;
 }
+.vol-table .expand-cell {
+  padding: 8px 0;
+}
 .vol-table .empty-tag {
   border: none;
   background: none;
diff --git a/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue b/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue
index f7ec3e1..96fae1f 100644
--- a/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue
+++ b/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue
@@ -1,154 +1,478 @@
-
 <template>
-    <view-grid
-      ref="grid"
-      :columns="columns"
-      :detail="detail"
-      :editFormFields="editFormFields"
-      :editFormOptions="editFormOptions"
-      :searchFormFields="searchFormFields"
-      :searchFormOptions="searchFormOptions"
-      :table="table"
-      :extend="extend"
-    >
-    </view-grid>
-  </template>
-    <script>
-  import extend from "@/extension/stock/stockInfo.js";
-  import { ref, defineComponent } from "vue";
-  export default defineComponent({
-    setup() {
-      const table = ref({
-        key: "id",
-        footer: "Foots",
-        cnName: "搴撳瓨淇℃伅",
-        name: "stockInfo",
-        url: "/StockInfo/",
-        sortName: "id",
-      });
-      const editFormFields = ref({
-        deviceCode: "",
-        deviceName: "",
-        deviceType: "",
-        deviceStatus: "",
-        deviceIp: "",
-        devicePort: "",
-        devicePlcType: "",
-        deviceRemark: "",
-      });
-      const editFormOptions = ref([
-       [
-        {field:'palletCode',title:'鎵樼洏缂栧彿',type:'string'},
-        {field:'locationCode',title:'璐т綅缂栧彿',type:'string'},
-       ]
+  <view-grid
+    ref="grid"
+    :columns="columns"
+    :detail="detail"
+    :editFormFields="editFormFields"
+    :editFormOptions="editFormOptions"
+    :searchFormFields="searchFormFields"
+    :searchFormOptions="searchFormOptions"
+    :table="table"
+    :tableExpand="tableExpand"
+    :extend="extend"
+  >
+  </view-grid>
+</template>
+
+<script>
+import extend from "@/extension/stock/stockInfo.js";
+import {
+  defineComponent,
+  getCurrentInstance,
+  h,
+  reactive,
+  ref,
+  resolveComponent,
+} from "vue";
+
+const TEXT = {
+  pageName: "搴撳瓨淇℃伅",
+  palletCode: "鎵樼洏缂栧彿",
+  locationCode: "璐т綅缂栧彿",
+  warehouse: "浠撳簱",
+  creator: "鍒涘缓浜�",
+  createDate: "鍒涘缓鏃堕棿",
+  modifier: "淇敼浜�",
+  modifyDate: "淇敼鏃堕棿",
+  detailName: "搴撳瓨鏄庣粏",
+  materielName: "鐗╂枡鍚嶇О",
+  serialNumber: "鐢佃姱鐮�",
+  stockQuantity: "搴撳瓨鏁伴噺",
+  status: "鐘舵��",
+  inboundOrderRowNo: "閫氶亾鍙�",
+  detailLoading: "搴撳瓨鏄庣粏鍔犺浇涓�...",
+  detailLoadFailed: "搴撳瓨鏄庣粏鍔犺浇澶辫触",
+  detailEmpty: "褰撳墠搴撳瓨澶存殏鏃犳槑缁嗘暟鎹�",
+  expandPrefix: "鎵樼洏锛�",
+  expandMiddle: " / ",
+  expandLocation: "璐т綅锛�",
+};
+
+export default defineComponent({
+  setup() {
+    const { proxy } = getCurrentInstance();
+    const ElTable = resolveComponent("el-table");
+    const ElTableColumn = resolveComponent("el-table-column");
+
+    const table = ref({
+      key: "id",
+      footer: "Foots",
+      cnName: TEXT.pageName,
+      name: "stockInfo",
+      url: "/StockInfo/",
+      sortName: "id",
+    });
+
+    const editFormFields = ref({
+      palletCode: "",
+      locationCode: "",
+    });
+
+    const editFormOptions = ref([
+      [
+        { field: "palletCode", title: TEXT.palletCode, type: "string" },
+        { field: "locationCode", title: TEXT.locationCode, type: "string" },
+      ],
+    ]);
+
+    const searchFormFields = ref({
+      palletCode: "",
+      locationCode: "",
+    });
+
+    const searchFormOptions = ref([
+      [
+        { title: TEXT.palletCode, field: "palletCode", type: "like" },
+        { title: TEXT.locationCode, field: "locationCode", type: "like" },
+      ],
+    ]);
+
+    const columns = ref([
+      {
+        field: "id",
+        title: "Id",
+        type: "int",
+        width: 90,
+        hidden: true,
+        readonly: true,
+        require: true,
+        align: "left",
+      },
+      {
+        field: "palletCode",
+        title: TEXT.palletCode,
+        type: "string",
+        width: 120,
+        align: "left",
+      },
+      {
+        field: "locationCode",
+        title: TEXT.locationCode,
+        type: "string",
+        width: 150,
+        align: "left",
+      },
+      {
+        field: "warehouseId",
+        title: TEXT.warehouse,
+        type: "select",
+        width: 100,
+        align: "left",
+        bind: { key: "warehouseEnum", data: [] },
+      },
+      {
+        field: "creater",
+        title: TEXT.creator,
+        type: "string",
+        width: 90,
+        align: "left",
+      },
+      {
+        field: "createDate",
+        title: TEXT.createDate,
+        type: "datetime",
+        width: 160,
+        align: "left",
+      },
+      {
+        field: "modifier",
+        title: TEXT.modifier,
+        type: "string",
+        width: 100,
+        align: "left",
+        hidden: true,
+      },
+      {
+        field: "modifyDate",
+        title: TEXT.modifyDate,
+        type: "datetime",
+        width: 160,
+        align: "left",
+        hidden: true,
+      },
+    ]);
+
+    const detail = ref({
+      cnName: "#detailCnName",
+      table: "",
+      columns: [],
+      sortName: "",
+    });
+
+    const detailState = reactive({
+      rowsMap: {},
+      loadingMap: {},
+      errorMap: {},
+    });
+
+    const stockStatusOptions = ref([]);
+
+    const detailColumns = [
+      { field: "materielName", title: TEXT.materielName, minWidth: 160 },
+      { field: "serialNumber", title: TEXT.serialNumber, minWidth: 160 },
+      { field: "stockQuantity", title: TEXT.stockQuantity, minWidth: 120 },
+      { field: "status", title: TEXT.status, minWidth: 120 },
+      { field: "inboundOrderRowNo", title: TEXT.inboundOrderRowNo, minWidth: 120 },
+    ];
+
+    const normalizeValue = (value) => {
+      return value === null || value === undefined || value === "" ? "--" : value;
+    };
+
+    const formatStatusText = (value) => {
+      const matched = stockStatusOptions.value.find((item) => `${item.key}` === `${value}`);
+      return matched ? matched.value || matched.label : normalizeValue(value);
+    };
+
+    const getDetailRows = (stockId) => {
+      return detailState.rowsMap[stockId] || [];
+    };
+
+    const loadDetailRows = async (row) => {
+      if (!row || !row.id || detailState.loadingMap[row.id]) {
+        return;
+      }
+      if (detailState.rowsMap[row.id]) {
+        return;
+      }
+
+      detailState.loadingMap[row.id] = true;
+      detailState.errorMap[row.id] = "";
+      try {
+        const result = await proxy.http.post("/api/StockInfoDetail/getPageData", {
+          page: 1,
+          rows: 200,
+          sort: "id",
+          order: "asc",
+          wheres: JSON.stringify([
+            {
+              name: "stockId",
+              value: String(row.id),
+              displayType: "int",
+            },
+          ]),
+        });
+        detailState.rowsMap[row.id] = (result && result.rows) || [];
+      } catch (error) {
+        detailState.rowsMap[row.id] = null;
+        detailState.errorMap[row.id] = error?.message || TEXT.detailLoadFailed;
+      } finally {
+        detailState.loadingMap[row.id] = false;
+      }
+    };
+
+    const loadStockStatusOptions = async () => {
+      try {
+        const result = await proxy.http.post("/api/Sys_Dictionary/GetVueDictionary", ["stockStatusEmun"]);
+        const matched = (result || []).find((item) => item.dicNo === "stockStatusEmun");
+        stockStatusOptions.value = matched ? matched.data || [] : [];
+      } catch (error) {
+        stockStatusOptions.value = [];
+      }
+    };
+
+    loadStockStatusOptions();
+
+    const renderStatus = (row) => {
+      if (detailState.loadingMap[row.id]) {
+        return h("div", { class: "stock-detail-status" }, TEXT.detailLoading);
+      }
+      if (detailState.errorMap[row.id]) {
+        return h(
+          "div",
+          { class: "stock-detail-status stock-detail-status--error" },
+          detailState.errorMap[row.id]
+        );
+      }
+      return null;
+    };
+
+    const renderDetailTable = (row) => {
+      const statusNode = renderStatus(row);
+      if (statusNode) {
+        return statusNode;
+      }
+
+      const rows = getDetailRows(row.id);
+      if (!rows.length) {
+        return h("div", { class: "stock-detail-status" }, TEXT.detailEmpty);
+      }
+
+      return h("div", { class: "stock-detail-table-wrapper" }, [
+        h("div", { class: "stock-detail-toolbar" }, [
+          h("div", { class: "stock-detail-toolbar__left" }, TEXT.detailName),
+          h("div", { class: "stock-detail-toolbar__right" }, [
+            h("span", { class: "stock-detail-count" }, `${rows.length} 鏉),
+          ]),
+        ]),
+        h(
+          ElTable,
+          {
+            data: rows,
+            border: true,
+            stripe: true,
+            size: "small",
+            class: "stock-detail-el-table",
+            maxHeight: 420,
+            emptyText: TEXT.detailEmpty,
+          },
+          () =>
+            detailColumns.map((column) =>
+              h(ElTableColumn, {
+                key: column.field,
+                prop: column.field,
+                label: column.title,
+                minWidth: column.minWidth,
+                showOverflowTooltip: true,
+                formatter: (detailRow) =>
+                  column.field === "status"
+                    ? formatStatusText(detailRow[column.field])
+                    : normalizeValue(detailRow[column.field]),
+              })
+            )
+        ),
       ]);
-      const searchFormFields = ref({
-        palletCode: "",
-        locationCode: "",
-      });
-      const searchFormOptions = ref([
-        [
-          { title: "鎵樼洏缂栧彿", field: "palletCode" },
-          { title: "璐т綅缂栧彿", field: "locationCode" },
-        ],
-      ]);
-      const columns = ref([
-        {
-          field: "id",
-          title: "Id",
-          type: "int",
-          width: 90,
-          hidden: true,
-          readonly: true,
-          require: true,
-          align: "left",
-        },
-        {
-          field: "palletCode",
-          title: "鎵樼洏缂栧彿",
-          type: "string",
-          width: 90,
-          align: "left",
-        },
-        {
-          field: "locationCode",
-          title: "璐т綅缂栧彿",
-          type: "string",
-          width: 150,
-          align: "left",
-        },
-        // {
-        //   field: "isFull",
-        //   title: "鏄惁婊$洏",
-        //   type: "string",
-        //   width: 150,
-        //   align: "left",
-        //   bind: { key: "yesno", data: [] },
-        // },
-         {
-          field: "warehouseId",
-          title: "浠撳簱",
-          type: "select",
-          width: 100,
-          align: "left",
-          bind: { key: "warehouseEnum", data: [] },
-        },
-        {
-          field: "creater",
-          title: "鍒涘缓浜�",
-          type: "string",
-          width: 90,
-          align: "left",
-        },
-        {
-          field: "createDate",
-          title: "鍒涘缓鏃堕棿",
-          type: "datetime",
-          width: 160,
-          align: "left",
-        },
-        {
-          field: "modifier",
-          title: "淇敼浜�",
-          type: "string",
-          width: 100,
-          align: "left",
-          hidden:true
-        },
-        {
-          field: "modifyDate",
-          title: "淇敼鏃堕棿",
-          type: "datetime",
-          width: 160,
-          align: "left",
-          hidden:true
-        },
-        {
-          field: "remark",
-          title: "澶囨敞",
-          type: "string",
-          width: 100,
-          align: "left",
-          hidden:true
-        },
-      ]);
-      const detail = ref({
-        cnName: "#detailCnName",
-        table: "",
-        columns: [],
-        sortName: "",
-      });
-      return {
-        table,
-        extend,
-        editFormFields,
-        editFormOptions,
-        searchFormFields,
-        searchFormOptions,
-        columns,
-        detail,
-      };
-    },
-  });
-  </script>
-    
\ No newline at end of file
+    };
+
+    const tableExpand = ref({
+      width: 55,
+      onChange(row, expandedRows) {
+        const isExpanded = expandedRows.some((item) => item.id === row.id);
+        if (isExpanded) {
+          loadDetailRows(row);
+        }
+      },
+      render(render, { row }) {
+        return render("div", { class: "stock-detail-panel" }, [
+          render("div", { class: "stock-detail-header" }, [
+            render("div", { class: "stock-detail-header__main" }, [
+              render("div", { class: "stock-detail-title" }, TEXT.detailName),
+              render(
+                "div",
+                { class: "stock-detail-subtitle" },
+                `${TEXT.expandPrefix}${normalizeValue(row.palletCode)}${TEXT.expandMiddle}${TEXT.expandLocation}${normalizeValue(row.locationCode)}`
+              ),
+            ]),
+            // render("div", { class: "stock-detail-tags" }, [
+            //   render("span", { class: "stock-detail-tag" }, normalizeValue(row.palletCode)),
+            //   render(
+            //     "span",
+            //     { class: "stock-detail-tag stock-detail-tag--muted" },
+            //     normalizeValue(row.locationCode)
+            //   ),
+            // ]),
+          ]),
+          renderDetailTable(row),
+        ]);
+      },
+    });
+
+    return {
+      table,
+      extend,
+      editFormFields,
+      editFormOptions,
+      searchFormFields,
+      searchFormOptions,
+      columns,
+      detail,
+      tableExpand,
+    };
+  },
+});
+</script>
+
+<style scoped>
+.stock-detail-panel {
+  margin: 4px 8px 12px;
+  padding: 14px 16px 16px;
+  background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
+  border: 1px solid #e8edf3;
+  border-radius: 10px;
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
+}
+
+.stock-detail-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 12px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #edf1f5;
+}
+
+.stock-detail-header__main {
+  min-width: 0;
+}
+
+.stock-detail-title {
+  margin-bottom: 4px;
+  font-size: 15px;
+  font-weight: 700;
+  color: #303133;
+}
+
+.stock-detail-subtitle {
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.6;
+}
+
+.stock-detail-tags {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: flex-end;
+  gap: 8px;
+}
+
+.stock-detail-tag {
+  display: inline-flex;
+  align-items: center;
+  height: 28px;
+  padding: 0 10px;
+  color: #1f5eff;
+  background: #edf4ff;
+  border: 1px solid #d8e6ff;
+  border-radius: 999px;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.stock-detail-tag--muted {
+  color: #4e5969;
+  background: #f4f6f8;
+  border-color: #e5e9ef;
+}
+
+.stock-detail-status {
+  padding: 14px 12px;
+  color: #606266;
+  background: #f8fafc;
+  border: 1px dashed #d9e2ec;
+  border-radius: 8px;
+}
+
+.stock-detail-status--error {
+  color: #f56c6c;
+  background: #fef0f0;
+  border-color: #fde2e2;
+}
+
+.stock-detail-table-wrapper {
+  overflow-x: auto;
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  background: #fff;
+}
+
+.stock-detail-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  padding: 12px 14px;
+  background: #f8fafc;
+  border-bottom: 1px solid #edf1f5;
+}
+
+.stock-detail-toolbar__left {
+  font-size: 13px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.stock-detail-count {
+  display: inline-flex;
+  align-items: center;
+  height: 24px;
+  padding: 0 10px;
+  color: #606266;
+  background: #fff;
+  border: 1px solid #e5e9ef;
+  border-radius: 999px;
+  font-size: 12px;
+}
+
+:deep(.stock-detail-el-table) {
+  border-top: none;
+}
+
+:deep(.stock-detail-el-table .el-table__inner-wrapper::before) {
+  display: none;
+}
+
+:deep(.stock-detail-el-table th.el-table__cell) {
+  background: #f5f7fa;
+  color: #303133;
+  font-weight: 600;
+}
+
+:deep(.stock-detail-el-table td.el-table__cell) {
+  color: #606266;
+}
+
+:deep(.stock-detail-el-table .el-table__body tr:hover > td.el-table__cell) {
+  background: #f0f7ff;
+}
+</style>
diff --git "a/\351\241\271\347\233\256\350\265\204\346\226\231/\350\256\276\345\244\207\345\215\217\350\256\256/\346\213\230\346\235\237\346\234\272\345\257\271\346\216\245\345\215\217\350\256\256/\346\213\230\346\235\237\346\234\272\345\257\271\346\216\245\345\215\217\350\256\256.xlsx" "b/\351\241\271\347\233\256\350\265\204\346\226\231/\350\256\276\345\244\207\345\215\217\350\256\256/\346\213\230\346\235\237\346\234\272\345\257\271\346\216\245\345\215\217\350\256\256/\346\213\230\346\235\237\346\234\272\345\257\271\346\216\245\345\215\217\350\256\256.xlsx"
new file mode 100644
index 0000000..dc2b088
--- /dev/null
+++ "b/\351\241\271\347\233\256\350\265\204\346\226\231/\350\256\276\345\244\207\345\215\217\350\256\256/\346\213\230\346\235\237\346\234\272\345\257\271\346\216\245\345\215\217\350\256\256/\346\213\230\346\235\237\346\234\272\345\257\271\346\216\245\345\215\217\350\256\256.xlsx"
Binary files differ

--
Gitblit v1.9.3