From b29d86d9ed34fd890eb14cb7a75f6ae4f7148865 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 01 九月 2026 13:58:03 +0800
Subject: [PATCH] feat(mes): 新增防伪防窜货功能模块

---
 yudao-server/src/main/resources/static/trace.html |  113 +++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 99 insertions(+), 14 deletions(-)

diff --git a/yudao-server/src/main/resources/static/trace.html b/yudao-server/src/main/resources/static/trace.html
index be5c7f5..afafce6 100644
--- a/yudao-server/src/main/resources/static/trace.html
+++ b/yudao-server/src/main/resources/static/trace.html
@@ -141,6 +141,34 @@
   .scroll-list::-webkit-scrollbar { width: 4px; }
   .scroll-list::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; }
 
+  /* 姝e搧楠岃瘉鍗★紙椤堕儴锛屾壂鐮佷紭鍏堝睍绀猴級 */
+  .verify-card {
+    border: 1px solid var(--primary-light);
+    background: linear-gradient(180deg, #eef8ee, #fff);
+  }
+  .verify-card .card-bd { padding: 14px 16px; }
+  .verify-title {
+    display: flex; align-items: center; gap: 8px;
+    font-size: 16px; font-weight: 700; color: var(--primary);
+    margin-bottom: 4px;
+  }
+  .verify-title::before { content: ""; width: 5px; height: 16px; border-radius: 2px; background: var(--primary); }
+  .verify-sub { font-size: 12px; color: var(--text-sub); margin-bottom: 10px; }
+  .verify-badge { margin-bottom: 10px; }
+  .verify-stats { margin-top: 10px; border-top: 1px dashed var(--border); padding-top: 4px; }
+
+  /* 鍙姌鍙犲崱鐗� */
+  .card.collapsible .card-hd { cursor: pointer; position: relative; padding-right: 30px; }
+  .card.collapsible .card-hd::after {
+    content: "鈻�"; position: absolute; right: 12px; top: 50%;
+    transform: translateY(-50%); color: var(--text-sub); font-size: 12px;
+    transition: transform .2s;
+  }
+  .card.collapsible:not(.open) .card-hd::after { transform: translateY(-50%) rotate(-90deg); }
+  .card.collapsible .card-hd .count { margin-right: 16px; }
+  .card.collapsible .card-bd { display: none; }
+  .card.collapsible.open .card-bd { display: block; }
+
   footer {
     max-width: 480px; margin: 0 auto;
     padding: 20px 16px 28px; text-align: center;
@@ -207,11 +235,17 @@
     return '<span class="badge ' + item[1] + '">' + esc(item[0]) + '</span>';
   }
 
-  function card(title, countHtml, bodyHtml) {
-    return '<div class="card">' +
-      '<div class="card-hd"><span class="bar"></span>' + esc(title) +
+  function card(title, countHtml, bodyHtml, opts) {
+    opts = opts || {};
+    var collapsible = !!opts.collapsible;
+    var open = collapsible && !!opts.open;
+    var hd = '<div class="card-hd"' +
+      (collapsible ? ' onclick="this.parentNode.classList.toggle(\'open\')"' : '') +
+      '><span class="bar"></span>' + esc(title) +
       (countHtml ? '<span class="count">' + countHtml + '</span>' : '') +
-      '</div>' + '<div class="card-bd">' + bodyHtml + '</div>' + '</div>';
+      '</div>';
+    return '<div class="card' + (collapsible ? ' collapsible' : '') + (open ? ' open' : '') + '">' +
+      hd + '<div class="card-bd">' + bodyHtml + '</div>' + '</div>';
   }
 
   function kv(k, v, dim) {
@@ -257,6 +291,7 @@
       return;
     }
     var html = '';
+    html += buildVerifyCard(d);
     html += buildItemCard(d);
     html += buildBatchCard(d);
     html += buildQualityCard(d);
@@ -269,13 +304,59 @@
     window.scrollTo({ top: 0, behavior: 'smooth' });
   }
 
+  // 鍖哄潡0锛氭鍝侀獙璇侊紙椤堕儴锛屾壂鐮佷紭鍏堝睍绀洪獙璇佺粨璁轰笌鏌ヨ缁熻锛�
+  function stripUrl(v) {
+    if (!v) return v;
+    if (/^https?:\/\//i.test(v)) {
+      var m = v.match(/[?&]code=([^&]*)/);
+      return m ? decodeURIComponent(m[1]) : '';
+    }
+    return v;
+  }
+
+  function buildVerifyCard(d) {
+    var typeLabel = { BAG: '琚嬬爜', PALLET: '鎵樼洏鐮�', BATCH: '鎵规鐮�' }[d.sourceType] || '杩芥函鐮�';
+    var code = stripUrl(d.requestCode) || d.batchCode || d.tempCode || '-';
+    var bagNo = '-';
+    if (d.sourceType === 'BAG' && d.bags) {
+      for (var i = 0; i < d.bags.length; i++) {
+        if (d.bags[i].code === code) {
+          bagNo = d.bags[i].bagNo;
+          break;
+        }
+      }
+    }
+    var statsHtml = '<div class="verify-stats">';
+    if (d.antiFake && d.antiFake.scanCount > 0) {
+      statsHtml += kv('绱鏌ヨ', esc(d.antiFake.scanCount) + ' 娆�') +
+        kv('棣栨鏌ヨ', fmtDate(d.antiFake.firstScanTime)) +
+        kv('鏈�杩戞煡璇�', fmtDate(d.antiFake.lastScanTime));
+    } else {
+      statsHtml += kv('绱鏌ヨ', '鏆傛棤鏌ヨ璁板綍');
+    }
+    statsHtml += '</div>';
+    return '<div class="card verify-card">' +
+      '<div class="card-bd">' +
+      '<div class="verify-title">姝e搧楠岃瘉</div>' +
+      '<div class="verify-badge"><span class="badge-big green">鉁� 姝e搧楠岃瘉閫氳繃</span></div>' +
+      '<div class="verify-sub">璇ヨ拷婧爜涓烘瑙勬笭閬撲骇鍝侊紝婧簮淇℃伅鐪熷疄鏈夋晥</div>' +
+      kv('杩芥函绫诲瀷', typeLabel) +
+      kv(typeLabel, text(code)) +
+      (d.sourceType === 'BAG' ? kv('琚嬪彿', bagNo) : '') +
+      kv('鎵规鍙�', text(d.batchCode)) +
+      kv('鍝佺', text(d.itemName)) +
+      statsHtml +
+      '</div></div>';
+  }
+
   // 鍖哄潡1锛氬搧绉嶄俊鎭�
   function buildItemCard(d) {
     if (!d.itemCode && !d.itemName && !d.itemSpecification) return '';
     return card('鍝佺淇℃伅', null,
       kv('鍝佺鍚嶇О', text(d.itemName)) +
       kv('瑙勬牸', text(d.itemSpecification)) +
-      kv('鍝佺缂栫爜', text(d.itemCode))
+      kv('鍝佺缂栫爜', text(d.itemCode)),
+      { collapsible: true, open: true }
     );
   }
 
@@ -293,7 +374,8 @@
       kv('宸茬敓鎴愯鐮�', fmtNum(d.bagQuantity)) +
       kv('宸茬敓鎴愭墭鐩�', fmtNum(d.palletQuantity)) +
       kv('鍏ュ簱鏃堕棿', fmtDate(d.inboundTime)) +
-      (d.remark ? kv('澶囨敞', text(d.remark)) : '')
+      (d.remark ? kv('澶囨敞', text(d.remark)) : ''),
+      { collapsible: true, open: true }
     );
   }
 
