gaoluyang
2 天以前 79fb20a2102f2d6a050b83f20477aa13b221f096
src/views/productionManagement/productStructure/Detail/index.vue
@@ -2,16 +2,21 @@
  <div class="app-container">
    <PageHeader content="产品结构详情">
      <template #right-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   @click="cancelEdit">取消
        </el-button>
        <el-button v-if="!dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="dataValue.isEdit = true">编辑
        </el-button>
        <el-button v-if="dataValue.isEdit && !isOrderPage"
                   type="primary"
                   @click="cancelEdit">取消
                   @click="dataValue.isEdit = true">点击进行修改
        </el-button>
        <el-button v-if="!isOrderPage"
                   type="primary"
                   @click="openBomAddDialog"
                   :disabled="!dataValue.isEdit">
          <el-icon><Document /></el-icon> 按BOM添加
        </el-button>
        <el-button v-if="!isOrderPage"
                   type="success"
                   :loading="dataValue.loading"
                   @click="submit"
                   :disabled="!dataValue.isEdit">确认
@@ -34,30 +39,30 @@
                      style="width: 100%">
              <el-table-column prop="productName"
                               label="产品" />
              <el-table-column prop="model"
                               label="规格">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-select v-model="row.model"
                               placeholder="请选择规格"
                               clearable
                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                               style="width: 100%"
                               @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                      <el-option v-if="row.model"
                                 :label="row.model"
                                 :value="row.model" />
                    </el-select>
                  </el-form-item>
                </template>
              </el-table-column>
                     <el-table-column prop="model"
                                              label="图纸编号">
                        <template #default="{ row, $index }">
                           <el-form-item v-if="dataValue.isEdit"
                                             :rules="[{ required: true, message: '请选择规格', trigger: ['blur','change'] }]"
                                             style="margin: 0">
                              <el-select v-model="row.model"
                                              placeholder="请选择规格"
                                              clearable
                                              :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
                                              style="width: 100%"
                                              @visible-change="(v) => { if (v) openDialog(row.tempId) }">
                                 <el-option v-if="row.model"
                                                 :label="row.model"
                                                 :value="row.model" />
                              </el-select>
                           </el-form-item>
                        </template>
                     </el-table-column>
              <el-table-column prop="processName"
                               label="消耗工序">
                               label="工序">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]"
                                :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选工序', trigger: 'change' }]"
                                style="margin: 0">
                    <el-select v-model="row.processId"
                               placeholder="请选择"
@@ -74,10 +79,10 @@
                </template>
              </el-table-column>
              <el-table-column prop="unitQuantity"
                               label="单位产出所需数量">
                               label="单位用量">
                <template #default="{ row, $index }">
                  <el-form-item v-if="dataValue.isEdit"
                                :rules="[{ required: true, message: '请输入单位产出所需数量', trigger: ['blur','change'] }]"
                                :rules="[{ required: true, message: '请输入单位用量', trigger: ['blur','change'] }]"
                                style="margin: 0">
                    <el-input-number v-model="row.unitQuantity"
                                     :min="0"
@@ -115,7 +120,7 @@
                    <el-input v-model="row.unit"
                              placeholder="请输入单位"
                              clearable
                               :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                              :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" />
                  </el-form-item>
                </template>
              </el-table-column>
