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/pallet/index.vue |  174 ++++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 138 insertions(+), 36 deletions(-)

diff --git a/src/views/mes/pro/pallet/index.vue b/src/views/mes/pro/pallet/index.vue
index 403a339..82adce6 100644
--- a/src/views/mes/pro/pallet/index.vue
+++ b/src/views/mes/pro/pallet/index.vue
@@ -5,25 +5,36 @@
   import { Page, useVbenModal } from "@vben/common-ui";
   import { downloadFileFromBlobPart } from "@vben/utils";
 
-  import { message } from "ant-design-vue";
+  import { Button, message, Modal, Tag } from "ant-design-vue";
 
   import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table";
   import {
     deletePallet,
     exportPalletExcel,
     exportPalletQr,
+    exportPalletQrByIds,
+    exportPalletBusinessQr,
+    generatePalletBusinessCodes,
     getPalletPage,
   } from "#/api/mes/pro/pallet";
+  import { IconifyIcon } from "@vben/icons";
   import { $t } from "#/locales";
 
   import { useGridColumns, useGridFormSchema } from "./data";
+  import BindBatchModal from "./modules/bind-batch.vue";
   import GenerateModal from "./modules/generate-form.vue";
   import QrTraceModal from "../components/qr-trace-modal.vue";
+  import BusinessQrModal from "../components/business-qr.vue";
 
   defineOptions({ name: "MesProPallet" });
 
   const [GenerateFormModal, generateFormModalApi] = useVbenModal({
     connectedComponent: GenerateModal,
+    destroyOnClose: true,
+  });
+
+  const [BindBatchFormModal, bindBatchFormModalApi] = useVbenModal({
+    connectedComponent: BindBatchModal,
     destroyOnClose: true,
   });
 
@@ -33,13 +44,45 @@
   });
 
   /** 鍒锋柊琛ㄦ牸 */
