云
9 小时以前 2c3df98c784bd825b6c2faab508e20fe4f2c3966
yudao-server/src/main/resources/static/trace.html
@@ -352,6 +352,7 @@
  // 因此统一由后端下发(/public/mes/trace/config),禁止在此硬编码。
  var COMPANY_NAME = '';
  var TRACE_URL = '';
  var TRACE_LOCATION_URL = '';
  var IS_IMPORTED = false;
  // 拉取溯源页配置;失败时不阻断查询(查询结果本身也带生产经营者与追溯网址)
@@ -362,6 +363,7 @@
        if (res && res.code === 0 && res.data) {
          COMPANY_NAME = res.data.operatorName || '';
          TRACE_URL = res.data.traceUrl || '';
          TRACE_LOCATION_URL = res.data.traceLocationUrl || '';
          IS_IMPORTED = !!res.data.imported;
        }
      })
@@ -372,14 +374,6 @@
  var CODE_INPUT = document.getElementById('codeInput');
  var SEARCH_BTN = document.getElementById('searchBtn');
  var FLOW_DIRECTION = { 1: ['入库', 'green'], 2: ['出库', 'blue'] };
  var FLOW_BIZ_TYPE = {
    110: '采购入库', 111: '采购入库取消', 112: '调拨出库', 113: '调拨入库',
    114: '其他出库', 115: '其他入库', 116: '生产领料', 117: '生产退料',
    118: '生产入库', 119: '销售出库', 120: '销售退货入库', 121: '外协发料',
    122: '外协入库', 123: '生产消耗', 124: '产品产出', 125: '供应商退货出库',
    126: '库存调整入库', 127: '库存调整出库'
  };
  var SOURCE_TYPE = { BAG: '袋码', PALLET: '托盘码', BATCH: '批次码' };
  // ========== 工具函数 ==========
