zhangwencui
4 小时以前 4be9b07e00bea78ae394aec67062b4db9895b565
src/views/productionManagement/productStructure/index.vue
@@ -1,380 +1,483 @@
<template>
  <div class="app-container">
    <div style="text-align: right; margin-bottom: 10px;">
      <el-button type="info" plain icon="Upload" @click="handleImport"
        v-hasPermi="['product:bom:import']">导入</el-button>
      <el-button type="warning" plain icon="Download" @click="handleExport" :disabled="selectedRows.length !== 1"
        v-hasPermi="['product:bom:export']">导出</el-button>
      <el-button type="primary" @click="handleAdd">新增</el-button>
      <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button>
      <el-button type="info"
                 plain
                 icon="Upload"
                 @click="handleImport"
                 v-hasPermi="['product:bom:import']">导入</el-button>
      <el-button type="warning"
                 plain
                 icon="Download"
                 @click="handleExport"
                 :disabled="selectedRows.length !== 1"
                 v-hasPermi="['product:bom:export']">导出</el-button>
      <el-button type="primary"
                 @click="handleAdd">新增</el-button>
      <el-button type="danger"
                 plain
                 @click="handleBatchDelete"
                 :disabled="selectedRows.length === 0">删除</el-button>
    </div>
    <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true"
      @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination">
    <PIMTable rowKey="id"
              :column="tableColumn"
              :tableData="tableData"
              :page="page"
              :isSelection="true"
              @selection-change="handleSelectionChange"
              :tableLoading="tableLoading"
              @pagination="pagination">
      <template #detail="{ row }">
        <el-button type="primary" text @click="showDetail(row)">{{ row.bomNo }}
        <el-button type="primary"
                   text
                   @click="showDetail(row)">{{ row.bomNo }}
        </el-button>
      </template>
    </PIMTable>
    <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow" />
    <StructureEdit v-if="showEdit"
                   v-model:show-model="showEdit"
                   :record="currentRow" />
    <!-- 新增/编辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? '新增BOM' : '编辑BOM'" width="600px"
      @close="closeDialog">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
        <el-form-item label="产品名称" prop="productModelId">
          <el-button type="primary" @click="showProductSelectDialog = true">
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增BOM' : '编辑BOM'"
               width="600px"
               @close="closeDialog">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="120px">
        <el-form-item label="产品名称"
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ form.productName || '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="版本号" prop="version">
          <el-input v-model="form.version" placeholder="请输入版本号" clearable />
        <el-form-item label="版本号"
                      prop="version">
          <el-input v-model="form.version"
                    placeholder="请输入版本号"
                    clearable />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" clearable />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="form.remark"
                    type="textarea"
                    :rows="3"
                    placeholder="请输入备注"
                    clearable />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="closeDialog">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
        <el-button type="primary"
                   @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
    <!-- 产品选择弹窗 -->
    <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single />
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <!-- BOM导入对话框 -->
    <ImportDialog ref="uploadRef" v-model="upload.open" :title="upload.title" :action="upload.url"
      :headers="upload.headers" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress"
      :on-success="handleFileSuccess" :show-download-template="true" @confirm="submitFileForm"
      @download-template="handleDownloadTemplate" @close="handleImportClose" />
    <ImportDialog ref="uploadRef"
                  v-model="upload.open"
                  :title="upload.title"
                  :action="upload.url"
                  :headers="upload.headers"
                  :disabled="upload.isUploading"
                  :on-progress="handleFileUploadProgress"
                  :on-success="handleFileSuccess"
                  :show-download-template="true"
                  @confirm="submitFileForm"
                  @download-template="handleDownloadTemplate"
                  @close="handleImportClose" />
  </div>
</template>
<script setup>
import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
import { getToken } from "@/utils/auth";
import { listPage, add, update, batchDelete, exportBom, downloadTemplate } from "@/api/productionManagement/productBom.js";
import { useRouter } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import ImportDialog from "@/components/Dialog/ImportDialog.vue";
  import {
    ref,
    reactive,
    toRefs,
    onMounted,
    getCurrentInstance,
    defineAsyncComponent,
  } from "vue";
  import { getToken } from "@/utils/auth";
  import {
    listPage,
    add,
    update,
    batchDelete,
    exportBom,
    downloadTemplate,
  } from "@/api/productionManagement/productBom.js";
  import { useRouter } from "vue-router";
  import { ElMessageBox } from "element-plus";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import ImportDialog from "@/components/Dialog/ImportDialog.vue";
