From 5d321660a2f1a4b8756f816283e96481a8c934e4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 28 十月 2026 14:26:30 +0800
Subject: [PATCH] feat(app): 更新应用名称并优化批次绑定功能

---
 src/views/mes/pro/batch/modules/form.vue |  274 ++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 204 insertions(+), 70 deletions(-)

diff --git a/src/views/mes/pro/batch/modules/form.vue b/src/views/mes/pro/batch/modules/form.vue
index f405c55..1ef766d 100644
--- a/src/views/mes/pro/batch/modules/form.vue
+++ b/src/views/mes/pro/batch/modules/form.vue
@@ -21,12 +21,14 @@
     getBatch,
     updateBatch,
   } from "#/api/mes/pro/batch";
-  import { getUnboundPalletList } from "#/api/mes/pro/pallet";
-  import { getUnboundTonBags } from "#/api/mes/pro/bagCode";
+  import { getUnboundPalletPage } from "#/api/mes/pro/pallet";
+  import { getUnboundTonBagPage } from "#/api/mes/pro/bagCode";
   import { $t } from "#/locales";
   import { UserSelect } from "#/views/system/user/components";
 
   import {
+    applyPalletOptionState,
+    applyTonBagOptionState,
     useFormSchema,
     useWorkerEditGridColumns,
     useWorkerReadGridColumns,
@@ -38,38 +40,177 @@
 
   const formType = ref<FormType>("create");
   const formData = ref<MesProBatchApi.Batch>();
-  const BINDING_OPTION_PAGE_SIZE = 50;
+  /** 缁戝畾涓嬫媺姣忔鍔犺浇鐨勬潯鏁� */
+  const BINDING_PAGE_SIZE = 50;
+  /** 璺濈涓嬫媺搴曢儴澶氬皯鍍忕礌鏃惰Е鍙戝姞杞戒笅涓�椤� */
   const BINDING_SCROLL_THRESHOLD = 32;
-  let palletOptionSource: PalletOption[] = [];
-  let tonBagOptionSource: TonBagOption[] = [];
+  /** 杈撳叆妗嗘悳绱㈤槻鎶栨椂闂达紙姣锛� */
+  const BINDING_SEARCH_DEBOUNCE = 300;
+
+  /**
+   * 缁戝畾涓嬫媺鐨勫垎椤靛姞杞界姸鎬併��
+   *
+   * 涓嬫媺閫夐」鏈韩灏辨槸娓叉煋鏁版嵁婧愶紙淇濇寔鏁扮粍寮曠敤涓嶅彉锛岀洿鎺� splice 鏇存柊锛夛紝
+   * 宸插姞杞界殑鍒嗛〉缁撴灉鏀惧湪 loaded 涓紝璺ㄩ〉閫変腑鐨勯�夐」鍗曠嫭璁板綍锛岄伩鍏嶆崲椤垫悳绱㈠悗鏍囩涓㈠け銆�
+   */
+  interface BindingLoader<T extends PalletOption> {
+    hasMore: boolean;
+    keyword: string;
+    loaded: T[];
+    loading: boolean;
+    pageNo: number;
+    seq: number;
+    timer?: ReturnType<typeof setTimeout>;
+  }
+
+  function createBindingLoader<T extends PalletOption>(): BindingLoader<T> {
+    return {
+      hasMore: true,
+      keyword: "",
+      loaded: [],
+      loading: false,
+      pageNo: 0,
+      seq: 0,
+    };
+  }
+
   const palletOptions = reactive<PalletOption[]>([]);
   const tonBagOptions = reactive<TonBagOption[]>([]);
-  let palletKeyword = "";
-  let tonBagKeyword = "";
+  const palletSelected = new Map<number, PalletOption>();
+  const tonBagSelected = new Map<number, TonBagOption>();
+  const palletLoader = createBindingLoader<PalletOption>();
+  const tonBagLoader = createBindingLoader<TonBagOption>();
 
-  function filterBindingOptions<T extends PalletOption>(source: T[], keyword: string): T[] {
-    const normalized = keyword.trim().toLowerCase();
-    if (!normalized) return source;
-    return source.filter((option) => option.label.toLowerCase().includes(normalized));
+  /** 宸插姞杞藉垎椤电粨鏋� + 璺ㄩ〉閫変腑椤瑰悎骞朵负涓嬫媺娓叉煋鏁版嵁 */
+  function renderBindingOptions<T extends PalletOption>(
+    target: T[],
+    loader: BindingLoader<T>,
+    selected: Map<number, T>
+  ) {
+    const merged = [...loader.loaded];
+    const loadedIds = new Set(loader.loaded.map((option) => option.value));
+    selected.forEach((option, id) => {
+      if (!loadedIds.has(id)) merged.push(option);
+    });
+    target.splice(0, target.length, ...merged);
   }
 
-  function resetBindingOptions<T extends PalletOption>(
+  /** 鍚屾閫変腑椤癸紙鎹㈤〉鎼滅储鍚庝粛闇�灞曠ず宸查�夋爣绛撅級 */
+  function syncBindingSelection<T extends PalletOption>(
     target: T[],
-    source: T[],
-    keyword = ""
+    loader: BindingLoader<T>,
+    selected: Map<number, T>,
+    ids: number[]
   ) {
-    const filtered = filterBindingOptions(source, keyword);
-    target.splice(0, target.length, ...filtered.slice(0, BINDING_OPTION_PAGE_SIZE));
+    const previous = new Map(selected);
+    selected.clear();
+    ids.forEach((id) => {
+      const option = previous.get(id)
+        ?? loader.loaded.find((item) => item.value === id);
+      if (option) selected.set(id, option);
+    });
+    renderBindingOptions(target, loader, selected);
   }
 
-  function appendBindingOptions<T extends PalletOption>(
+  /** 閫夐」绂佺敤鎬佷緷璧栧綋鍓嶈〃鍗曞�硷紝姣忔鍔犺浇鏂颁竴椤靛悗閲嶆柊璁$畻 */
+  async function refreshBindingOptionState() {
+    const values = await formApi.getValues();
+    const existingBinding = formData.value?.bindingMode;
+    applyPalletOptionState(palletOptions, values, existingBinding);
+    applyTonBagOptionState(tonBagOptions, values, existingBinding);
+  }
+
+  /** 鎷夊彇涓�椤电粦瀹氬�欓�夐」锛況eset=true 琛ㄧず浠庣涓�椤甸噸鏂板紑濮嬶紙棣栨鎵撳紑銆佹悳绱級 */
+  async function loadBindingPage<T extends PalletOption>(
+    loader: BindingLoader<T>,
     target: T[],
-    source: T[],
-    keyword: string
+    selected: Map<number, T>,
+    fetchPage: (params: {
+      keyword?: string;
+      pageNo: number;
+      pageSize: number;
+    }) => Promise<{ list: T[]; total: number }>,
+    reset: boolean
   ) {
-    const filtered = filterBindingOptions(source, keyword);
-    if (target.length >= filtered.length) return;
-    target.push(...filtered.slice(target.length, target.length + BINDING_OPTION_PAGE_SIZE));
+    if (!reset && (loader.loading || !loader.hasMore)) return;
+    const seq = ++loader.seq;
+    loader.loading = true;
+    try {
+      const pageNo = reset ? 1 : loader.pageNo + 1;
+      const page = await fetchPage({
+        keyword: loader.keyword || undefined,
+        pageNo,
+        pageSize: BINDING_PAGE_SIZE,
+      });
+      // 宸茶鏇存柊鐨勬悳绱㈡垨骞跺彂璇锋眰瑕嗙洊锛屼涪寮冩湰娆$粨鏋�
+      if (seq !== loader.seq) return;
+      loader.loaded = reset ? page.list : [...loader.loaded, ...page.list];
+      loader.pageNo = pageNo;
+      loader.hasMore = loader.loaded.length < page.total;
+      renderBindingOptions(target, loader, selected);
+      await refreshBindingOptionState();
+    } finally {
+      if (seq === loader.seq) loader.loading = false;
+    }
+  }
+
+  function fetchPalletPage(params: {
+    keyword?: string;
+    pageNo: number;
+    pageSize: number;
+  }) {
+    return getUnboundPalletPage(params).then((page) => ({
+      list: page.list.map((pallet) => ({
+        label: `${pallet.tempCode ?? `鎵樼洏${pallet.id}`} 路 ${pallet.itemName || "寰呭畬鍠勫搧绉�"}锛堝凡瑁� ${pallet.bagCount ?? 0} 琚嬶級`,
+        value: pallet.id as number,
+        itemId: pallet.itemId,
+        itemName: pallet.itemName,
+        itemCode: pallet.itemCode,
+      })),
+      total: page.total,
+    }));
+  }
+
+  function fetchTonBagPage(params: {
+    keyword?: string;
+    pageNo: number;
+    pageSize: number;
+  }) {
+    return getUnboundTonBagPage(params).then((page) => ({
+      list: page.list.map((bag) => ({
+        label: `${bag.code ?? `鍚ㄨ${bag.id}`} 路 ${bag.itemName || "寰呭畬鍠勫搧绉�"}`,
+        value: bag.id as number,
+        itemId: bag.itemId,
+        itemName: bag.itemName,
+        itemCode: bag.itemCode,
+      })),
+      total: page.total,
+    }));
+  }
+
+  function loadPalletPage(reset: boolean) {
+    return loadBindingPage(palletLoader, palletOptions, palletSelected, fetchPalletPage, reset);
+  }
+
+  function loadTonBagPage(reset: boolean) {
+    return loadBindingPage(tonBagLoader, tonBagOptions, tonBagSelected, fetchTonBagPage, reset);
+  }
+
+  /** 閲嶇疆涓嬫媺鍒嗛〉鐘舵�侊紙姣忔鎵撳紑寮圭獥璋冪敤锛夛紝鍚屾椂璁╁湪閫旇姹傜殑缁撴灉澶辨晥 */
+  function resetBindingLoader<T extends PalletOption>(
+    loader: BindingLoader<T>,
+    target: T[],
+    selected: Map<number, T>
+  ) {
+    clearTimeout(loader.timer);
+    loader.seq += 1;
+    loader.hasMore = true;
+    loader.keyword = "";
+    loader.loaded = [];
+    loader.loading = false;
+    loader.pageNo = 0;
+    target.splice(0, target.length);
+    selected.clear();
   }
 
   function isPopupNearBottom(event: Event) {
@@ -77,26 +218,30 @@
     return element.scrollHeight - element.scrollTop - element.clientHeight <= BINDING_SCROLL_THRESHOLD;
   }
 
+  function scheduleBindingSearch<T extends PalletOption>(
+    loader: BindingLoader<T>,
+    keyword: string,
+    reload: (reset: boolean) => Promise<void>
+  ) {
+    loader.keyword = keyword.trim();
+    clearTimeout(loader.timer);
+    loader.timer = setTimeout(() => void reload(true), BINDING_SEARCH_DEBOUNCE);
+  }
+
   function handlePalletSearch(keyword: string) {
-    palletKeyword = keyword;
-    resetBindingOptions(palletOptions, palletOptionSource, keyword);
+    scheduleBindingSearch(palletLoader, keyword, loadPalletPage);
   }
 
   function handleTonBagSearch(keyword: string) {
-    tonBagKeyword = keyword;
-    resetBindingOptions(tonBagOptions, tonBagOptionSource, keyword);
+    scheduleBindingSearch(tonBagLoader, keyword, loadTonBagPage);
   }
 
   function handlePalletPopupScroll(event: Event) {
-    if (isPopupNearBottom(event)) {
-      appendBindingOptions(palletOptions, palletOptionSource, palletKeyword);
-    }
+    if (isPopupNearBottom(event)) void loadPalletPage(false);
   }
 
   function handleTonBagPopupScroll(event: Event) {
-    if (isPopupNearBottom(event)) {
-      appendBindingOptions(tonBagOptions, tonBagOptionSource, tonBagKeyword);
-    }
+    if (isPopupNearBottom(event)) void loadTonBagPage(false);
   }
 
   const palletHandlers = {
@@ -109,19 +254,27 @@
     onPopupScroll: handleTonBagPopupScroll,
     onSearch: handleTonBagSearch,
   };
+
   async function handlePalletChange(ids: number[] = []) {
     const values = await formApi.getValues();
     if (ids.length && (values.tonBagIds?.length || formData.value?.bindingMode === 'tonBag')) {
       message.warning('缁戝畾鎵樼洏鍜岀粦瀹氬惃琚嬪彧鑳介�夋嫨涓�绉�');
       await formApi.setFieldValue('palletIds', []);
+      syncBindingSelection(palletOptions, palletLoader, palletSelected, []);
       return;
     }
-    const selected = palletOptionSource.filter(option => ids.includes(option.value));
+    const selected = ids
+      .map((id) => palletSelected.get(id) ?? palletLoader.loaded.find((option) => option.value === id))
+      .filter((option): option is PalletOption => !!option);
     const source = formData.value?.itemId ? formData.value : selected[0];
+    let finalIds = ids;
     if (selected.some(option => !option.itemId || (source?.itemId && option.itemId !== source.itemId))) {
       message.warning('鐢熶骇鎵规鍙兘缁戝畾鐩稿悓鍝佺鐨勬墭鐩�');
-      await formApi.setFieldValue('palletIds', selected.filter(option => option.itemId === source?.itemId).map(option => option.value));
+      finalIds = selected.filter(option => option.itemId === source?.itemId).map(option => option.value);
+      await formApi.setFieldValue('palletIds', finalIds);
     }
+    syncBindingSelection(palletOptions, palletLoader, palletSelected, finalIds);
+    await refreshBindingOptionState();
     await formApi.setFieldValue('itemId', source?.itemId);
     await formApi.setFieldValue('itemName', source?.itemName);
     await formApi.setFieldValue('itemCode', source?.itemCode);
@@ -132,14 +285,21 @@
     if (ids.length && (values.palletIds?.length || formData.value?.bindingMode === 'pallet')) {
       message.warning('缁戝畾鎵樼洏鍜岀粦瀹氬惃琚嬪彧鑳介�夋嫨涓�绉�');
       await formApi.setFieldValue('tonBagIds', []);
+      syncBindingSelection(tonBagOptions, tonBagLoader, tonBagSelected, []);
       return;
     }
-    const selected = tonBagOptionSource.filter(option => ids.includes(option.value));
+    const selected = ids
+      .map((id) => tonBagSelected.get(id) ?? tonBagLoader.loaded.find((option) => option.value === id))
+      .filter((option): option is TonBagOption => !!option);
     const source = formData.value?.itemId ? formData.value : selected[0];
+    let finalIds = ids;
     if (selected.some(option => option.itemId !== source?.itemId)) {
       message.warning('鐢熶骇鎵规鍙兘缁戝畾鐩稿悓鍝佺鐨勫惃琚�');
-      await formApi.setFieldValue('tonBagIds', selected.filter(option => option.itemId === source?.itemId).map(option => option.value));
+      finalIds = selected.filter(option => option.itemId === source?.itemId).map(option => option.value);
+      await formApi.setFieldValue('tonBagIds', finalIds);
     }
+    syncBindingSelection(tonBagOptions, tonBagLoader, tonBagSelected, finalIds);
+    await refreshBindingOptionState();
     await formApi.setFieldValue('itemId', source?.itemId);
     await formApi.setFieldValue('itemName', source?.itemName);
     await formApi.setFieldValue('itemCode', source?.itemCode);
@@ -319,14 +479,9 @@
       }
       const data = modalApi.getData<{ formType: FormType; id?: number }>();
       formType.value = data.formType;
-      // 鏂板/琛ュ綍/缂栬緫鎬佸姞杞芥湭缁戝畾鎵规鐨勬墭鐩橈紝渚涖�岀粦瀹氭墭鐩樸�嶅閫変笅鎷変娇鐢�
-      palletOptionSource = [];
-      tonBagOptionSource = [];
-      palletOptions.splice(0);
-      tonBagOptions.splice(0);
-      palletKeyword = "";
-      tonBagKeyword = "";
-      // 鍏堟寕杞藉熀纭�琛ㄥ崟锛屽�欓�夋墭鐩�/鍚ㄨ鍦ㄥ悗鍙板姞杞斤紝閬垮厤鍙樉绀轰汉鍛橀厤缃殑绌虹獥鏈�
+      // 姣忔鎵撳紑閲嶇疆缁戝畾鍊欓�夛紝涓嬫媺鏀逛负婊氬姩鍒嗛〉鍔犺浇锛岄鎵瑰湪琛ㄥ崟鎸傝浇鍚庡紓姝ユ媺鍙�
+      resetBindingLoader(palletLoader, palletOptions, palletSelected);
+      resetBindingLoader(tonBagLoader, tonBagOptions, tonBagSelected);
       formApi.setState({
         schema: useFormSchema(
           formType.value,
@@ -337,34 +492,11 @@
         ),
       });
       if (formType.value !== "detail") {
-        const [pallets, tonBags] = await Promise.all([getUnboundPalletList(), getUnboundTonBags()]);
-        palletOptionSource = pallets.map((pallet) => ({
-          label: `${pallet.tempCode ?? `鎵樼洏${pallet.id}`} 路 ${pallet.itemName || "寰呭畬鍠勫搧绉�"}锛堝凡瑁� ${pallet.bagCount ?? 0} 琚嬶級`,
-          value: pallet.id as number,
-          itemId: pallet.itemId, itemName: pallet.itemName, itemCode: pallet.itemCode,
-        }));
-        tonBagOptionSource = tonBags.map((bag) => ({
-          label: `${bag.code ?? `鍚ㄨ${bag.id}`} 路 ${bag.itemName || '寰呭畬鍠勫搧绉�'}`,
-          value: bag.id as number,
-          itemId: bag.itemId, itemName: bag.itemName, itemCode: bag.itemCode,
-        }));
-        resetBindingOptions(palletOptions, palletOptionSource);
-        resetBindingOptions(tonBagOptions, tonBagOptionSource);
-      }
-      // 閲嶆柊鍚屾涓�娆� Schema锛屼娇渚濊禆瀛楁涓殑鍔ㄦ�� options 绔嬪嵆鎷垮埌棣栨壒鍊欓�夐」
-      formApi.setState({
-        schema: useFormSchema(
-          formType.value,
-          palletOptions,
-          palletHandlers,
-          tonBagOptions,
-          tonBagHandlers
-        ),
-      });
-      if (formType.value !== "detail") {
-        // 娓呯┖涓婃鎵撳紑鏃跺嬀閫夌殑鎵樼洏锛岄伩鍏嶆畫鐣欏�艰瑙﹀彂銆屾壒娆¤繘鍏ョ敓浜т腑銆�
+        // 娓呯┖涓婃鎵撳紑鏃跺嬀閫夌殑鎵樼洏/鍚ㄨ锛岄伩鍏嶆畫鐣欏�艰瑙﹀彂銆屾壒娆¤繘鍏ョ敓浜т腑銆�
         await formApi.setFieldValue("palletIds", undefined);
         await formApi.setFieldValue("tonBagIds", undefined);
+        void loadPalletPage(true);
+        void loadTonBagPage(true);
       }
       // 鏍规嵁琛ㄥ崟鎬佸垏鎹汉鍛樺瓙琛ㄥ垪
       workerGridApi.setGridOptions({
@@ -379,6 +511,8 @@
           const existingBinding = formData.value.bindingMode;
           formApi.setState({ schema: useFormSchema(formType.value, palletOptions, palletHandlers, tonBagOptions, tonBagHandlers, existingBinding) });
           await formApi.setValues(formData.value);
+          // 宸叉湁缁戝畾浼氶攣瀹氬彟涓�渚т笅鎷夛紝閫夐」绂佺敤鎬侀渶瑕佹寜鏈�鏂拌〃鍗曞�奸噸绠�
+          await refreshBindingOptionState();
           const workers = (formData.value.workers ?? []).map(
             (worker, index) => ({ ...worker, _rowKey: worker.id ?? index })
           );

--
Gitblit v1.9.3