zhangwencui
2 天以前 0ede6469f1507b81dd827efc36590cfd9773ebde
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -47,16 +47,45 @@
            <span class="info-value">{{ routeInfo.quantity || '-' }}</span>
          </div>
        </div>
        <div class="info-item full-width"
             v-if="routeInfo.description">
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">描述</span>
            <span class="info-label">备注</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.description }}</span>
          </div>
        </div>
      </div>
    </el-card>
    <!-- 附件模块 -->
    <div v-if="pageType === 'order'"
         class="section-header">
      <div class="section-title">附件</div>
    </div>
    <el-card v-if="pageType === 'order'"
             class="attachment-card"
             shadow="hover"
             style="margin-top: 10px; margin-bottom: 20px;">
      <el-table :data="attachmentTableData"
                border
                class="attachment-table">
        <el-table-column label="附件名称"
                         prop="originalFilename"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
                         label="操作"
                         width="200"
                         align="center">
          <template #default="scope">
            <el-button link
                       type="primary"
                       size="small"
                       @click="downloadAttachmentFile(scope.row.downloadURL)">
              下载
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- 表格视图 -->
    <div v-if="viewMode === 'table'"
@@ -111,6 +140,13 @@
      <el-table-column label="单位"
                       prop="unit"
                       width="100" />
      <el-table-column label="计费类型"
                       prop="type"
                       width="100">
        <template #default="scope">
          {{scope.row.type==0 ? "计时" : "计件"}}
        </template>
      </el-table-column>
      <el-table-column label="是否质检"
                       prop="isQuality"
                       width="100">
@@ -181,12 +217,16 @@
                  {{ item.model }}
                  <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
                </div>
                <el-tag class="product-tag"
                        :type="item.type == 1 ? 'primary' : 'success'"
                        style="margin-left: 8px;">{{ item.type==0?'计时':'计件' }}</el-tag>
                <el-tag type="primary"
                        class="product-tag"
                        style="margin-left: 8px;"
                        v-if="item.isQuality">质检</el-tag>
                <el-tag type="primary"
                        class="product-tag"
                        :style="item.isQuality?'margin-left:8px':''"
                        style="margin-left: 8px;"
                        v-if="item.isProduction">生产</el-tag>
              </div>
              <div v-else
@@ -304,7 +344,7 @@
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     @change="handleUnitQuantityChange(row)"
                                     @change="handleUnitQuantityChange"
                                     :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
                  </el-form-item>
                </template>
@@ -322,7 +362,7 @@
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
                                     :disabled="true" />
                  </el-form-item>
                </template>
              </el-table-column>
@@ -371,6 +411,18 @@
                         v-model="bomDataValue.showProductDialog"
                         :single="true"
                         @confirm="handleBomProduct" />
    <!-- 上传组件弹窗 -->
    <el-dialog v-model="uploadDialogVisible"
               title="上传附件"
               width="50%"
               @close="closeAttachmentUpload">
      <AttachmentUpload v-model:file-list="newFileList" />
      <template #footer>
        <el-button @click="saveAttachmentUpload"
                   type="primary">保存</el-button>
        <el-button @click="closeAttachmentUpload">关闭</el-button>
      </template>
    </el-dialog>
    <!-- 新增/编辑弹窗 -->
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
@@ -423,6 +475,13 @@
        <el-form-item label="单位"
                      v-else>
          <span>{{ form.unit }}</span>
        </el-form-item>
        <el-form-item label="计费类型"
                      prop="type">
          <el-radio-group v-model="form.type">
            <el-radio :label="0">计时</el-radio>
            <el-radio :label="1">计件</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="是否质检"
                      prop="isQuality">
@@ -500,6 +559,12 @@
    queryList2,
    add2,
  } from "@/api/productionManagement/productStructure.js";
  import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue";
  import {
    attachmentList,
    deleteAttachment,
    createAttachment,
  } from "@/api/basicData/storageAttachment.js";
  import { useRoute } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
@@ -512,6 +577,7 @@
  const orderId = computed(() => route.query.orderId);
  const pageType = computed(() => route.query.type);
  const editable = computed(() => route.query.editable !== "false");
  const technologyRoutingId = computed(() => route.query.technologyRoutingId);
  const tableLoading = ref(false);
  const tableData = ref([]);