@@ -325,7 +407,8 @@
       kv('妫�楠屽憳', text(q.inspectorName)) +
       kv('妫�楠屾椂闂�', fmtDate(q.inspectedAt)) +
       (q.reason ? kv('涓嶅悎鏍煎師鍥�', text(q.reason)) : '') +
-      (rows ? '<div style="margin-top:8px">' + rows + '</div>' : '')
+      (rows ? '<div style="margin-top:8px">' + rows + '</div>' : ''),
+      { collapsible: true, open: false }
     );
   }
 
@@ -341,7 +424,7 @@
         esc(type) + '锛�' + esc(name) + '</span>';
     });
     html += '</div>';
-    return card('鐢熶骇浜哄憳', null, html);
+    return card('鐢熶骇浜哄憳', null, html, { collapsible: true, open: false });
   }
 
   // 鍖哄潡5锛氭壂鐮佸叆搴撲簨浠�
@@ -353,7 +436,8 @@
       kv('鍏ュ簱鏁伴噺', fmtNum(e.quantity)) +
       kv('璁惧缂栫爜', text(e.deviceCode)) +
       kv('浜х嚎', text(e.lineCode)) +
-      kv('鍏ュ簱鏃堕棿', fmtDate(e.eventTime))
+      kv('鍏ュ簱鏃堕棿', fmtDate(e.eventTime)),
+      { collapsible: true, open: false }
     );
   }
 
