11 小时以前 b29d86d9ed34fd890eb14cb7a75f6ae4f7148865
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; }
  /* 正品验证卡(顶部,扫码优先展示) */
  .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">正品验证</div>' +
      '<div class="verify-badge"><span class="badge-big green">✅ 正品验证通过</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;
  }