From ee7ac8df8bce38add1f75baf876b49de94b9e00a Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 16 十月 2026 17:58:33 +0800
Subject: [PATCH] feat(mes): 赋码管理绑定托盘支持未录入批次的袋码

---
 src/views/mes/pro/components/qr-trace-modal.vue |  230 +++++++++++++++++++++++++++-----------------------------
 1 files changed, 111 insertions(+), 119 deletions(-)

diff --git a/src/views/mes/pro/components/qr-trace-modal.vue b/src/views/mes/pro/components/qr-trace-modal.vue
index 710a2e7..f701da6 100644
--- a/src/views/mes/pro/components/qr-trace-modal.vue
+++ b/src/views/mes/pro/components/qr-trace-modal.vue
@@ -3,7 +3,7 @@
   import type { MesProBatchApi } from "#/api/mes/pro/batch";
   import type { MesProPalletApi } from "#/api/mes/pro/pallet";
 
-  import { ref } from "vue";
+  import { computed, ref } from "vue";
 
   import { Barcode, useVbenModal } from "@vben/common-ui";
   import { DICT_TYPE } from "@vben/constants";
@@ -11,9 +11,10 @@
 
   import { Button, Empty, Input, message, Spin, Tag } from "ant-design-vue";
 
-  import { getBagCodeByCode } from "#/api/mes/pro/bagCode";
+  import { getBagCodeByCode, resolveTraceType } from "#/api/mes/pro/bagCode";
   import { getBatchByCode } from "#/api/mes/pro/batch";
   import { getPalletByCode } from "#/api/mes/pro/pallet";
+  import { getQrConfig } from "#/api/mes/trace/config";
   import { useDescription } from "#/components/description";
 
   defineOptions({ name: "MesProQrTraceModal" });
@@ -35,16 +36,41 @@
 
   const traceTypeLabel = ref("");
 
