gaoluyang
2026-08-12 c64bb94c7e1758f47e8c946e4656a71bb5fd53e8
src/views/mes/pro/workorder/modules/form.vue
@@ -2,14 +2,12 @@
  import type { FormType } from "../data";
  import type { MesProWorkOrderApi } from "#/api/mes/pro/workorder";
  import type { MesProWorkOrderBomApi } from "#/api/mes/pro/workorder/bom";
  import type { MesProWorkOrderProcessApi } from "#/api/mes/pro/workorder/process";
  import { computed, ref } from "vue";
  import { confirm, useVbenModal } from "@vben/common-ui";
  import {
    BarcodeBizTypeEnum,
    MesProWorkOrderStatusEnum,
  } from "@vben/constants";
@@ -23,12 +21,15 @@
    getWorkOrder,
    updateWorkOrder,
  } from "#/api/mes/pro/workorder";
  import { batchSaveWorkOrderProcess } from "#/api/mes/pro/workorder/process";
  import { batchSaveWorkOrderProcess, getWorkOrderProcessListByWorkOrderId } from "#/api/mes/pro/workorder/process";
  import { $t } from "#/locales";
  import { BarcodeDetail } from "#/views/wls/barcode/components";
  // import { BarcodeDetail } from "#/views/wls/barcode/components";
  import { useFormSchema } from "../data";
  import BomList from "./bom-list.vue";
  import PredictDelivery from "./ai/predict-delivery.vue";
  import PredictDuration from "./ai/predict-duration.vue";
  import PredictMaterial from "./ai/predict-material.vue";
  import PredictRisk from "./ai/predict-risk.vue";
  import ItemList from "./item-list.vue";
  import ProcessList from "./process-list.vue";
@@ -36,8 +37,8 @@
  const formType = ref<FormType>("create");
  const formData = ref<MesProWorkOrderApi.WorkOrder>();
  const originalSnapshot = ref("");
  const subTabsName = ref("bom");
  const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
  const subTabsName = ref("process");
  // const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
  const processListRef = ref<InstanceType<typeof ProcessList>>();
  const currentProductId = ref<number>(); // 当前选中的产品 ID(用于工序加载)
  const tempProcessList = ref<MesProWorkOrderProcessApi.WorkOrderProcess[]>([]); // 临时工序列表
@@ -52,6 +53,12 @@
    () =>
      formType.value === "update" &&
      formData.value?.status === MesProWorkOrderStatusEnum.PREPARE
  );
  const canUseAi = computed(
    () =>
      formData.value?.id &&
      (formData.value?.status === MesProWorkOrderStatusEnum.PREPARE ||
        formData.value?.status === MesProWorkOrderStatusEnum.CONFIRMED)
  );
  const getTitle = computed(() => {
    const isChild = !!formData.value?.parentId;
@@ -101,64 +108,55 @@
  });
  /** 重新挂载 schema 并按表单态控制底部按钮 */
  const mainActionText = computed(() => {
    if (isConfirm.value) return '确认';
    if (isFinish.value) return '完成';
    return '提交';
  });
  const showMainBtn = computed(() => isEditable.value || isConfirm.value || isFinish.value);
  function applySchema() {
    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
    const showBtn = isEditable.value || isConfirm.value || isFinish.value;
    modalApi.setState({
      showConfirmButton: showBtn,
      confirmText: isConfirm.value || canConfirm.value
        ? '确认'
        : isFinish.value
          ? '完成'
          : undefined,
    });
    modalApi.setState({ showCancelButton: false, showConfirmButton: false });
  }
  /** 仅保存(不改变状态) */
  async function handleSave() {
    const { valid } = await formApi.validate();
    if (!valid || !formData.value?.id) return;
    modalApi.lock();
    try {
      const data = (await formApi.getValues()) as MesProWorkOrderApi.WorkOrder;
      await updateWorkOrder({ ...formData.value, ...data });
      if (tempProcessList.value.length > 0) {
        const processList = tempProcessList.value.map(item => ({
          ...item,
          workOrderId: formData.value!.id,
        }));
        await batchSaveWorkOrderProcess(processList);
      }
      formData.value = { ...formData.value, ...data };
      originalSnapshot.value = JSON.stringify(await formApi.getValues());
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
    } finally {
      modalApi.unlock();
    }
  }
  /** 查看订单条码 */
  function handleBarcode() {
    if (!formData.value?.id) {
      return;
    }
    barcodeDetailRef.value?.openByBusiness(
      formData.value.id,
      BarcodeBizTypeEnum.WORKORDER,
      formData.value.code,
      formData.value.name
    );
  }
  /** 从 BOM 物料行预填子订单 */
  async function handleGenerateWorkOrder(
    bomRow: MesProWorkOrderBomApi.WorkOrderBom
  ) {
    const current = formData.value;
    if (!current) {
      return;
    }
    formData.value = undefined;
    formType.value = "create";
    subTabsName.value = "bom";
    applySchema();
    await formApi.resetForm();
    await formApi.setValues({
      clientId: current.clientId,
      name: `${bomRow.itemName}【${bomRow.quantity}】${
        bomRow.unitMeasureName || ""
      }`,
      orderSourceCode: current.orderSourceCode,
      orderSourceType: current.orderSourceType,
      parentId: current.id,
      productId: bomRow.itemId,
      quantity: bomRow.quantity,
      requestDate: current.requestDate,
      type: current.type,
      vendorId: current.vendorId,
    });
    // 设置当前产品 ID,触发工序加载
    currentProductId.value = bomRow.itemId;
    originalSnapshot.value = JSON.stringify(await formApi.getValues());
    message.info("已从 BOM 物料预填子订单,请补充订单编码等信息后保存");
  }
  // function handleBarcode() {
  //   if (!formData.value?.id) {
  //     return;
  //   }
  //   barcodeDetailRef.value?.openByBusiness(
  //     formData.value.id,
  //     BarcodeBizTypeEnum.WORKORDER,
  //     formData.value.code,
  //     formData.value.name
  //   );
  // }
  /** 工序列表变化回调 */
  function handleProcessListChange(list: MesProWorkOrderProcessApi.WorkOrderProcess[]) {
@@ -195,7 +193,13 @@
            }));
            await batchSaveWorkOrderProcess(processList);
          }
          // 3. 确认工单
          // 3. 校验是否有工序
          const processes = await getWorkOrderProcessListByWorkOrderId(formData.value.id);
          if (!processes || processes.length === 0) {
            message.warning('当前订单未配置工序,请先在工单中维护工序信息后再确认');
            return;
          }
          // 4. 确认工单
          await confirmWorkOrder(formData.value.id);
          message.success('订单已确认');
          await modalApi.close();