@@ -143,12 +148,57 @@
                       prop="bomNo" />
      <el-table-column label="产品名称"
                       prop="productName" />
      <el-table-column label="规格型号"
                       prop="model" />
         <el-table-column label="图纸编号"
                                           prop="model" />
    </el-table>
    <product-select-dialog v-if="dataValue.showProductDialog"
                           v-model:model-value="dataValue.showProductDialog"
                           @confirm="handleProduct" />
    <!-- 按BOM添加弹窗 -->
    <el-dialog v-model="bomAddDialogVisible" title="按BOM添加" width="500px" @close="closeBomAddDialog">
      <el-form ref="bomAddFormRef" :model="bomAddForm" :rules="bomAddRules" label-width="100px">
        <el-form-item label="父项产品" prop="parentProductId">
          <el-select v-model="bomAddForm.parentProductId" placeholder="请选择" clearable filterable
            style="width: 100%" @change="handleBomParentProductChange">
            <el-option v-for="item in parentProductOptions" :key="item.id"
              :label="`${item.model || item.productCode || item.id} | ${item.productName}`"
              :value="item.id" />
          </el-select>
        </el-form-item>
        <!-- 选中产品后展示产品信息 -->
        <div v-if="selectedBomProduct" class="selected-product-info">
          <div class="product-info-row">
            <span class="info-label">产品编号</span>
            <span class="info-value">{{ selectedBomProduct.model || selectedBomProduct.productCode || '-' }}</span>
          </div>
          <div class="product-info-row">
            <span class="info-label">产品名称</span>
            <span class="info-value">{{ selectedBomProduct.productName || '-' }}</span>
          </div>
          <div class="product-info-row">
            <span class="info-label">产品规格</span>
            <span class="info-value">{{ selectedBomProduct.spec || selectedBomProduct.drawingNumber || '-' }}</span>
          </div>
          <div class="stock-info-box">
            <div class="stock-number">{{ selectedBomProduct.stockQuantity || 0 }}</div>
            <div class="stock-label">库存数量(台)</div>
          </div>
        </div>
        <el-form-item label="用量系数" prop="coefficient" style="margin-top: 20px;">
          <el-input-number v-model="bomAddForm.coefficient" :min="0.01" :precision="2" :step="1"
            controls-position="right" style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button type="primary" @click="submitBomAdd">确定</el-button>
        <el-button @click="closeBomAddDialog">取消</el-button>
      </template>
    </el-dialog>
    <!-- 高级选择产品弹窗 -->
    <product-select-dialog v-if="showAdvancedProductDialog" v-model:model-value="showAdvancedProductDialog"
      @confirm="handleAdvancedProductSelect" />
  </div>