@@ -367,7 +451,7 @@
         fmtNum(p.bagCount) + ' / ' + fmtNum(p.capacity) + '</td></tr>';
     });
     rows += '</tbody></table>';
-    return card('鎵樼洏淇℃伅', countHtml, rows);
+    return card('鎵樼洏淇℃伅', countHtml, rows, { collapsible: true, open: false });
   }
 
   // 鍖哄潡7锛氳鐮侊紙涓�琚嬩竴鐮侊級
@@ -381,7 +465,8 @@
     });
     rows += '</tbody></table>';
     return card('琚嬬爜淇℃伅', countHtml,
-      '<div class="scroll-list">' + rows + '</div>'
+      '<div class="scroll-list">' + rows + '</div>',
+      { collapsible: true, open: false }
     );
   }
 
@@ -394,7 +479,7 @@
         rows += '<tr><td>' + text(s.receiptCode) + '</td><td>' + text(s.vendorName) + '</td><td>' + fmtNum(s.receivedQuantity) + '</td><td>' + fmtDate(s.receiptDate) + '</td></tr>';
       });
       rows += '</tbody></table>';
-      html += card('閲囪喘鏉ユ簮', null, rows);
+      html += card('閲囪喘鏉ユ簮', null, rows, { collapsible: true, open: false });
     }
     if (!isEmptyList(d.salesTargets)) {
       var rows2 = '<table class="mini-table"><thead><tr><th>閿�鍞嚭搴撳崟</th><th>瀹㈡埛</th><th>鏁伴噺</th><th>鏃ユ湡</th></tr></thead><tbody>';
@@ -402,7 +487,7 @@
         rows2 += '<tr><td>' + text(s.outCode || s.salesOrderCode || '') + '</td><td>' + text(s.customerName) + '</td><td>' + fmtNum(s.quantity) + '</td><td>' + fmtDate(s.outTime) + '</td></tr>';
       });
       rows2 += '</tbody></table>';
-      html += card('閿�鍞幓鍚�', null, rows2);
+      html += card('閿�鍞幓鍚�', null, rows2, { collapsible: true, open: false });
     }
     if (!isEmptyList(d.processTraces)) {
       var rows3 = '<table class="mini-table"><thead><tr><th>鑺傜偣</th><th>璇存槑</th><th>鏃堕棿</th></tr></thead><tbody>';
@@ -410,7 +495,7 @@
         rows3 += '<tr><td>' + text(t.nodeName) + '</td><td>' + text(t.description) + '</td><td>' + fmtDate(t.processTime) + '</td></tr>';
       });
       rows3 += '</tbody></table>';
-      html += card('鍔犲伐娴佽浆', null, rows3);
+      html += card('鍔犲伐娴佽浆', null, rows3, { collapsible: true, open: false });
     }
     return html;
   }

--
Gitblit v1.9.3