zhangwencui
2 天以前 000bbc47e9a2220d18f9cd69b35af23d4d838d46
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -38,16 +38,68 @@
            <span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
          </div>
        </div>
        <div class="info-item full-width"
             v-if="routeInfo.description">
        <div class="info-item"
             v-if="routeInfo.quantity && routeInfo.quantity !== 0">
          <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.quantity || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <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 class="section-actions">
        <el-button v-if="editable"
                   type="primary"
                   @click="handleUploadAttachment">
          上传附件
        </el-button>
      </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>
            <el-button v-if="editable"
                       link
                       type="danger"
                       size="small"
                       @click="handleDeleteAttachment(scope.row)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- 表格视图 -->
    <div v-if="viewMode === 'table'"
@@ -59,7 +111,8 @@
                   style="margin-right: 10px;">
          卡片视图
        </el-button>
        <el-button type="primary"
        <el-button v-if="editable"
                   type="primary"
                   @click="handleAdd">新增</el-button>
      </div>
    </div>
@@ -101,6 +154,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">
@@ -124,12 +184,12 @@
                     link
                     size="small"
                     @click="handleEdit(scope.row)"
                     :disabled="scope.row.isComplete">编辑</el-button>
                     :disabled="scope.row.isComplete || !editable">编辑</el-button>
          <el-button type="danger"
                     link
                     size="small"
                     @click="handleDelete(scope.row)"
                     :disabled="scope.row.isComplete">删除</el-button>
                     :disabled="scope.row.isComplete || !editable">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
@@ -143,7 +203,8 @@
                     style="margin-right: 10px;">
            表格视图
          </el-button>
          <el-button type="primary"
          <el-button v-if="editable"
                     type="primary"
                     @click="handleAdd">新增</el-button>
        </div>
      </div>
@@ -170,12 +231,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
@@ -187,7 +252,7 @@
                         link
                         size="small"
                         @click="handleEdit(item)"
                         :disabled="item.isComplete">编辑</el-button>
                         :disabled="item.isComplete || !editable">编辑</el-button>
              <el-button type="info"
                         link
                         size="small"
@@ -196,7 +261,7 @@
                         link
                         size="small"
                         @click="handleDelete(item)"
                         :disabled="item.isComplete">删除</el-button>
                         :disabled="item.isComplete || !editable">删除</el-button>
            </div>
          </div>
        </div>
@@ -206,8 +271,8 @@
    <div class="section-header"
         style="margin-top: 20px;">
      <div class="section-title">BOM 结构</div>
      <!-- <div class="section-actions"
           v-if="pageType === 'order'">
      <div class="section-actions"
           v-if="pageType === 'order' && editable">
        <el-button v-if="!bomDataValue.isEdit"
                   type="primary"
                   @click="bomDataValue.isEdit = true">
@@ -223,7 +288,7 @@
                   :loading="bomDataValue.loading">
          保存BOM
        </el-button>
      </div> -->
      </div>
    </div>
    <el-table :data="bomTableData"
              border
@@ -293,6 +358,7 @@
                                     :step="1"
                                     controls-position="right"
                                     style="width: 100%"
                                     @change="handleUnitQuantityChange"
                                     :disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
                  </el-form-item>
                </template>
@@ -310,7 +376,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>
@@ -359,6 +425,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' ? '新增工艺路线项目' : '编辑工艺路线项目'"
@@ -412,6 +490,13 @@
                      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">
          <el-switch v-model="form.isQuality"
@@ -437,7 +522,6 @@
                         @confirm="handleProductSelect"
                         single />
    <!-- 参数列表对话框 -->
    <!-- :editable="!routeInfo.status" -->
    <ProcessParamListDialog v-model="showParamListDialog"
                            :title="`${currentProcess ? (currentProcess.processName || currentProcess.technologyOperationName || currentProcess.operationName) : ''} - 参数列表`"
                            :route-id="routeId"
@@ -445,6 +529,7 @@
                            :process="currentProcess"
                            :page-type="pageType"
                            :param-list="paramList"
                            :editable="editable"
                            @getsyncProcessParamItem="getsyncProcessParamItem"
                            @refresh="refreshParamList" />
  </div>
@@ -486,8 +571,14 @@
  import {
    queryList,
    queryList2,
    addBomDetail,
    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";
@@ -499,6 +590,8 @@
  const routeId = computed(() => route.query.id);
  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([]);
@@ -516,7 +609,67 @@
    model: "",
    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);