@@ -411,12 +405,6 @@
  // 表格窄列(数量 / 日期)不换行,避免数字被拆行
  function nw(html) {
    return '<span style="white-space:nowrap">' + html + '</span>';
  }
  function statusBadge(map, key) {
    var item = map[key];
    if (!item) return '<span class="badge gray">' + (key === null || key === undefined ? '' : esc(key)) + '</span>';
    return '<span class="badge ' + item[1] + '">' + esc(item[0]) + '</span>';
  }
  function kv(k, v, dim) {
@@ -499,14 +487,89 @@
      return;
    }
    var html = '';
    if (new URLSearchParams(location.search).get('view') === 'location') {
      html += locationSections(d);
      html += sec('产品追溯标识', null, tracePanel(d), null);
      STATE.innerHTML = html;
      window.scrollTo({ top: 0, behavior: 'smooth' });
      return;
    }
    html += sec('防伪查询', null, fakePanel(d), null);
    html += sec('追溯查询', null, tracePanel(d), null);
    // 生产明细折叠区:对外展示种子流向、加工流转,生产批次信息不展示。
    // 生产明细折叠区:对外展示加工流转,生产批次信息不展示。
    // 质检/人员/托盘/袋码/采购来源等内部明细不对外展示,
    // 如需一并展示,改为 detailSections(d, true) 即可。
    html += detailSections(d, false);
    STATE.innerHTML = html || '<div class="empty-tip">暂无追溯数据</div>';
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }
  // 定位授权入口沿用本页的品牌背景与区块样式,按生产、仓储、物流组织已有真实数据。
  function groupOutboundOrders(rows, codeOf, dateField) {
    var grouped = [];
    var byCode = new Map();
    rows.forEach(function (item) {
      var code = String(codeOf(item) || '').trim();
      var quantity = Number(item.quantity);
      var validQuantity = item.quantity !== null && item.quantity !== undefined && Number.isFinite(quantity);
      var existing = code ? byCode.get(code) : null;
      if (!existing) {
        var order = Object.assign({}, item, { quantity: validQuantity ? Math.abs(quantity) : null });
        grouped.push(order);
        if (code) byCode.set(code, order);
      } else {
        if (validQuantity) existing.quantity = (existing.quantity || 0) + Math.abs(quantity);
        if (!existing.contactName && item.contactName) existing.contactName = item.contactName;
        if (item[dateField] && (!existing[dateField] || item[dateField] > existing[dateField])) {
          existing[dateField] = item[dateField];
        }
      }
    });
    return grouped;
  }
  function locationSections(d) {
    var production = kv('产品名称', text(d.itemName)) +
      kv('规格', text(d.itemSpecification || d.itemUnitMeasureName)) +
      kv('加工批次', text(d.batchCode || d.tempCode));
    if (d.produceDate) production = kv('生产日期', fmtDay(d.produceDate)) + production;
    var html = sec('生产信息', null, production, null);
    var salesFlows = groupOutboundOrders((d.seedFlows || []).filter(function (item) { return item.bizType === 119; }),
      function (item) { return item.bizCode; }, 'transactionTime');
    var salesTargets = groupOutboundOrders(d.salesTargets || [],
      function (item) { return item.salesCode || item.salesOrderCode; }, 'salesDate');
    var flow = (d.seedFlows || []).find(function (item) { return item.warehouseName; });
    var storage = '';
    if (d.inboundTime) storage += kv('入库日期', fmtDay(d.inboundTime));
    var outboundDates = salesFlows.map(function (item) { return item.transactionTime; })
      .concat(salesTargets.map(function (item) { return item.salesDate; })).filter(Boolean).sort();
    if (outboundDates.length) storage += kv('最近出库日期', fmtDay(outboundDates[outboundDates.length - 1]));
    if (flow) storage += kv('仓库', text(flow.warehouseName));
    html += sec('仓储信息', null, storage || '<div class="empty-tip">暂无仓储信息</div>', null);
    var logistics = salesFlows.map(function (item) {
      var route = [item.warehouseName, item.clientName].filter(Boolean).join(' → ');
      var description = '该批次' + (item.transactionTime ? '于 ' + String(item.transactionTime).slice(0, 10) + ' ' : '') +
        (item.warehouseName ? '从' + item.warehouseName + '出库' : '完成销售出库') +
        (item.clientName ? ',发往' + item.clientName : '') + '。';
      return li('<span class="li-ttl">' + text(route || '销售出库') + '</span>', fmtDay(item.transactionTime),
        kv('出库单号', text(item.bizCode)) + kv('数量', fmtNum(item.quantity)) +
        kv('流转说明', text(description)) +
        (item.contactName ? kv('联系人', text(item.contactName)) : ''));
    }).join('');
    if (!logistics && !isEmptyList(salesTargets)) {
      logistics = salesTargets.map(function (item) {
        var description = '该批次' + (item.salesDate ? '于 ' + String(item.salesDate).slice(0, 10) + ' ' : '') +
          '完成销售出库' + (item.clientName ? ',去向' + item.clientName : '') + '。';
        return li('<span class="li-ttl">' + text(item.clientName || '销售去向') + '</span>', fmtDay(item.salesDate),
          kv('出库单号', text(item.salesCode || item.salesOrderCode)) + kv('数量', fmtNum(item.quantity)) +
          kv('流转说明', text(description)) +
          (item.contactName ? kv('联系人', text(item.contactName)) : ''));
      }).join('');
    }
    html += sec('物流信息', null, logistics || '<div class="empty-tip">暂无物流信息</div>', null);
    return html;
  }
  function stripUrl(v) {
@@ -567,6 +630,8 @@
    // 生产经营者与追溯网址优先取查询结果(与标签印刷内容同源),退回页面配置
    var operatorName = d.operatorName || COMPANY_NAME;
    var traceUrl = d.traceUrl || TRACE_URL;
    var locationUrl = new URL(TRACE_LOCATION_URL || location.pathname.replace(/trace\.html$/, 'trace-location.html'), location.href);
    locationUrl.searchParams.set('code', code || '');
    var operatorLabel = IS_IMPORTED ? '进口商名称' : '生产经营者';
    // 只有袋码才是「最小销售单元」,其码值才称「单元识别代码」;
    // 托盘码/批次码不属于最小销售单元标识,按实际类型展示,避免误导消费者
@@ -583,7 +648,7 @@
      kv(operatorLabel, text(operatorName)) +
      kv(codeLabel, text(code || '-')) +
      kv('追溯网址', traceUrl
        ? '<a class="link" href="' + esc(traceUrl) + '">' + esc(traceUrl) + '</a>'
        ? '<a class="link" target="_top" href="' + esc(locationUrl.href) + '">' + esc(traceUrl) + '</a>'
        : '-') +
      kv('计量单位', text(d.itemUnitMeasureName)) +
      kv('追溯类型', esc(SOURCE_TYPE[d.sourceType] || '追溯码')) +
@@ -664,27 +729,6 @@
      html += sec('采购来源', null, table(['采购入库单', '供应商', '数量', '日期'], psRows), { collapsible: true, open: false });
    }
    // 种子流向信息(仅销售出库 bizType=119)
    var salesFlows = (d.seedFlows || []).filter(function (f) { return f.bizType === 119; });
    if (!isEmptyList(salesFlows)) {
      var sfBody = salesFlows.map(function (f) {
        var qty = (f.quantity === null || f.quantity === undefined) ? '-' : fmtNum(Math.abs(Number(f.quantity)));
        var wh = [f.warehouseName, f.locationName].filter(Boolean).join(' / ') || '-';
        return li(statusBadge(FLOW_DIRECTION, f.type) + '<span class="li-ttl">' + esc(FLOW_BIZ_TYPE[f.bizType] || '') + '</span>',
          fmtDate(f.transactionTime),
          kv('来源单号', text(f.bizCode)) +
          kv('客户', text(f.clientName)) +
          kv('数量', qty) +
          kv('仓库/库区', text(wh)));
      }).join('');
      html += sec('种子流向信息', null, '<div class="scroll-list">' + sfBody + '</div>', { collapsible: true, open: false });
    } else if (!isEmptyList(d.salesTargets)) {
      var stRows = d.salesTargets.map(function (s) {
        return [text(s.salesCode || s.salesOrderCode || ''), text(s.clientName), nw(fmtNum(s.quantity)), nw(fmtDay(s.salesDate))];
      });
      html += sec('种子流向信息', null, table(['销售出库单', '客户', '数量', '日期'], stRows), { collapsible: true, open: false });
    }
    // 加工流转
    if (!isEmptyList(d.processTraces)) {
      var ptBody = d.processTraces.map(function (t) {
@@ -724,15 +768,15 @@
  })();
  // 先拉取页面配置(生产经营者名称、追溯网址),再执行查询;查询结果中同样带有这两项,互为兜底
  loadTraceConfig();
  var initialCode = new URLSearchParams(location.search).get('code') || '';
  CODE_INPUT.value = initialCode;
  if (initialCode) {
    query(initialCode);
  } else {
    renderState('<div class="empty-tip">扫码或输入追溯码,即可查询产品全程溯源信息</div>');
  }
  loadTraceConfig().then(function () {
    if (initialCode) {
      query(initialCode);
    } else {
      renderState('<div class="empty-tip">扫码或输入追溯码,即可查询产品全程溯源信息</div>');
    }
  });
})();
</script>
</body>