</template>
@@ -161,11 +211,14 @@
    reactive,
    ref,
  } from "vue";
  import { queryList, add } from "@/api/productionManagement/productStructure.js";
  import { queryList, add, listByBomIdIsParent } from "@/api/productionManagement/productStructure.js";
  import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
  import { list } from "@/api/productionManagement/productionProcess";
  import { productListPage } from "@/api/basicData/product.js";
  import { listPage as listBomPage } from "@/api/productionManagement/productBom.js";
  import { ElMessage } from "element-plus";
  import { useRoute, useRouter } from "vue-router";
  import { Search, Document } from '@element-plus/icons-vue'
  defineComponent({
    name: "StructureEdit",
@@ -192,6 +245,7 @@
  // 从路由参数获取产品信息
  const routeBomNo = computed(() => route.query.bomNo || "");
  const routeProductName = computed(() => route.query.productName || "");
  const routeDrawingNumber = computed(() => route.query.drawingNumber || "");
  const routeProductModelName = computed(
    () => route.query.productModelName || ""
  );
@@ -212,9 +266,25 @@
    isEdit: false,
  });
  // 按BOM添加相关
  const bomAddDialogVisible = ref(false);
  const bomAddFormRef = ref();
  const parentProductOptions = ref([]);
  const selectedBomProduct = ref(null);
  const selectedBomTreeData = ref([]); // 保存选中产品时获取的BOM树数据
  const bomAddForm = reactive({
    parentProductId: undefined,
    coefficient:1
  });
  const bomAddRules = {
    parentProductId: [{ required: true, message: "请选择父项产品", trigger: "change" }],
    coefficient: [{ required: true, message: "请输入用量系数", trigger: "blur" }]
  };
  const tableData = reactive([
    {
      productName: "",
      drawingNumber: "",
      model: "",
      bomNo: "",
    },
@@ -264,15 +334,20 @@
    const productData = row[0];
    //  最外层组件中,与当前产品相同的产品只能有一个
    const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName);
    const isTopLevel = dataValue.dataList.some(
      item => (item as any).tempId === dataValue.currentRowName
    );
    if (isTopLevel) {
      if (productData.productName === tableData[0].productName &&
        productData.model === tableData[0].model) {
      if (
        productData.productName === tableData[0].productName &&
        productData.model === tableData[0].model
      ) {
        //  查找是否已经有其他顶层行已经是这个产品
        const hasOther = dataValue.dataList.some(item =>
          (item as any).tempId !== dataValue.currentRowName &&
          (item as any).productName === tableData[0].productName &&
          (item as any).model === tableData[0].model
        const hasOther = dataValue.dataList.some(
          item =>
            (item as any).tempId !== dataValue.currentRowName &&
            (item as any).productName === tableData[0].productName &&
            (item as any).model === tableData[0].model
        );
        if (hasOther) {
          ElMessage.warning("最外层和当前产品一样的一级只能有一个");
@@ -288,6 +363,7 @@
    dataValue.dataList.map(item => {
      if (item.tempId === dataValue.currentRowName) {
        item.productName = productData.productName;
        item.drawingNumber = productData.drawingNumber || "";
        item.model = productData.model;
        item.productModelId = productData.id;
        item.unit = productData.unit || "";
@@ -300,6 +376,7 @@
  const childItem = (item: any, tempId: any, productData: any) => {
    if (item.tempId === tempId) {
      item.productName = productData.productName;
      item.drawingNumber = productData.drawingNumber || "";
      item.model = productData.model;
      item.productModelId = productData.id;
      item.unit = productData.unit || "";
@@ -327,13 +404,8 @@
        isValid = false;
        return;
      }
      if (!isTopLevel && !item.processId) {
        ElMessage.error("请选择消耗工序");
        isValid = false;
        return;
      }
      if (!item.unitQuantity) {
        ElMessage.error("请输入单位产出所需数量");
        ElMessage.error("请输入单位用量");
        isValid = false;
        return;
      }
@@ -390,7 +462,7 @@
    }
  };
  const removeItem = (tempId:string) => {
  const removeItem = (tempId: string) => {
    // 先尝试从顶层删除
    const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
    if (topIndex !== -1) {
@@ -498,9 +570,155 @@
    fetchData();
  };
  // 获取父项产品列表
  const getParentProductList = async () => {
    try {
      const { data } = await listBomPage({ current: 1, size: 1000 });
      parentProductOptions.value = data?.records || [];
    } catch (error) {
      console.error("获取父项产品列表失败:", error);
      parentProductOptions.value = [];
    }
  };
  // 打开按BOM添加弹窗
  const openBomAddDialog = () => {
    bomAddDialogVisible.value = true;
    bomAddForm.parentProductId = undefined;
    bomAddForm.coefficient = 1;
    selectedBomProduct.value = null;
    getParentProductList();
  };
  // 关闭按BOM添加弹窗
  const closeBomAddDialog = () => {
    bomAddDialogVisible.value = false;
    bomAddFormRef.value?.resetFields();
    selectedBomProduct.value = null;
  };
  // 打开高级选择
  const openAdvancedSelect = () => {
    showAdvancedProductDialog.value = true;
  };
  // 父项产品变更
  const handleBomParentProductChange = async (val) => {
    if (val) {
      const product = parentProductOptions.value.find(item => item.id === val);
      selectedBomProduct.value = product || null;
      // 直接用选中的 BOM ID 调用 listByBomIdIsParent
      const { data: treeData } = await listByBomIdIsParent(val);
      selectedBomTreeData.value = treeData || [];
      // 将二级树中的产品添加到父项产品选项(从第二级开始)
      const addTreeToOptions = (items: any[]) => {
        items.forEach((item: any) => {
          // 跳过第一级,只添加第二级及以下的产品
          if (item.children && item.children.length > 0) {
            item.children.forEach((child: any) => {
              const exists = parentProductOptions.value.some(opt => opt.id === child.id);
              if (!exists) {
                parentProductOptions.value.push({
                  id: child.id,
                  productName: child.productName,
                  model: child.model,
                  productCode: child.model,
                  spec: child.drawingNumber,
                  drawingNumber: child.drawingNumber,
                  stockQuantity: child.stockQuantity || 0
                });
              }
              // 递归添加子项
              if (child.children && child.children.length > 0) {
                addTreeToOptions([child]);
              }
            });
          }
        });
      };
      if (selectedBomTreeData.value.length > 0) {
        addTreeToOptions(selectedBomTreeData.value);
      }
    } else {
      selectedBomProduct.value = null;
      selectedBomTreeData.value = [];
    }
  };
  // 提交按BOM添加
  const submitBomAdd = () => {
    bomAddFormRef.value.validate(async (valid) => {
      if (!valid) return;
      const product = parentProductOptions.value.find(item => item.id === bomAddForm.parentProductId);
      if (!product) {
        ElMessage.error("未找到选中的产品");
        return;
      }
      try {
        // 使用选择产品时保存的BOM树数据
        const bomItems = selectedBomTreeData.value || [];
        if (bomItems.length === 0) {
          ElMessage.warning("该产品没有BOM信息");
          return;
        }
        // 列表的第一级已经存在,把BOM数据作为第一级的子项(第二级)添加
        if (dataValue.dataList.length > 0) {
          const firstLevelItem = dataValue.dataList[0];
          // 把BOM数据添加到第一级的children中
          const addBomItemsRecursively = (items: any[], parentItem: any) => {
            items.forEach((item: any) => {
              const newItem: any = {
                parentId: item.parentId || "",
                parentTempId: parentItem.tempId || "",
                productName: item.productName || "",
                productId: item.productId || item.productModelId || "",
                model: item.model || "",
                productModelId: item.productModelId || "",
                drawingNumber: item.drawingNumber || "",
                processId: item.processId || "",
                processName: item.processName || "",
                unitQuantity: (item.unitQuantity || 0) * bomAddForm.coefficient,
                demandedQuantity: (item.demandedQuantity || 0) * bomAddForm.coefficient,
                unit: item.unit || "",
                children: [],
                tempId: new Date().getTime() + Math.random(),
              };
              // 添加到父项的children
              if (!parentItem.children) {
                parentItem.children = [];
              }
              parentItem.children.push(newItem);
              // 递归处理子项
              if (item.children && item.children.length > 0) {
                addBomItemsRecursively(item.children, newItem);
              }
            });
          };
          addBomItemsRecursively(bomItems, firstLevelItem);
        }
        ElMessage.success("添加成功");
        closeBomAddDialog();
      } catch (error) {
        console.error("按BOM添加失败:", error);
        ElMessage.error("添加失败");
      }
    });
  };
  onMounted(async () => {
    // 从路由参数回显数据
    tableData[0].productName = routeProductName.value as string;
    tableData[0].drawingNumber = routeDrawingNumber.value as string;
    tableData[0].model = routeProductModelName.value as string;
    tableData[0].bomNo = routeBomNo.value as string;
@@ -513,4 +731,56 @@
    await fetchProcessOptions();
    await fetchData();
  });
</script>
</script>
<style scoped>
.selected-product-info {
  background-color: #f5f7fa;
  border-radius: 4px;
  padding: 16px;
  margin: 10px 0;
  position: relative;
}
.product-info-row {
  display: flex;
  margin-bottom: 8px;
  font-size: 14px;
}
.product-info-row:last-child {
  margin-bottom: 0;
}
.info-label {
  color: #909399;
  width: 70px;
  flex-shrink: 0;
}
.info-value {
  color: #303133;
  flex: 1;
}
.stock-info-box {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  text-align: center;
}
.stock-number {
  font-size: 28px;
  font-weight: bold;
  color: #303133;
  line-height: 1;
  margin-bottom: 4px;
}
.stock-label {
  font-size: 12px;
  color: #909399;
}
</style>