+  const [BusinessQrViewModal, businessQrApi] = useVbenModal({ connectedComponent: BusinessQrModal, destroyOnClose: true });
+  function handleBusinessQr(row: MesProPalletApi.Pallet) {
+    businessQrApi.setData({ code: row.businessCode }).open();
+  }
+  function getBusinessIds() {
+    const records = [...(gridApi.grid.getCheckboxReserveRecords?.() || []), ...(gridApi.grid.getCheckboxRecords?.() || [])] as MesProPalletApi.Pallet[];
+    const ids = [...new Set(records.map(row => row.id).filter((id): id is number => id != null))];
+    if (!ids.length || ids.length > 200) { message.warning('璇峰嬀閫� 1 鑷� 200 鏉℃墭鐩樿褰�'); return; }
+    if (records.some(row => row.status === 20)) { message.warning('宸蹭綔搴熸墭鐩樹笉鍙搷浣滀笟鍔$爜'); return; }
+    return ids;
+  }
+  async function handleGenerateBusiness() {
+    const ids = getBusinessIds(); if (!ids) return;
+    const count = await generatePalletBusinessCodes(ids);
+    message.success(`宸茶ˉ鐢熸垚 ${count} 浠戒笟鍔$爜锛屽凡鏈夌爜鍊间繚鎸佷笉鍙榒); handleRefresh();
+  }
+  async function handleExportBusiness() {
+    const ids = getBusinessIds(); if (!ids) return;
+    const data = await exportPalletBusinessQr(ids);
+    downloadFileFromBlobPart({ fileName: '鎵樼洏涓氬姟浜岀淮鐮�.zip', source: data });
+  }
+
   function handleRefresh() {
     gridApi.query();
   }
 
-  /** 鎵归噺鐢熸垚鎵樼洏鐮� */
+  /** 鎵归噺棰勭敓鎴愭墭鐩樼爜 */
   function handleCreate() {
     generateFormModalApi.open();
+  }
+
+  /** 棰勭敓鎴愭垚鍔燂細鍒锋柊鍒楄〃骞跺睍绀虹敓鎴愮殑涓存椂鎵樼洏鐮� */
+  function handlePreGenerateSuccess(result: { id: number; tempCode: string }[]) {
+    handleRefresh();
+    Modal.success({
+      title: "棰勭敓鎴愪复鏃舵墭鐩樼爜鎴愬姛",
+      content: result.map((item) => item.tempCode).join("\n"),
+      centered: true,
+    });
   }
 
   /** 鎵爜杩芥函 */
@@ -47,9 +90,44 @@
     qrTraceQueryModalApi.open();
   }
 
-  /** 鏌ョ湅浜岀淮鐮侊紙琛屽唴棰勫~鐮佸�硷級 */
+  /** 鑾峰彇鎵樼洏鏄剧ず鐢ㄧ殑鐮佸�硷紙姝e紡鎵樼洏鐮佷紭鍏堬紝鍏舵涓存椂鎵樼洏鐮侊級 */
+  function getDisplayCode(row: MesProPalletApi.Pallet) {
+    return row.code || row.tempCode || "";
+  }
+
+  /** 鏌ョ湅浜岀淮鐮侊紙琛屽唴棰勫~鐮佸�硷紝涓存椂鎵樼洏鐢ㄤ复鏃舵墭鐩樼爜锛� */
   function handleViewQr(row: MesProPalletApi.Pallet) {
-    qrTraceQueryModalApi.setData({ code: row.code }).open();
+    qrTraceQueryModalApi.setData({ code: getDisplayCode(row) }).open();
+  }
+
+  /** 鑾峰彇鍕鹃�夌殑涓存椂鎵樼洏璁板綍 */
+  function getSelectedTempPallets(): MesProPalletApi.Pallet[] | undefined {
+    const records = [
+      ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+      ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+    ] as MesProPalletApi.Pallet[];
+    const uniqueRecords = [
+      ...new Map(records.map((record) => [record.id, record])).values(),
+    ];
+    const tempRecords = uniqueRecords.filter((record) => !record.batchId);
+    if (uniqueRecords.length === 0) {
+      message.warning("璇疯嚦灏戝嬀閫変竴鏉℃墭鐩樼爜璁板綍");
+      return undefined;
+    }
+    if (tempRecords.length === 0) {
+      message.warning("鍕鹃�夌殑璁板綍鍧囧凡缁戝畾鎵规锛屾棤闇�缁戝畾");
+      return undefined;
+    }
+    return tempRecords;
+  }
+
+  /** 缁戝畾鎵规 */
+  function handleBindBatch() {
+    const records = getSelectedTempPallets();
+    if (!records) return;
+    bindBatchFormModalApi
+      .setData({ ids: records.map((record) => record.id!) })
+      .open();
   }
 
   /** 瀵煎嚭浜岀淮鐮� ZIP锛堟寜褰撳墠鏌ヨ鏉′欢鐨勬壒娆★級 */
@@ -63,10 +141,19 @@
     downloadFileFromBlobPart({ fileName: "鎵樼洏鐮佷簩缁寸爜.zip", source: data });
   }
 
+  /** 瀵煎嚭鍗曚釜鎵樼洏鐮佷簩缁寸爜 ZIP锛堟敮鎸佹湭缁戝畾鎵规鐨勪复鏃舵墭鐩橈級 */
+  async function handleExportQrById(row: MesProPalletApi.Pallet) {
+    const data = await exportPalletQrByIds([row.id!]);
+    downloadFileFromBlobPart({
+      fileName: `鎵樼洏鐮佷簩缁寸爜-${getDisplayCode(row)}.zip`,
+      source: data,
+    });
+  }
+
   /** 鍒犻櫎鎵樼洏鐮� */
   async function handleDelete(row: MesProPalletApi.Pallet) {
     await deletePallet(row.id!);
-    message.success($t("ui.actionMessage.deleteSuccess", [row.code]));
+    message.success($t("ui.actionMessage.deleteSuccess", [getDisplayCode(row)]));
     handleRefresh();
   }
 
@@ -81,6 +168,10 @@
       schema: useGridFormSchema(),
     },
     gridOptions: {
+      checkboxConfig: {
+        highlight: true,
+        reserve: true,
+      },
       columns: useGridColumns(),
       height: "auto",
       keepSource: true,
@@ -109,46 +200,51 @@
 
 <template>
   <Page auto-content-height>
-    <GenerateFormModal @success="handleRefresh" />
+    <GenerateFormModal @success="handlePreGenerateSuccess" />
+    <BindBatchFormModal @success="handleRefresh" />
     <QrTraceQueryModal />
+    <BusinessQrViewModal />
     <Grid table-title="鎵樼洏鐮佸垪琛�">
+      <template #table-title><span class="text-[1rem] font-bold" style="white-space:nowrap">鎵樼洏鐮佸垪琛�</span></template>
       <template #toolbar-tools>
-        <TableAction :actions="[
-            {
-              label: '鎵归噺鐢熸垚',
-              type: 'primary',
-              icon: ACTION_ICON.ADD,
-              auth: ['mes:pro-pallet:create'],
-              onClick: handleCreate,
-            },
-            {
-              label: '鎵爜杩芥函',
-              icon: ACTION_ICON.SEARCH,
-              auth: ['mes:pro-pallet:query'],
-              onClick: handleTrace,
-            },
-            {
-              label: '瀵煎嚭浜岀淮鐮�',
-              icon: ACTION_ICON.DOWNLOAD,
-              auth: ['mes:pro-pallet:export'],
-              onClick: handleExportQr,
-            },
-            {
-              label: $t('ui.actionTitle.export'),
-              type: 'primary',
-              icon: ACTION_ICON.DOWNLOAD,
-              auth: ['mes:pro-pallet:export'],
-              onClick: handleExport,
-            },
-          ]" />
+        <div class="mes-code-toolbar">
+          <TableAction :actions="[{ label: '棰勭敓鎴愪簩缁寸爜', type: 'primary', icon: ACTION_ICON.ADD, auth: ['mes:pro-pallet:create'], onClick: handleCreate },
+          { label: '缁戝畾鎵规', auth: ['mes:pro-pallet:update'], onClick: handleBindBatch },
+          { label: '鎵爜杩芥函', icon: ACTION_ICON.SEARCH, auth: ['mes:pro-pallet:query'], onClick: handleTrace }]" />
+          <TableAction :dropdown-trigger="['click']" :drop-down-actions="[{ label: '杩芥函浜岀淮鐮侊紙鎸夋壒娆★級', auth: ['mes:pro-pallet:export'], onClick: handleExportQr },
+          { label: '涓氬姟浜岀淮鐮侊紙閫変腑璁板綍锛�', auth: ['mes:pro-pallet:export'], onClick: handleExportBusiness },
+          { label: 'Excel 鏁版嵁', auth: ['mes:pro-pallet:export'], onClick: handleExport }]">
+            <template #more><Button>瀵煎嚭 <IconifyIcon icon="lucide:chevron-down" /></Button></template>
+          </TableAction>
+          <TableAction :dropdown-trigger="['click']" :drop-down-actions="[{ label: '琛ョ敓鎴愪笟鍔$爜锛堥�変腑璁板綍锛�', auth: ['mes:pro-pallet:create'], onClick: handleGenerateBusiness }]">
+            <template #more><Button>鏇村鎿嶄綔 <IconifyIcon icon="lucide:chevron-down" /></Button></template>
+          </TableAction>
+        </div>
+      </template>
+      <template #code="{ row }">
+        <span>{{ getDisplayCode(row) }}</span>
+        <Tag v-if="!row.code" color="warning">鏈粦瀹氭壒娆�</Tag>
+        <Tag v-if="row.autoGenerated" color="processing">鑷姩鐢熸垚</Tag>
+      </template>
+      <template #batchCode="{ row }">
+        <span v-if="row.batchCode">{{ row.batchCode }}</span>
+        <Tag v-else color="warning">鏈粦瀹氭壒娆�</Tag>
       </template>
       <template #actions="{ row }">
         <TableAction :actions="[
             {
-              label: '鏌ョ湅浜岀淮鐮�',
+              label: '杩芥函浜岀淮鐮�',
               type: 'link',
               auth: ['mes:pro-pallet:query'],
               onClick: handleViewQr.bind(null, row),
+            },
+            { label: '涓氬姟浜岀淮鐮�', type: 'link', auth: ['mes:pro-pallet:query'], ifShow: !!row.businessCode, onClick: handleBusinessQr.bind(null, row) },
+            {
+              label: '瀵煎嚭浜岀淮鐮�',
+              type: 'link',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:pro-pallet:export'],
+              onClick: handleExportQrById.bind(null, row),
             },
             {
               label: $t('common.delete'),
@@ -158,7 +254,7 @@
               auth: ['mes:pro-pallet:delete'],
               ifShow: !row.bagCount,
               popConfirm: {
-                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                title: $t('ui.actionMessage.deleteConfirm', [getDisplayCode(row)]),
                 confirm: handleDelete.bind(null, row),
               },
             },
@@ -167,3 +263,9 @@
     </Grid>
   </Page>
 </template>
+
+<style scoped>
+.mes-code-toolbar { display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px; }
+.mes-code-toolbar :deep(.ant-btn) { white-space:nowrap; }
+.mes-code-toolbar :deep(.ant-btn > .iconify) { display:inline-block;width:14px;height:14px;vertical-align:middle; }
+</style>

--
Gitblit v1.9.3