const router = useRouter()
const { proxy } = getCurrentInstance()
const StructureEdit = defineAsyncComponent(() => import('@/views/productionManagement/productStructure/StructureEdit.vue'))
  const router = useRouter();
  const { proxy } = getCurrentInstance();
  const StructureEdit = defineAsyncComponent(() =>
    import("@/views/productionManagement/productStructure/StructureEdit.vue")
  );
const tableColumn = ref([
  {
    label: "BOM编号",
    prop: "bomNo",
    dataType: 'slot',
    slot: "detail",
    minWidth: 140
  },
  {
    label: "产品名称",
    prop: "productName",
  const tableColumn = ref([
    {
      label: "BOM编号",
      prop: "bomNo",
      dataType: "slot",
      slot: "detail",
      minWidth: 140,
    },
    {
      label: "产品名称",
      prop: "productName",
    minWidth: 160
  },
  {
    label: "规格型号",
    prop: "productModelName",
    minWidth: 140
  },
  {
    label: "版本号",
    prop: "version",
    width: 100
  },
  {
    label: "备注",
    prop: "remark",
    minWidth: 160
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          handleEdit(row)
        }
      },
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          handleDelete(row)
        }
      }
    ]
  }
]);
      minWidth: 160,
    },
    {
      label: "规格型号",
      prop: "productModelName",
      minWidth: 140,
    },
    {
      label: "版本号",
      prop: "version",
      width: 100,
    },
    {
      label: "备注",
      prop: "remark",
      minWidth: 160,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 150,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "删除",
          type: "danger",
          link: true,
          clickFun: row => {
            handleDelete(row);
          },
        },
      ],
    },
  ]);
const tableData = ref([]);
const tableLoading = ref(false);
const showEdit = ref(false);
const selectedRows = ref([]);
const currentRow = ref({});
const dialogVisible = ref(false);
const operationType = ref('add'); // add | edit
const formRef = ref(null);
const showProductSelectDialog = ref(false);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const showEdit = ref(false);
  const selectedRows = ref([]);
  const currentRow = ref({});
  const dialogVisible = ref(false);
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const showProductSelectDialog = ref(false);
//  BOM导入参数
const upload = reactive({
  // 是否显示弹出层(BOM导入)
  open: false,
  // 弹出层标题(BOM导入)
  title: "",
  // 是否禁用上传
  isUploading: false,
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // 上传的地址
  url: import.meta.env.VITE_APP_BASE_API + "/productBom/uploadBom"
});
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const data = reactive({
  form: {
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: ""
  },
  rules: {
    productModelId: [{ required: true, message: "请选择产品", trigger: "change" }],
    version: [{ required: true, message: "请输入版本号", trigger: "blur" }]
  }
});
const { form, rules } = toRefs(data);
// 表格选择数据
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// 分页
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
// 查询列表
const getList = () => {
  tableLoading.value = true;
  listPage({
    current: page.current,
    size: page.size,
  })
    .then((res) => {
      const records = res?.data?.records || [];
      tableData.value = records;
      page.total = res?.data?.total || 0;
    })
    .catch((err) => {
      console.error("获取列表失败:", err);
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
// 新增
const handleAdd = () => {
  operationType.value = 'add';
  Object.assign(form.value, {
    id: undefined,
    productName: "",
    productModelName: "",
    productModelId: "",
    remark: "",
    version: ""
  //  BOM导入参数
  const upload = reactive({
    // 是否显示弹出层(BOM导入)
    open: false,
    // 弹出层标题(BOM导入)
    title: "",
    // 是否禁用上传
    isUploading: false,
    // 设置上传的请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // 上传的地址
    url: import.meta.env.VITE_APP_BASE_API + "/productBom/uploadBom",
  });
  dialogVisible.value = true;
};
// 编辑
const handleEdit = (row) => {
  operationType.value = 'edit';
  Object.assign(form.value, {
    id: row.id,
    productName: row.productName || "",
    productModelName: row.productModelName || "",
    productModelId: row.productModelId || "",
    remark: row.remark || "",
    version: row.version || ""
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  dialogVisible.value = true;
};
// 删除(单条)
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该BOM?', '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      batchDelete([row.id])
        .then(() => {
          proxy.$modal.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy.$modal.msgError('删除失败');
        });
    })
    .catch(() => { });
};
// 批量删除
const handleBatchDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$modal.msgWarning('请选择数据');
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      batchDelete(ids)
        .then(() => {
          proxy.$modal.msgSuccess('删除成功');
          getList();
        })
        .catch(() => {
          proxy.$modal.msgError('删除失败');
        });
    })
    .catch(() => { });
};
// 产品选择
const handleProductSelect = (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    form.value.productModelId = product.id;
    form.value.productName = product.productName;
    form.value.productModelName = product.model;
  }
  showProductSelectDialog.value = false;
};
// 提交表单
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
      const payload = { ...form.value };
      if (operationType.value === 'add') {
        add(payload)
          .then(() => {
            proxy.$modal.msgSuccess('新增成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError('新增失败');
          });
      } else {
        update(payload)
          .then(() => {
            proxy.$modal.msgSuccess('修改成功');
            closeDialog();
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError('修改失败');
          });
      }
    }
  const data = reactive({
    form: {
      id: undefined,
      productName: "",
      productModelName: "",
      productModelId: "",
      remark: "",
      version: "",
    },
    rules: {
      productModelId: [
        { required: true, message: "请选择产品", trigger: "change" },
      ],
      version: [{ required: true, message: "请输入版本号", trigger: "blur" }],
    },
  });
};
// 关闭弹窗
const closeDialog = () => {
  dialogVisible.value = false;
  formRef.value?.resetFields();
};
  const { form, rules } = toRefs(data);