@@ -530,7 +596,66 @@
    bomNo: "",
    description: "",
    quantity: 0,
    technologyRoutingId: "",
  });
  // 附件相关
  const attachmentTableData = ref([]);
  const uploadDialogVisible = ref(false);
  const newFileList = ref([]);
  const getAttachmentList = () => {
    if (!technologyRoutingId.value) return;
    attachmentList({
      recordType: "technology_routing",
      recordId: technologyRoutingId.value,
    }).then(res => {
      attachmentTableData.value = (res && res.data) || [];
    });
  };
  const handleUploadAttachment = () => {
    uploadDialogVisible.value = true;
  };
  const saveAttachmentUpload = async () => {
    if (newFileList.value.length > 0) {
      createAttachment({
        application: "file",
        recordType: "technology_routing",
        recordId: technologyRoutingId.value,
        storageBlobDTOs: [...newFileList.value, ...attachmentTableData.value],
      })
        .then(res => {
          if (res && res.code === 200) {
            proxy?.$modal?.msgSuccess("上传成功");
            newFileList.value = [];
            getAttachmentList();
          }
        })
        .finally(() => {
          uploadDialogVisible.value = false;
        });
    }
  };
  const closeAttachmentUpload = () => {
    newFileList.value = [];
    uploadDialogVisible.value = false;
  };
  const handleDeleteAttachment = async row => {
    deleteAttachment([row.storageAttachmentId]).then(res => {
      if (res && res.code === 200) {
        proxy?.$modal?.msgSuccess("删除成功");
        getAttachmentList();
      }
    });
  };
  const downloadAttachmentFile = url => {
    window.open(url, "_blank");
  };
  const processOptions = ref([]);
  const showProductSelectDialog = ref(false);
@@ -558,6 +683,7 @@
    model: "",
    unit: "",
    isQuality: false,
    type: 0,
    isProduction: false,
  });
@@ -661,6 +787,7 @@
      bomId: route.query.bomId || "",
      description: route.query.description || "",
      quantity: route.query.quantity || 0,
      technologyRoutingId: route.query.technologyRoutingId || "",
      status: !(route.query.status == 1 || route.query.status === "false"),
    };
    bomTableData.value[0].productName = routeInfo.value.productName;
@@ -687,6 +814,7 @@
      model: row.model || "",
      unit: row.unit || "",
      isQuality: row.isQuality,
      type: row.type || 0,
      isProduction: row.isProduction,
    };
    dialogVisible.value = true;
@@ -758,6 +886,7 @@
                operationName: getProcessName(form.value.technologyOperationId),
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
                dragSort,
              })
@@ -766,6 +895,7 @@
                technologyOperationId: form.value.technologyOperationId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
                dragSort,
              });
@@ -793,6 +923,7 @@
                operationName: getProcessName(form.value.technologyOperationId),
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
              })
            : addOrUpdateProcessRouteItem1({
@@ -801,6 +932,7 @@
                productModelId: form.value.productModelId,
                id: form.value.id,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
              });
@@ -832,6 +964,7 @@
      model: "",
      unit: "",
      isQuality: false,
      type: 0,
      isProduction: false,
    };
    formRef.value?.resetFields();
@@ -1064,18 +1197,97 @@
      }
    });
  };
  const toQuantityNumber = value => {
    const numberValue = Number(value);
    if (!Number.isFinite(numberValue)) {
      return 0;
    }
    return Number(numberValue.toFixed(2));
  };
  const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => {
    items.forEach(item => {
      if (parentDemandedQuantity !== null) {
        item.demandedQuantity = toQuantityNumber(
          parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
        );
      }
      if (Array.isArray(item.children) && item.children.length > 0) {
        syncDemandedQuantityTree(
          item.children,
          toQuantityNumber(item.demandedQuantity)
        );
      }
    });
  };
  const recalculateDemandedQuantities = () => {
    if (pageType.value !== "order") {
      return;
    }
    const rootDemandedQuantity = routeInfo.value.quantity;
    if (
      rootDemandedQuantity === undefined ||
      rootDemandedQuantity === null ||
      rootDemandedQuantity === ""
    ) {
      syncDemandedQuantityTree(bomDataValue.value.dataList);
      return;
    }
    syncDemandedQuantityTree(
      bomDataValue.value.dataList,
      toQuantityNumber(rootDemandedQuantity)
    );
  };
  const processChange = value => {
    processOptions.value.forEach(item => {
      if (item.id == value) {
        form.value.isQuality = item.isQuality;
        form.value.type = item.type || 0;
        form.value.isProduction = item.isProduction;
      }
    });
  };
  const findSiblings = (items, tempId) => {
    if (!items || items.length === 0) return null;
    // 检查当前层级
    if (items.some(item => item.tempId === tempId)) {
      return items;
    }
    // 递归查找子级
    for (const item of items) {
      if (item.children && item.children.length > 0) {
        const result = findSiblings(item.children, tempId);
        if (result) return result;
      }
    }
    return null;
  };
  const handleBomProcessChange = (row, value) => {
    row.processId = value || "";
    syncProcessOperationFields(row);
    // 检查同一层级是否已经有其他不同的工序被选中
    const siblings = findSiblings(bomDataValue.value.dataList, row.tempId);
    if (siblings && value) {
      const hasDifferentProcess = siblings.some(sibling => {
        return (
          sibling.tempId !== row.tempId &&
          sibling.processId &&
          sibling.processId !== value
        );
      });
      if (hasDifferentProcess) {
        ElMessage.warning("同一层级已存在不同的工序,请先统一工序后再进行修改");
      }
    }
  };
  const openBomDialog = tempId => {
@@ -1091,6 +1303,7 @@
      );
      bomDataValue.value.dataList = data || [];
      normalizeTreeData(bomDataValue.value.dataList);
      recalculateDemandedQuantities();
    } catch (err) {
      console.error("获取BOM数据失败:", err);
    }
