| | |
| | | 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"; |
| | |
| | | import { getBagCodeByCode } 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" }); |
| | |
| | | |
| | | const traceTypeLabel = ref(""); |
| | | |
| | | /** 识别二维码内容类型:批次号 / 托盘码 / 袋码 */ |
| | | /** 溯源 H5 页面基础地址(会话内只拉取一次) */ |
| | | const traceBaseUrl = ref<string>(""); |
| | | |
| | | async function ensureTraceBaseUrl() { |
| | | if (traceBaseUrl.value) return; |
| | | try { |
| | | const res = await getQrConfig(); |
| | | traceBaseUrl.value = res.traceBaseUrl || ""; |
| | | } catch { |
| | | traceBaseUrl.value = ""; |
| | | } |
| | | } |
| | | |
| | | /** 二维码展示内容:配置了溯源地址则拼 H5 URL,否则回退纯码值(与后端 buildQrCodeContent 一致) */ |
| | | const qrContent = computed(() => { |
| | | if (!currentCode.value) return ""; |
| | | return traceBaseUrl.value |
| | | ? `${traceBaseUrl.value}?code=${encodeURIComponent(currentCode.value)}` |
| | | : currentCode.value; |
| | | }); |
| | | |
| | | /** 从完整 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 { |
| | | // 解析失败按原始输入处理 |
| | | } |
| | | } |
| | | return trimmed; |
| | | } |
| | | |
| | | /** 识别二维码内容类型:批次号(含临时批次码)/ 托盘码(含临时托盘码)/ 袋码 */ |
| | | function resolveCodeType(raw: string): TraceType | undefined { |
| | | const value = raw.trim().toUpperCase(); |
| | | if (/^PB-\d{8}$/.test(value)) { |
| | | return "batch"; |
| | | } |
| | | if (/^PL-\d{8}$/.test(value)) { |
| | | return "pallet"; |
| | | } |
| | | if (/^.*-P\d{2}$/.test(value)) { |
| | | return "pallet"; |
| | | } |
| | | if (/^.*-\d{4}$/.test(value)) { |
| | | if (/^BAG-\d{8}$/.test(value) || /^.*-\d{4}$/.test(value)) { |
| | | return "bag"; |
| | | } |
| | | return "batch"; |
| | |
| | | |
| | | /** 扫码/输入二维码内容查询追溯信息 */ |
| | | 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 (traceType.value === "pallet" && palletTrace.value) { |
| | | const d = palletTrace.value; |
| | | return [ |
| | | { label: "托盘码", value: d.code || "" }, |
| | | { label: "托盘码", value: d.code || d.tempCode || "" }, |
| | | { label: "托盘序号", value: d.palletNo ? String(d.palletNo) : "" }, |
| | | { label: "批次号", value: d.batchCode || "" }, |
| | | { label: "品种", value: d.itemName || "" }, |
| | |
| | | if (batchTrace.value) { |
| | | const d = batchTrace.value; |
| | | return [ |
| | | { label: "批次号", value: d.code || "" }, |
| | | { label: "批次号", value: d.code || d.tempCode || "" }, |
| | | { label: "品种", value: d.itemName || "" }, |
| | | { label: "规格", value: d.itemSpecification || "" }, |
| | | { |
| | |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | ensureTraceBaseUrl(); |
| | | const data = modalApi.getData<{ code?: string }>() || {}; |
| | | codeInput.value = data.code ?? undefined; |
| | | resetState(); |
| | |
| | | schema: [ |
| | | { |
| | | field: "code", |
| | | label: "托盘码(二维码内容)", |
| | | label: "托盘码(正式托盘码)", |
| | | span: 2, |
| | | }, |
| | | { |
| | | field: "tempCode", |
| | | label: "临时托盘码(未绑定批次)", |
| | | span: 2, |
| | | }, |
| | | { |
| | |
| | | schema: [ |
| | | { |
| | | field: "code", |
| | | label: "批次号(二维码内容)", |
| | | label: "批次号(正式批次号)", |
| | | span: 2, |
| | | }, |
| | | { |
| | | field: "tempCode", |
| | | label: "临时批次码(待补录)", |
| | | span: 2, |
| | | }, |
| | | { |
| | |
| | | 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> |