-  /** 璇嗗埆浜岀淮鐮佸唴瀹圭被鍨嬶細鎵规鍙� / 鎵樼洏鐮� / 琚嬬爜 */
-  function resolveCodeType(raw: string): TraceType | undefined {
-    const value = raw.trim().toUpperCase();
-    if (/^.*-P\d{2}$/.test(value)) {
-      return "pallet";
+  /** 婧簮 H5 椤甸潰鍩虹鍦板潃锛堜細璇濆唴鍙媺鍙栦竴娆★級 */
+  const traceBaseUrl = ref<string>("");
+
+  async function ensureTraceBaseUrl() {
+    if (traceBaseUrl.value) return;
+    try {
+      const res = await getQrConfig();
+      traceBaseUrl.value = res.traceBaseUrl || "";
+    } catch {
+      traceBaseUrl.value = "";
     }
-    if (/^.*-\d{4}$/.test(value)) {
-      return "bag";
+  }
+
+  /** 浜岀淮鐮佸睍绀哄唴瀹癸細閰嶇疆浜嗘函婧愬湴鍧�鍒欐嫾 H5 URL锛屽惁鍒欏洖閫�绾爜鍊硷紙涓庡悗绔� buildQrCodeContent 涓�鑷达級 */
+  const qrContent = computed(() => {
+    if (!currentCode.value) return "";
+    const code = bagTrace.value?.code || currentCode.value;
+    return traceBaseUrl.value
+      ? `${traceBaseUrl.value}?code=${encodeURIComponent(code)}`
+      : code;
+  });
+
+  /** 浠庡畬鏁� H5 閾炬帴涓彁鍙栬拷婧爜锛堝吋瀹规壂鐮佹灙鎵弿鎵撳嵃鏍囩閾炬帴锛� */
+  function extractCodeFromUrl(raw: string): string {
+    const trimmed = raw.trim();
+    if (/^https?:\/\//i.test(trimmed)) {
+      try {
+        const url = new URL(trimmed);
+        const code = url.searchParams.get("code");
+        if (code) return code;
+      } catch {
+        // 瑙f瀽澶辫触鎸夊師濮嬭緭鍏ュ鐞�
+      }
     }
-    return "batch";
+    return trimmed;
   }
 
   function resetState() {
@@ -60,18 +86,14 @@
 
   /** 鎵爜/杈撳叆浜岀淮鐮佸唴瀹规煡璇㈣拷婧俊鎭� */
   async function handleSearch(raw?: string) {
-    const value = (raw ?? codeInput.value)?.trim().toUpperCase();
+    const input = (raw ?? codeInput.value)?.trim() ?? "";
+    const value = extractCodeFromUrl(input).toUpperCase();
     if (!value) {
       message.warning("璇锋壂鎻忔垨杈撳叆浜岀淮鐮佸唴瀹�");
       return;
     }
     if (value.length > MAX_CODE_LENGTH) {
       message.warning("浜岀淮鐮佸唴瀹硅繃闀匡紝璇锋鏌ユ槸鍚︽壂鎻忔纭�");
-      return;
-    }
-    const type = resolveCodeType(value);
-    if (!type) {
-      message.warning("鏃犳硶璇嗗埆浜岀淮鐮佺被鍨�");
       return;
     }
     loading.value = true;
@@ -84,15 +106,24 @@
     palletTrace.value = undefined;
     batchTrace.value = undefined;
     try {
-      if (type === "batch") {
-        batchTrace.value = await getBatchByCode(value);
-        traceTypeLabel.value = "鎵规鍙�";
+      // 缂栫爜宸茬粺涓�涓恒��16 浣嶇函鏁板瓧銆嶏紝鏃犳硶鍐嶆寜鍓嶇紑鍒ゆ柇绫诲瀷锛屾敼鐢卞悗绔寜缂栫爜瑙f瀽绫诲瀷
+      const type = await resolveTraceType(value);
+      if (type === "bag") {
+        bagTrace.value = await getBagCodeByCode(value);
+        currentCode.value = bagTrace.value.code || value;
+        traceTypeLabel.value = "琚嬬爜";
       } else if (type === "pallet") {
         palletTrace.value = await getPalletByCode(value);
+        currentCode.value = palletTrace.value.code || palletTrace.value.tempCode || value;
         traceTypeLabel.value = "鎵樼洏鐮�";
+      } else if (type === "batch") {
+        batchTrace.value = await getBatchByCode(value);
+        currentCode.value = batchTrace.value.code || batchTrace.value.tempCode || value;
+        traceTypeLabel.value = "鎵规鍙�";
       } else {
-        bagTrace.value = await getBagCodeByCode(value);
-        traceTypeLabel.value = "琚嬬爜";
+        searchError.value = true;
+        currentCode.value = undefined;
+        return;
       }
       traceType.value = type;
     } catch {
@@ -104,58 +135,6 @@
     }
   }
 
-  /** 褰撳墠杩芥函鏁版嵁鐨勫叧閿俊鎭紙涓嬭浇/鎵撳嵃鍥剧墖鍙犲姞鏂囧瓧鐢級 */
-  function buildLabelText(): { label: string; value: string }[] {
-    if (traceType.value === "bag" && bagTrace.value) {
-      const d = bagTrace.value;
-      return [
-        { label: "琚嬬爜", value: d.code || "" },
-        { label: "琚嬪簭鍙�", value: d.bagNo ? String(d.bagNo) : "" },
-        { label: "鎵规鍙�", value: d.batchCode || "" },
-        { label: "鍝佺", value: d.itemName || "" },
-        { label: "瑙勬牸", value: d.itemSpecification || "" },
-        {
-          label: "浜х嚎",
-          value: [d.lineCode, d.lineName].filter(Boolean).join(" / "),
-        },
-        { label: "鐢熶骇鏃ユ湡", value: d.produceDate || "" },
-        { label: "璐d换浜�", value: d.ownerUserName || "" },
-      ];
-    }
-    if (traceType.value === "pallet" && palletTrace.value) {
-      const d = palletTrace.value;
-      return [
-        { label: "鎵樼洏鐮�", value: d.code || "" },
-        { label: "鎵樼洏搴忓彿", value: d.palletNo ? String(d.palletNo) : "" },
-        { label: "鎵规鍙�", value: d.batchCode || "" },
-        { label: "鍝佺", value: d.itemName || "" },
-        { label: "瑙勬牸", value: d.itemSpecification || "" },
-        {
-          label: "浜х嚎",
-          value: [d.lineCode, d.lineName].filter(Boolean).join(" / "),
-        },
-        { label: "鐢熶骇鏃ユ湡", value: d.produceDate || "" },
-        { label: "璐d换浜�", value: d.ownerUserName || "" },
-      ];
-    }
-    if (batchTrace.value) {
-      const d = batchTrace.value;
-      return [
-        { label: "鎵规鍙�", value: d.code || "" },
-        { label: "鍝佺", value: d.itemName || "" },
-        { label: "瑙勬牸", value: d.itemSpecification || "" },
-        {
-          label: "浜х嚎",
-          value: [d.lineCode, d.lineName].filter(Boolean).join(" / "),
-        },
-        { label: "鐢熶骇鏃ユ湡", value: d.produceDate || "" },
-        { label: "璁″垝鏁伴噺", value: d.planQuantity ? String(d.planQuantity) : "" },
-        { label: "璐d换浜�", value: d.ownerUserName || "" },
-      ];
-    }
-    return [];
-  }
-
   function escapeHtml(value: string) {
     return value
       .replaceAll("&", "&amp;")
@@ -163,14 +142,14 @@
       .replaceAll(">", "&gt;");
   }
 
-  /** 涓嬭浇浜岀淮鐮� PNG锛堜簩缁寸爜 + 鍏抽敭淇℃伅鏂囧瓧鍚堟垚锛� */
+  /** 涓嬭浇浜岀淮鐮� PNG锛堜簩缁寸爜鍦ㄤ笂锛岀爜鍊煎湪涓嬶級 */
   function handleDownload() {
     const base64 = barcodeRef.value?.getImageBase64();
     if (!base64) {
       message.warning("浜岀淮鐮佺敓鎴愬け璐ワ紝鏃犳硶涓嬭浇");
       return;
     }
-    const items = buildLabelText();
+    const code = currentCode.value || "";
     const canvas = document.createElement("canvas");
     const ctx = canvas.getContext("2d");
     if (!ctx) {
@@ -179,45 +158,23 @@
     }
     const img = new Image();
     img.onload = () => {
-      const padding = 20;
-      const infoX = img.width + padding * 2;
-      const infoWidth = 320;
-      canvas.width = infoX + infoWidth + padding;
-      canvas.height = Math.max(img.height + padding * 2, 300);
+      const padding = 16;
+      const gap = 4;
+      const textHeight = 28;
+      canvas.width = img.width + padding * 2;
+      canvas.height = img.height + padding * 2 + gap + textHeight;
       ctx.fillStyle = "#ffffff";
       ctx.fillRect(0, 0, canvas.width, canvas.height);
       ctx.drawImage(img, padding, padding);
       ctx.fillStyle = "#000000";
-      let y = padding + 10;
-      const lineHeight = 26;
-      items.forEach((item) => {
-        ctx.font = "bold 14px 'Microsoft YaHei', Arial, sans-serif";
-        ctx.fillText(`${item.label}:`, infoX, y);
-        y += 18;
-        ctx.font = "14px 'Microsoft YaHei', Arial, sans-serif";
-        const text = item.value || "-";
-        const maxWidth = infoWidth - 10;
-        if (ctx.measureText(text).width > maxWidth) {
-          let currentText = text;
-          while (currentText.length > 0) {
-            let line = "";
-            for (let i = 0; i < currentText.length; i++) {
-              const testLine = line + currentText[i];
-              if (ctx.measureText(testLine).width > maxWidth) {
-                break;
-              }
-              line = testLine;
-            }
-            ctx.fillText(line, infoX + 5, y);
-            y += 16;
-            currentText = currentText.slice(line.length);
-          }
-          y += 6;
-        } else {
-          ctx.fillText(text, infoX + 5, y);
-          y += lineHeight;
-        }
-      });
+      let fontSize = 20;
+      ctx.font = `bold ${fontSize}px Arial, sans-serif`;
+      while (ctx.measureText(code).width > img.width && fontSize > 1) {
+        ctx.font = `bold ${--fontSize}px Arial, sans-serif`;
+      }
+      ctx.textAlign = "center";
+      ctx.textBaseline = "middle";
+      ctx.fillText(code, canvas.width / 2, padding + img.height + gap + textHeight / 2);
       const link = document.createElement("a");
       link.href = canvas.toDataURL("image/png");
       link.download = `${currentCode.value || "qr"}_${Date.now()}.png`;
@@ -234,7 +191,7 @@
       message.warning("浜岀淮鐮佺敓鎴愬け璐ワ紝鏃犳硶鎵撳嵃");
       return;
     }
-    const items = buildLabelText();
+    const code = currentCode.value || "";
     const printWindow = window.open("", "_blank");
     if (!printWindow) {
       message.error("鏃犳硶鎵撳紑鎵撳嵃绐楀彛锛岃妫�鏌ユ祻瑙堝櫒璁剧疆");
@@ -242,12 +199,19 @@
     }
     printWindow.document.write(`<!DOCTYPE html>
 <html><head><meta charset="UTF-8"><title>鎵撳嵃浜岀淮鐮�</title>
-<style>*{margin:0;padding:0}body{font-family:'Microsoft YaHei',Arial,sans-serif;padding:20px}.print-container{text-align:center}.qr-img{max-width:100%;margin:20px 0}.info{margin-top:20px;text-align:left;font-size:13px}.info p{margin:4px 0}@media print{body{padding:0}.print-container{padding:20px}}</style>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:Arial,sans-serif;background:#fff;color:#000}
+.print-container{width:40mm;padding:2mm;break-inside:avoid;text-align:center}
+.qr-img{display:block;width:36mm;height:36mm;object-fit:contain}
+.code{display:block;width:36mm;height:5mm;margin-top:0.5mm}
+@page{margin:0}
+</style>
 </head><body><div class="print-container">
 <img src="${base64}" class="qr-img" alt="浜岀淮鐮�" />
-<div class="info">
-${items.map((item) => `<p><strong>${escapeHtml(item.label)}:</strong> ${escapeHtml(item.value || "-")}</p>`).join("")}
-</div></div></body></html>`);
+<svg class="code" viewBox="0 0 200 28" xmlns="http://www.w3.org/2000/svg" aria-label="鐮佸��">
+<text x="100" y="20" text-anchor="middle" font-family="Arial,sans-serif" font-size="20" font-weight="700"${code.length > 16 ? ' textLength="196" lengthAdjust="spacingAndGlyphs"' : ''}>${escapeHtml(code)}</text>
+</svg></div></body></html>`);
     printWindow.document.close();
     printWindow.addEventListener("load", () => {
       setTimeout(() => printWindow.print(), 500);
@@ -259,6 +223,7 @@
       if (!isOpen) {
         return;
       }
+      ensureTraceBaseUrl();
       const data = modalApi.getData<{ code?: string }>() || {};
       codeInput.value = data.code ?? undefined;
       resetState();
@@ -274,9 +239,10 @@
     schema: [
       {
         field: "code",
-        label: "琚嬬爜锛堜簩缁寸爜鍐呭锛�",
+        label: "杩芥函鐮�",
         span: 2,
       },
+      { field: 'businessCode', label: '涓氬姟鐮侊紙鍑哄叆搴擄級', span: 2 },
       {
         field: "bagNo",
         label: "琚嬪簭鍙�",
@@ -357,7 +323,12 @@
     schema: [
       {
         field: "code",
-        label: "鎵樼洏鐮侊紙浜岀淮鐮佸唴瀹癸級",
+        label: "鎵樼洏鐮侊紙姝e紡鎵樼洏鐮侊級",
+        span: 2,
+      },
+      {
+        field: "tempCode",
+        label: "涓存椂鎵樼洏鐮侊紙鏈粦瀹氭壒娆★級",
         span: 2,
       },
       {
@@ -419,7 +390,12 @@
     schema: [
       {
         field: "code",
-        label: "鎵规鍙凤紙浜岀淮鐮佸唴瀹癸級",
+        label: "鎵规鍙凤紙姝e紡鎵规鍙凤級",
+        span: 2,
+      },
+      {
+        field: "tempCode",
+        label: "涓存椂鎵规鐮侊紙寰呰ˉ褰曪級",
         span: 2,
       },
       {
@@ -496,12 +472,28 @@
             class="mb-4 flex items-center gap-6 rounded bg-gray-100 p-5"
           >
             <div class="flex-shrink-0">
-              <Barcode ref="barcodeRef" :content="currentCode" :width="200" />
+              <Barcode ref="barcodeRef" :content="qrContent" :width="200" />
             </div>
             <div class="min-w-0 flex-1">
               <p class="text-lg font-bold break-all">{{ currentCode }}</p>
               <p class="mt-1 text-xs text-gray-500">
                 鐮佺被鍨嬶細{{ traceTypeLabel }}
+                <Tag
+                  v-if="traceType === 'batch' && batchTrace && !batchTrace.code"
+                  color="warning"
+                  class="ml-2"
+                >
+                  寰呰ˉ褰�
+                </Tag>
+                <Tag
+                  v-else-if="
+                    traceType === 'pallet' && palletTrace && !palletTrace.code
+                  "
+                  color="warning"
+                  class="ml-2"
+                >
+                  鏈粦瀹氭壒娆�
+                </Tag>
               </p>
             </div>
           </div>

--
Gitblit v1.9.3