@@ -1186,10 +1399,8 @@
    });
  };
  const handleUnitQuantityChange = row => {
    if (routeInfo.value.quantity && routeInfo.value.quantity !== 0) {
      row.demandedQuantity = (row.unitQuantity || 0) * routeInfo.value.quantity;
    }
  const handleUnitQuantityChange = () => {
    recalculateDemandedQuantities();
  };
  const addchildItem = (item, tempId) => {
@@ -1210,14 +1421,12 @@
          "",
        operationName: "",
        unitQuantity: 1,
        demandedQuantity:
          routeInfo.value.quantity && routeInfo.value.quantity !== 0
            ? 1 * routeInfo.value.quantity
            : 0,
        demandedQuantity: 0,
        children: [],
        unit: "",
        tempId: new Date().getTime(),
      });
      recalculateDemandedQuantities();
      return true;
    }
    if (item.children && item.children.length > 0) {
@@ -1249,14 +1458,12 @@
            "",
          operationName: "",
          unitQuantity: 1,
          demandedQuantity:
            routeInfo.value.quantity && routeInfo.value.quantity !== 0
              ? 1 * routeInfo.value.quantity
              : 0,
          demandedQuantity: 0,
          unit: "",
          children: [],
          tempId: new Date().getTime(),
        });
        recalculateDemandedQuantities();
        return;
      }
      addchildItem(item, tempId);
@@ -1296,9 +1503,36 @@
      }
    };
    // 校验同一层级的工序是否一致
    const validateProcessConsistency = items => {
      if (!items || items.length === 0) return;
      // 检查当前层级
      const processes = items
        .filter(item => item.processId)
        .map(item => item.processId);
      if (processes.length > 1) {
        const uniqueProcesses = [...new Set(processes)];
        if (uniqueProcesses.length > 1) {
          ElMessage.error("同一层级的工序必须一致");
          isValid = false;
          return;
        }
      }
      // 递归检查子级
      items.forEach(item => {
        if (item.children && item.children.length > 0) {
          validateProcessConsistency(item.children);
        }
      });
    };
    bomDataValue.value.dataList.forEach(item => {
      validateItem(item, true);
    });
    validateProcessConsistency(bomDataValue.value.dataList);
    return isValid;
  };
@@ -1324,6 +1558,7 @@
    console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList");
    normalizeTreeData(bomDataValue.value.dataList);
    recalculateDemandedQuantities();
    const valid = validateAllBom();
    if (valid) {
@@ -1335,7 +1570,7 @@
        .then(() => {
          ElMessage.success("BOM保存成功");
          bomDataValue.value.isEdit = false;
          fetchBomData();
          refreshCurrentPage();
        })
        .catch(() => {
          ElMessage.error("BOM保存失败");
@@ -1348,11 +1583,18 @@
    }
  };
  onMounted(() => {
  const refreshCurrentPage = () => {
    getRouteInfo();
    getList();
    getProcessList();
    fetchBomData();
    if (pageType.value === "order") {
      getAttachmentList();
    }
  };
  onMounted(() => {
    refreshCurrentPage();
  });
  onUnmounted(() => {
@@ -1619,4 +1861,4 @@
    line-height: 1.5;
    word-break: break-all;
  }
</style>
</style>