云
11 小时以前 1ed040591afdcbe3c5df42ad5e02e2cfa56edd87
src/views/mes/pro/batch/modules/form.vue
@@ -5,7 +5,7 @@
  import type { MesProBatchApi } from "#/api/mes/pro/batch";
  import type { SystemUserApi } from "#/api/system/user";
  import { computed, nextTick, ref } from "vue";
  import { computed, nextTick, reactive, ref } from "vue";
  import { useVbenModal } from "@vben/common-ui";
  import { DICT_TYPE } from "@vben/constants";
@@ -38,8 +38,77 @@
  const formType = ref<FormType>("create");
  const formData = ref<MesProBatchApi.Batch>();
  let palletOptions: PalletOption[] = [];
  let tonBagOptions: TonBagOption[] = [];
  const BINDING_OPTION_PAGE_SIZE = 50;
  const BINDING_SCROLL_THRESHOLD = 32;
  let palletOptionSource: PalletOption[] = [];
  let tonBagOptionSource: TonBagOption[] = [];
  const palletOptions = reactive<PalletOption[]>([]);
  const tonBagOptions = reactive<TonBagOption[]>([]);
  let palletKeyword = "";
  let tonBagKeyword = "";
  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 resetBindingOptions<T extends PalletOption>(
    target: T[],
    source: T[],
    keyword = ""
  ) {
    const filtered = filterBindingOptions(source, keyword);
    target.splice(0, target.length, ...filtered.slice(0, BINDING_OPTION_PAGE_SIZE));
  }
  function appendBindingOptions<T extends PalletOption>(
    target: T[],
    source: T[],
    keyword: string
  ) {
    const filtered = filterBindingOptions(source, keyword);
    if (target.length >= filtered.length) return;
    target.push(...filtered.slice(target.length, target.length + BINDING_OPTION_PAGE_SIZE));
  }
  function isPopupNearBottom(event: Event) {
    const element = event.target as HTMLElement;
    return element.scrollHeight - element.scrollTop - element.clientHeight <= BINDING_SCROLL_THRESHOLD;
  }
  function handlePalletSearch(keyword: string) {
    palletKeyword = keyword;
    resetBindingOptions(palletOptions, palletOptionSource, keyword);
  }
  function handleTonBagSearch(keyword: string) {
    tonBagKeyword = keyword;
    resetBindingOptions(tonBagOptions, tonBagOptionSource, keyword);
  }
  function handlePalletPopupScroll(event: Event) {
    if (isPopupNearBottom(event)) {
      appendBindingOptions(palletOptions, palletOptionSource, palletKeyword);
    }
  }
  function handleTonBagPopupScroll(event: Event) {
    if (isPopupNearBottom(event)) {
      appendBindingOptions(tonBagOptions, tonBagOptionSource, tonBagKeyword);
    }
  }
  const palletHandlers = {
    onChange: handlePalletChange,
    onPopupScroll: handlePalletPopupScroll,
    onSearch: handlePalletSearch,
  };
  const tonBagHandlers = {
    onChange: handleTonBagChange,
    onPopupScroll: handleTonBagPopupScroll,
    onSearch: handleTonBagSearch,
  };
  async function handlePalletChange(ids: number[] = []) {
    const values = await formApi.getValues();
    if (ids.length && (values.tonBagIds?.length || formData.value?.bindingMode === 'tonBag')) {
@@ -47,7 +116,7 @@
      await formApi.setFieldValue('palletIds', []);
      return;
    }
    const selected = palletOptions.filter(option => ids.includes(option.value));
    const selected = palletOptionSource.filter(option => ids.includes(option.value));
    const source = formData.value?.itemId ? formData.value : selected[0];
    if (selected.some(option => !option.itemId || (source?.itemId && option.itemId !== source.itemId))) {
      message.warning('生产批次只能绑定相同品种的托盘');
@@ -65,7 +134,7 @@
      await formApi.setFieldValue('tonBagIds', []);
      return;
    }
    const selected = tonBagOptions.filter(option => ids.includes(option.value));
    const selected = tonBagOptionSource.filter(option => ids.includes(option.value));
    const source = formData.value?.itemId ? formData.value : selected[0];
    if (selected.some(option => option.itemId !== source?.itemId)) {
      message.warning('生产批次只能绑定相同品种的吨袋');
@@ -129,8 +198,9 @@
  let workerRowSeq = 0;
  /** 添加人员行 */
  function handleAddWorker() {
    workerGridApi.grid.insertAt({ _rowKey: -(++workerRowSeq) }, -1);
  async function handleAddWorker() {
    await waitWorkerGridReady();
    await workerGridApi.grid.insertAt({ _rowKey: -(++workerRowSeq) }, -1);
  }
  /** 删除人员行 */
@@ -250,22 +320,47 @@
      const data = modalApi.getData<{ formType: FormType; id?: number }>();
      formType.value = data.formType;
      // 新增/补录/编辑态加载未绑定批次的托盘,供「绑定托盘」多选下拉使用
      palletOptions = [];
      tonBagOptions = [];
      palletOptionSource = [];
      tonBagOptionSource = [];
      palletOptions.splice(0);
      tonBagOptions.splice(0);
      palletKeyword = "";
      tonBagKeyword = "";
      // 先挂载基础表单,候选托盘/吨袋在后台加载,避免只显示人员配置的空窗期
      formApi.setState({
        schema: useFormSchema(
          formType.value,
          palletOptions,
          palletHandlers,
          tonBagOptions,
          tonBagHandlers
        ),
      });
      if (formType.value !== "detail") {
        const [pallets, tonBags] = await Promise.all([getUnboundPalletList(), getUnboundTonBags()]);
        palletOptions = pallets.map((pallet) => ({
        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,
        }));
        tonBagOptions = tonBags.map((bag) => ({
        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);
      }
      formApi.setState({ schema: useFormSchema(formType.value, palletOptions, handlePalletChange, tonBagOptions, handleTonBagChange) });
      // 重新同步一次 Schema,使依赖字段中的动态 options 立即拿到首批候选项
      formApi.setState({
        schema: useFormSchema(
          formType.value,
          palletOptions,
          palletHandlers,
          tonBagOptions,
          tonBagHandlers
        ),
      });
      if (formType.value !== "detail") {
        // 清空上次打开时勾选的托盘,避免残留值误触发「批次进入生产中」
        await formApi.setFieldValue("palletIds", undefined);
@@ -282,7 +377,7 @@
        try {
          formData.value = await getBatch(data.id);
          const existingBinding = formData.value.bindingMode;
          formApi.setState({ schema: useFormSchema(formType.value, palletOptions, handlePalletChange, tonBagOptions, handleTonBagChange, existingBinding) });
          formApi.setState({ schema: useFormSchema(formType.value, palletOptions, palletHandlers, tonBagOptions, tonBagHandlers, existingBinding) });
          await formApi.setValues(formData.value);
          const workers = (formData.value.workers ?? []).map(
            (worker, index) => ({ ...worker, _rowKey: worker.id ?? index })
@@ -295,6 +390,7 @@
      } else {
        await waitWorkerGridReady();
        await workerGridApi.grid.loadData([]);
        await handleAddWorker();
      }
    },
  });