@@ -208,6 +212,12 @@
      // ---- 完成订单模式 ----
      if (isFinish.value) {
        if (!formData.value?.id) return;
        // 校验是否有工序
        const processes = await getWorkOrderProcessListByWorkOrderId(formData.value.id);
        if (!processes || processes.length === 0) {
          message.warning('当前订单未配置工序,请先在工单中维护工序信息后再完成');
          return;
        }
        try {
          await confirm('确认要完成该订单吗?');
        } catch {
@@ -289,7 +299,7 @@
        parentRow?: MesProWorkOrderApi.WorkOrder;
      }>();
      formType.value = data.formType;
      subTabsName.value = "bom";
      subTabsName.value = "process";
      tempProcessList.value = [];
      applySchema();
      if (data?.id) {
@@ -320,25 +330,16 @@
<template>
  <Modal :title="getTitle"
         class="w-3/5">
         class="w-3/5"
         content-class="!max-h-[80vh] overflow-y-auto">
    <Form class="mx-4" />
    <!-- BOM / 工序 / 物料需求 Tab -->
    <!-- 工序 / 物料需求 Tab -->
    <template v-if="showTabs">
      <Tabs v-model:active-key="subTabsName"
            class="mx-4">
        <Tabs.TabPane key="bom"
                      tab="订单 BOM">
          <BomList v-if="formData?.id"
                   :form-type="formType"
                   :work-order="formData"
                   :work-order-id="formData.id"
                   @generate-work-order="handleGenerateWorkOrder" />
          <div v-else class="text-gray-400 text-center py-8">
            请先保存工单后再配置 BOM
          </div>
        </Tabs.TabPane>
        <Tabs.TabPane key="process"
                      tab="工序">
                      tab="工序"
                      forceRender>
          <ProcessList
            ref="processListRef"
            :form-type="formType"
@@ -359,11 +360,36 @@
      </Tabs>
    </template>
    <template #prepend-footer>
      <Button v-if="formType === 'detail' && formData?.id"
              @click="handleBarcode">
        查看条码
      </Button>
      <div class="flex flex-auto flex-col gap-2">
        <div v-if="canUseAi" class="flex items-center gap-2">
          <PredictMaterial :work-order-id="formData!.id" />
          <PredictDuration :work-order-id="formData!.id" />
          <PredictRisk :work-order-id="formData!.id" />
          <PredictDelivery :work-order-id="formData!.id" />
        </div>
        <div v-if="canUseAi && showMainBtn" class="border-t border-gray-200" />
        <div class="flex items-center">
          <Button v-if="showMainBtn"
                  type="primary"
                  @click="modalApi.onConfirm()">
            {{ mainActionText }}
          </Button>
        </div>
      </div>
    </template>
    <template #append-footer>
      <div class="flex gap-2">
        <Button v-if="canConfirm"
                @click="handleSave">
          保存修改
        </Button>
        <!-- <Button v-if="formType === 'detail' && formData?.id"
                @click="handleBarcode">
          查看条码
        </Button> -->
        <Button @click="modalApi.close()">取消</Button>
      </div>
    </template>
  </Modal>
  <BarcodeDetail ref="barcodeDetailRef" />
  <!-- <BarcodeDetail ref="barcodeDetailRef" /> -->
</template>