//  导入按钮操作
const handleImport = () => {
  upload.title = "BOM导入";
  upload.open = true;
};
  // 表格选择数据
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
// 关闭导入对话框时清除文件
const handleImportClose = () => {
  proxy.$refs["uploadRef"].clearFiles();
};
//  文件上传中处理
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
//  文件上传成功处理
const handleFileSuccess = (response, file, fileList) => {
  upload.open = false;
  upload.isUploading = false;
  proxy.$refs["uploadRef"].clearFiles();
  if (response.code === 200) {
    proxy.$modal.msgSuccess(response.msg || "导入成功");
  // 分页
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  } else {
    proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
  }
};
  };
// 提交上传文件
const submitFileForm = () => {
  proxy.$refs["uploadRef"].submit();
};
  // 查询列表
  const getList = () => {
    tableLoading.value = true;
    listPage({
      current: page.current,
      size: page.size,
    })
      .then(res => {
        const records = res?.data?.records || [];
        tableData.value = records;
        page.total = res?.data?.total || 0;
      })
      .catch(err => {
        console.error("获取列表失败:", err);
      })
      .finally(() => {
        tableLoading.value = false;
      });
  };
//  导出按钮操作
const handleExport = () => {
  if (selectedRows.value.length !== 1) {
    proxy.$modal.msgWarning("请选择一条数据进行导出");
    return;
  }
  // 新增
  const handleAdd = () => {
    operationType.value = "add";
    Object.assign(form.value, {
      id: undefined,
      productName: "",
      productModelName: "",
      productModelId: "",
      remark: "",
      version: "",
    });
    dialogVisible.value = true;
  };
  const bomId = selectedRows.value[0].id;
  const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`;
  // 编辑
  const handleEdit = row => {
    operationType.value = "edit";
    Object.assign(form.value, {
      id: row.id,
      productName: row.productName || "",
      productModelName: row.productModelName || "",
      productModelId: row.productModelId || "",
      remark: row.remark || "",
      version: row.version || "",
    });
    dialogVisible.value = true;
  };
  exportBom(bomId).then(res => {
    // 返回的数据是否为空
    if (!res) {
      proxy.$modal.msgError("导出失败,返回数据为空");
  // 删除(单条)
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该BOM?", "提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        batchDelete([row.id])
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("删除失败");
          });
      })
      .catch(() => {});
  };
  // 批量删除
  const handleBatchDelete = () => {
    if (!selectedRows.value.length) {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        batchDelete(ids)
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
          .catch(() => {
            proxy.$modal.msgError("删除失败");
          });
      })
      .catch(() => {});
  };
  // 产品选择
  const handleProductSelect = products => {
    if (products && products.length > 0) {
      const product = products[0];
      form.value.productModelId = product.skuId;
      form.value.productName = product.materialName;
      form.value.productModelName = product.specification;
    }
    showProductSelectDialog.value = false;
  };
  // 提交表单
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        const payload = { ...form.value };
        if (operationType.value === "add") {
          add(payload)
            .then(() => {
              proxy.$modal.msgSuccess("新增成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("新增失败");
            });
        } else {
          update(payload)
            .then(() => {
              proxy.$modal.msgSuccess("修改成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy.$modal.msgError("修改失败");
            });
        }
      }
    });
  };
  // 关闭弹窗
  const closeDialog = () => {
    dialogVisible.value = false;
    formRef.value?.resetFields();
  };
  //  导入按钮操作
  const handleImport = () => {
    upload.title = "BOM导入";
    upload.open = true;
  };
  // 关闭导入对话框时清除文件
  const handleImportClose = () => {
    proxy.$refs["uploadRef"].clearFiles();
  };
  //  文件上传中处理
  const handleFileUploadProgress = (event, file, fileList) => {
    upload.isUploading = true;
  };
  //  文件上传成功处理
  const handleFileSuccess = (response, file, fileList) => {
    upload.open = false;
    upload.isUploading = false;
    proxy.$refs["uploadRef"].clearFiles();
    if (response.code === 200) {
      proxy.$modal.msgSuccess(response.msg || "导入成功");
      getList();
    } else {
      proxy.$alert(
        "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
          response.msg +
          "</div>",
        "导入结果",
        { dangerouslyUseHTMLString: true }
      );
    }
  };
  // 提交上传文件
  const submitFileForm = () => {
    proxy.$refs["uploadRef"].submit();
  };
  //  导出按钮操作
  const handleExport = () => {
    if (selectedRows.value.length !== 1) {
      proxy.$modal.msgWarning("请选择一条数据进行导出");
      return;
    }
    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    const downloadElement = document.createElement('a');
    const bomId = selectedRows.value[0].id;
    const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`;
    exportBom(bomId)
      .then(res => {
        // 返回的数据是否为空
        if (!res) {
          proxy.$modal.msgError("导出失败,返回数据为空");
          return;
        }
        const blob = new Blob([res], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        const downloadElement = document.createElement("a");
        const href = window.URL.createObjectURL(blob);
        downloadElement.style.display = "none";
        downloadElement.href = href;
        downloadElement.download = fileName;
        document.body.appendChild(downloadElement);
        downloadElement.click();
        document.body.removeChild(downloadElement);
        window.URL.revokeObjectURL(href);
        proxy.$modal.msgSuccess("导出成功");
      })
      .catch(err => {
        console.error("导出异常:", err);
        proxy.$modal.msgError("系统异常,导出失败");
      });
  };
  //  下载模板
  const handleDownloadTemplate = async () => {
    const res = await downloadTemplate();
    // 返回的数据是否为空
    if (!res) {
      proxy.$modal.msgError("下载失败,返回数据为空");
      return;
    }
    const blob = new Blob([res], {
      type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    });
    const downloadElement = document.createElement("a");
    const href = window.URL.createObjectURL(blob);
    downloadElement.style.display = 'none';
    downloadElement.href = href;
    downloadElement.download = fileName;
    downloadElement.download = "BOM模板.xlsx";
    document.body.appendChild(downloadElement);
    downloadElement.click();
@@ -382,52 +485,23 @@
    document.body.removeChild(downloadElement);
    window.URL.revokeObjectURL(href);
    proxy.$modal.msgSuccess("导出成功");
  }).catch(err => {
    console.error("导出异常:", err);
    proxy.$modal.msgError("系统异常,导出失败");
    proxy.$modal.msgSuccess("下载成功");
  };
  // 查看详情
  const showDetail = row => {
    router.push({
      path: "/productionManagement/productStructureDetail",
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productName || "",
        productModelName: row.productModelName || "",
      },
    });
  };
  onMounted(() => {
    getList();
  });
};
//  下载模板
const handleDownloadTemplate = async () => {
  const res = await downloadTemplate();
  // 返回的数据是否为空
  if (!res) {
    proxy.$modal.msgError("下载失败,返回数据为空");
    return;
  }
  const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  const downloadElement = document.createElement('a');
  const href = window.URL.createObjectURL(blob);
  downloadElement.href = href;
  downloadElement.download = "BOM模板.xlsx";
  document.body.appendChild(downloadElement);
  downloadElement.click();
  document.body.removeChild(downloadElement);
  window.URL.revokeObjectURL(href);
  proxy.$modal.msgSuccess("下载成功");
};
// 查看详情
const showDetail = (row) => {
  router.push({
    path: '/productionManagement/productStructureDetail',
    query: {
      id: row.id,
      bomNo: row.bomNo || '',
      productName: row.productName || '',
      productModelName: row.productModelName || ''
    }
  });
};
onMounted(() => {
  getList();
});
</script>