@@ -544,6 +697,7 @@
    model: "",
    unit: "",
    isQuality: false,
    type: 0,
    isProduction: false,
  });
@@ -646,6 +800,8 @@
      bomNo: route.query.bomNo || "",
      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;
@@ -672,6 +828,7 @@
      model: row.model || "",
      unit: row.unit || "",
      isQuality: row.isQuality,
      type: row.type || 0,
      isProduction: row.isProduction,
    };
    dialogVisible.value = true;
@@ -743,6 +900,7 @@
                operationName: getProcessName(form.value.technologyOperationId),
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
                dragSort,
              })
@@ -751,6 +909,7 @@
                technologyOperationId: form.value.technologyOperationId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
                dragSort,
              });
@@ -778,6 +937,7 @@
                operationName: getProcessName(form.value.technologyOperationId),
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
              })
            : addOrUpdateProcessRouteItem1({
@@ -786,6 +946,7 @@
                productModelId: form.value.productModelId,
                id: form.value.id,
                isQuality: form.value.isQuality,
                type: form.value.type,
                isProduction: form.value.isProduction,
              });
@@ -817,6 +978,7 @@
      model: "",
      unit: "",
      isQuality: false,
      type: 0,
      isProduction: false,
    };
    formRef.value?.resetFields();
@@ -831,15 +993,19 @@
  // 查看参数列表
  const handleViewParams = row => {
    currentProcess.value = row;
    const query = {
    const param = {
      productionOrderRoutingOperationId: row.id,
      productionOrderId: orderId.value,
    };
    const param1 = {
      technologyRoutingOperationId: row.id,
      productionOrderId: orderId.value,
    };
    const apiPromise =
      pageType.value === "order"
        ? findProcessParamListOrder(query)
        : getProcessParamList(query);
        ? findProcessParamListOrder(param)
        : getProcessParamList(param1);
    apiPromise
      .then(res => {
@@ -862,6 +1028,7 @@
  // 初始化拖拽排序
  const initSortable = () => {
    destroySortable();
    if (!editable.value) return;
    if (viewMode.value === "table") {
      // 表格视图的拖拽排序
@@ -1044,18 +1211,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 => {
@@ -1071,6 +1317,7 @@
      );
      bomDataValue.value.dataList = data || [];
      normalizeTreeData(bomDataValue.value.dataList);
      recalculateDemandedQuantities();
    } catch (err) {
      console.error("获取BOM数据失败:", err);
    }
@@ -1166,6 +1413,10 @@
    });
  };
  const handleUnitQuantityChange = () => {
    recalculateDemandedQuantities();
  };
  const addchildItem = (item, tempId) => {
    if (item.tempId === tempId) {
      if (!item.children) {
@@ -1189,6 +1440,7 @@
        unit: "",
        tempId: new Date().getTime(),
      });
      recalculateDemandedQuantities();
      return true;
    }
    if (item.children && item.children.length > 0) {
@@ -1225,6 +1477,7 @@
          children: [],
          tempId: new Date().getTime(),
        });
        recalculateDemandedQuantities();
        return;
      }
      addchildItem(item, tempId);
@@ -1264,9 +1517,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;
  };
@@ -1292,17 +1572,19 @@
    console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList");
    normalizeTreeData(bomDataValue.value.dataList);
    recalculateDemandedQuantities();
    const valid = validateAllBom();
    if (valid) {
      addBomDetail({
        bomId: Number(routeInfo.value.bomId),
      add2({
        // bomId: Number(routeInfo.value.bomId),
        productionOrderBomId: Number(routeInfo.value.bomId) || null,
        children: buildSubmitTree(bomDataValue.value.dataList || []),
      })
        .then(() => {
          ElMessage.success("BOM保存成功");
          bomDataValue.value.isEdit = false;
          fetchBomData();
          refreshCurrentPage();
        })
        .catch(() => {
          ElMessage.error("BOM保存失败");
@@ -1315,11 +1597,18 @@
    }
  };
  onMounted(() => {
  const refreshCurrentPage = () => {
    getRouteInfo();
    getList();
    getProcessList();
    fetchBomData();
    if (pageType.value === "order") {
      getAttachmentList();
    }
  };
  onMounted(() => {
    refreshCurrentPage();
  });
  onUnmounted(() => {
@@ -1586,4 +1875,4 @@
    line-height: 1.5;
    word-break: break-all;
  }
</style>
</style>