云
7 天以前 21599300cc73ff7384b74cc5e4ac2cbd82bdc293
src/views/mes/pro/bagCode/modules/generate-form.vue
@@ -7,29 +7,40 @@
  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 emit = defineEmits(["success"]);
  // 所选品种主计量单位类型:48千克/100千克 -> 每托 N 袋;吨 -> 无托盘直接输袋码数
  // 所选品种主计量单位类型:48袋/100袋 -> 每托 N 袋;吨 -> 无托盘直接输袋码数
  const unitType = ref("");
  const kgPerPallet = ref<number | null>(null);
  const bagsPerPallet = ref<number | null>(null);
  const isTon = ref(false);
  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(() => {
    if (unitTypeError.value) {
      return "该品种主计量单位未配置类型(仅支持 48千克/100千克/吨),请先前往「计量单位」维护后重试";
      return "该品种主计量单位未配置类型(仅支持 48袋/100袋/吨),请先前往「计量单位」维护后重试";
    }
    if (!unitType.value) {
      return "";
@@ -39,7 +50,7 @@
        bagCountVal.value ? `,将生成 ${bagCountVal.value} 个袋码` : ""
      }`;
    }
    const per = kgPerPallet.value ?? 0;
    const per = bagsPerPallet.value ?? 0;
    const count = palletCountVal.value ?? 0;
    return `计量单位类型为「${unitType.value}」:每托 ${per} 袋${
      count > 0
@@ -61,7 +72,7 @@
        rules: "selectRequired",
      },
    ];
    if (kgPerPallet.value) {
    if (bagsPerPallet.value) {
      schema.push({
        fieldName: "palletCount",
        label: "托盘数",
@@ -110,7 +121,7 @@
  /** 选择品种后读取主计量单位类型,动态切换托盘数/袋码数输入 */
  async function handleItemChange(item?: MdmItemApi.Item) {
    unitType.value = "";
    kgPerPallet.value = null;
    bagsPerPallet.value = null;
    isTon.value = false;
    unitTypeError.value = false;
    palletCountVal.value = null;
@@ -123,9 +134,9 @@
        if (type === "吨") {
          isTon.value = true;
        } else {
          const matcher = /^(\d{1,5})千克$/.exec(type);
          const matcher = /^(\d{1,5})袋$/.exec(type);
          if (matcher) {
            kgPerPallet.value = Number(matcher[1]);
            bagsPerPallet.value = Number(matcher[1]);
          } else {
            unitTypeError.value = true;
          }
@@ -155,17 +166,20 @@
    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;
@@ -173,24 +187,45 @@
          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 = "";
        kgPerPallet.value = null;
        bagsPerPallet.value = null;
        isTon.value = false;
        unitTypeError.value = false;
        palletCountVal.value = null;
@@ -207,17 +242,67 @@
      }
    },
  });
  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>