| | |
| | | |
| | | import { useVbenModal } from "@vben/common-ui"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | import { Button, message, Progress } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { generateBagCodes } from "#/api/mes/pro/bagCode"; |
| | | import { getUnit } from "#/api/mdm/unit"; |
| | | import { MdmItemSelect } from "#/views/basicData/mdm/components"; |
| | | |
| | | import { isFinalTaskStatus, useGenerateTask } from "./use-generate-task"; |
| | | |
| | | defineOptions({ name: "MesProBagCodeGenerateForm" }); |
| | | |
| | |
| | | const unitTypeError = ref(false); |
| | | const palletCountVal = ref<number | null>(null); |
| | | const bagCountVal = ref<number | null>(null); |
| | | |
| | | /** 幂等键:打开弹窗时生成,网络重试复用;失败后重新生成 */ |
| | | const requestId = ref(""); |
| | | |
| | | function newRequestId() { |
| | | return `${Date.now()}-${Math.random().toString(36).slice(2)}-${Math.random() |
| | | .toString(36) |
| | | .slice(2)}`; |
| | | } |
| | | |
| | | /** 换算/配置提示文案 */ |
| | | const tipText = computed(() => { |
| | |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) return; |
| | | if (task.value && !isFinalTaskStatus(task.value.status)) return; |
| | | if (unitTypeError.value) { |
| | | message.warning(tipText.value); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | let asyncStarted = false; |
| | | try { |
| | | const values = |
| | | (await formApi.getValues()) as MesProBagCodeApi.GenerateParams; |
| | | const data: MesProBagCodeApi.GenerateParams = { |
| | | itemId: values.itemId, |
| | | remark: values.remark, |
| | | requestId: requestId.value, |
| | | }; |
| | | if (isTon.value) { |
| | | data.bagCount = values.bagCount; |
| | |
| | | data.palletCount = values.palletCount; |
| | | } |
| | | const result = await generateBagCodes(data); |
| | | if (result.async && result.taskId) { |
| | | asyncStarted = true; |
| | | modalApi.unlock(); |
| | | // 大批量已转后台:保持弹窗打开,展示进度并轮询 |
| | | startGenerateTask({ |
| | | id: result.taskId, |
| | | status: 10, |
| | | statusName: "排队中", |
| | | progress: 0, |
| | | totalCount: result.totalCount, |
| | | finishedCount: 0, |
| | | palletCount: result.palletCount, |
| | | uuid: result.uuid, |
| | | }); |
| | | return; |
| | | } |
| | | message.success( |
| | | `已成功生成 ${result.totalCount} 条袋码` + |
| | | (result.palletIds?.length |
| | | ? `,并自动生成 ${result.palletIds.length} 个托盘` |
| | | (result.palletCount |
| | | ? `,并自动生成 ${result.palletCount} 个托盘` |
| | | : ""), |
| | | ); |
| | | emit("success"); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | if (!asyncStarted) modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | if (!isOpen) { |
| | | stopGenerateTaskPolling(); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | resetGenerateTask(); |
| | | requestId.value = newRequestId(); |
| | | unitType.value = ""; |
| | | bagsPerPallet.value = null; |
| | | isTon.value = false; |
| | |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | const { |
| | | task, |
| | | canceling: generateTaskCanceling, |
| | | cancel: cancelGenerateTask, |
| | | reset: resetGenerateTask, |
| | | stopPolling: stopGenerateTaskPolling, |
| | | start: startGenerateTask, |
| | | } = useGenerateTask(modalApi, { |
| | | onSuccess: () => emit("success"), |
| | | onFailed: () => { |
| | | // 失败后换新的幂等键,允许用户直接重新提交 |
| | | requestId.value = newRequestId(); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="生成袋码数据包" class="w-2/5"> |
| | | <Form class="mx-4" /> |
| | | <div |
| | | v-if="tipText" |
| | | class="mx-4 mb-2 rounded bg-gray-50 px-3 py-2 text-sm" |
| | | :class="unitTypeError ? 'text-red-500' : 'text-gray-500'" |
| | | > |
| | | {{ tipText }} |
| | | </div> |
| | | <template v-if="task"> |
| | | <div class="mx-4 mb-2"> |
| | | <p class="mb-1 text-sm"> |
| | | 任务 #{{ task.id }} · {{ task.statusName }} |
| | | </p> |
| | | <p class="mb-2 text-sm text-gray-500"> |
| | | 已生成 {{ task.finishedCount ?? 0 }} / {{ task.totalCount ?? 0 }} 条 |
| | | </p> |
| | | <Progress |
| | | :percent="task.progress ?? 0" |
| | | :status=" |
| | | task.status === 50 |
| | | ? 'exception' |
| | | : task.status === 30 |
| | | ? 'success' |
| | | : 'active' |
| | | " |
| | | /> |
| | | <p v-if="task.errorMsg" class="mt-2 text-sm text-red-500"> |
| | | {{ task.errorMsg }} |
| | | </p> |
| | | <div class="mt-4 flex justify-end"> |
| | | <Button |
| | | danger |
| | | size="small" |
| | | :loading="generateTaskCanceling" |
| | | @click="cancelGenerateTask" |
| | | > |
| | | 取消生成 |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <template v-else> |
| | | <Form class="mx-4" /> |
| | | <div |
| | | v-if="tipText" |
| | | class="mx-4 mb-2 rounded bg-gray-50 px-3 py-2 text-sm" |
| | | :class="unitTypeError ? 'text-red-500' : 'text-gray-500'" |
| | | > |
| | | {{ tipText }} |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | | </template> |