| | |
| | | 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"; |
| | |
| | | |
| | | 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')) { |
| | |
| | | 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('生产批次只能绑定相同品种的托盘'); |
| | |
| | | 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('生产批次只能绑定相同品种的吨袋'); |
| | |
| | | let workerRowSeq = 0; |
| | | |
| | | /** 添加人员行 */ |
| | | function handleAddWorker() { |
| | | workerGridApi.grid.insertAt({ _rowKey: -(++workerRowSeq) }, -1); |
| | | async function handleAddWorker() { |
| | | await waitWorkerGridReady(); |
| | | await workerGridApi.grid.insertAt({ _rowKey: -(++workerRowSeq) }, -1); |
| | | } |
| | | |
| | | /** 删除人员行 */ |
| | |
| | | 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); |
| | |
| | | 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 }) |
| | |
| | | } else { |
| | | await waitWorkerGridReady(); |
| | | await workerGridApi.grid.loadData([]); |
| | | await handleAddWorker(); |
| | | } |
| | | }, |
| | | }); |