gaoluyang
9 天以前 06bea42e538d820b1837c14ba87691a63bf51936
src/views/basicData/product/index.vue
@@ -1,396 +1,500 @@
<template>
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
            v-model="search"
            style="width: 210px"
            placeholder="输入关键字进行搜索"
            @change="searchFilter"
            @clear="searchFilter"
    <div class="main-content">
      <div class="search-section">
        <el-form :inline="true" :model="queryForm" class="search-form">
          <el-form-item label="产品名称">
            <el-input
              v-model="queryForm.productName"
              placeholder="请输入产品名称"
              clearable
              style="width: 200px"
              @keyup.enter="handleSearch"
            />
          </el-form-item>
          <el-form-item label="图纸编号">
            <el-input
              v-model="queryForm.model"
              placeholder="请输入图纸编号"
              clearable
              style="width: 200px"
              @keyup.enter="handleSearch"
            />
          </el-form-item>
          <el-form-item label="产品属性">
            <el-select
              v-model="queryForm.productType"
              placeholder="请选择产品属性"
              clearable
              style="width: 150px"
            >
              <el-option label="自制" :value="1" />
              <el-option label="外购" :value="2" />
              <el-option label="委外" :value="3" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="handleSearch" icon="Search">
              搜索
            </el-button>
            <el-button @click="handleReset" icon="Refresh">
              重置
            </el-button>
          </el-form-item>
        </el-form>
        <div class="action-buttons">
          <el-button type="primary" @click="openModelDia('add')" icon="Plus">
            新增产品
          </el-button>
          <el-button type="success" @click="handleImport" icon="Upload">
            导入
          </el-button>
          <el-button type="danger" @click="handleDelete" icon="Delete" plain>
            删除
          </el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      ></PIMTable>
    </div>
    <FormDialog
      v-model="modelDia"
      title="产品信息"
      width="500px"
      @close="closeModelDia"
      @confirm="submitModelForm"
    >
      <el-form
        :model="modelForm"
        label-width="100px"
        :rules="modelRules"
        ref="modelFormRef"
      >
        <el-form-item label="产品名称" prop="productName">
          <el-input
            v-model="modelForm.productName"
            placeholder="请输入产品名称"
            clearable
            prefix-icon="Search"
        />
        <el-button type="primary" @click="openProDia('addOne')" style="margin-left: 10px">新增产品大类</el-button>
      </div>
      <div ref="containerRef">
        <el-tree ref="tree" v-loading="treeLoad" :data="list" @node-click="handleNodeClick"
                 :expand-on-click-node="false" default-expand-all
                 :default-expanded-keys="expandedKeys" :draggable="true" :filter-node-method="filterNode"
                 :props="{ children: 'children', label: 'label' }" highlight-current node-key="id"
                 style="height: calc(100vh - 190px);overflow-y: scroll;scrollbar-width: none;">
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span>{{ node.label }}</span>
              <div>
                <el-button type="primary" link @click="openProDia('edit', data)">
                  编辑
                </el-button>
                <el-button type="primary" link @click="openProDia('add', data)">
                  添加产品
                </el-button>
                <el-button
                    v-if="!node.childNodes.length"
                    style="margin-left: 4px"
                    type="danger"
                    link
                    @click="remove(node, data)"
                >
                  删除
                </el-button>
              </div>
            </div>
          </template>
        </el-tree>
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px" v-if="isShowButton">
        <el-button type="primary" @click="openModelDia('add')">新增规格型号</el-button>
        <el-button type="danger" @click="handleDelete" style="margin-left: 10px" plain>删除</el-button>
      </div>
      <PIMTable :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" :handleSelectionChange="handleSelectionChange"
                :tableLoading="tableLoading" @pagination="pagination" :total="total"></PIMTable>
    </div>
    <el-dialog v-model="productDia" title="产品" width="400px">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品名称:" prop="productName">
              <el-input v-model="form.productName" placeholder="请输入产品名称" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
        <el-form-item label="图纸编号" prop="model">
          <el-input
            v-model="modelForm.model"
            placeholder="请输入图纸编号"
            clearable
          />
        </el-form-item>
        <el-form-item label="规格型号" prop="drawingNumber">
          <el-input
            v-model="modelForm.drawingNumber"
            placeholder="请输入规格型号"
            clearable
          />
        </el-form-item>
        <el-form-item label="单位" prop="unit">
          <el-input
            v-model="modelForm.unit"
            placeholder="请输入单位"
            clearable
          />
        </el-form-item>
        <el-form-item label="产品属性" prop="productType">
          <el-select
            v-model="modelForm.productType"
            placeholder="请选择产品属性"
            clearable
            style="width: 100%"
          >
            <el-option label="自制" :value="1" />
            <el-option label="外购" :value="2" />
            <el-option label="委外" :value="3" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeProDia">取消</el-button>
    </FormDialog>
    <FormDialog
      v-model="importDia"
      title="产品导入"
      width="600px"
      @cancel="importDia = false"
      @confirm="submitImport"
    >
      <el-upload
        ref="importUploadRef"
        :limit="1"
        accept=".xlsx,.xls"
        :action="importUpload.url"
        :headers="importUpload.headers"
        :before-upload="importUpload.beforeUpload"
        :on-success="importUpload.onSuccess"
        :on-error="importUpload.onError"
        :on-progress="importUpload.onProgress"
        :on-change="importUpload.onChange"
        :auto-upload="false"
        drag
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">
          将文件拖到此处,或<em>点击上传</em>
        </div>
      </template>
    </el-dialog>
    <el-dialog v-model="modelDia" title="规格型号" width="400px" @close="closeModelDia">
      <el-form :model="modelForm" label-width="140px" label-position="top" :rules="modelRules" ref="modelFormRef">
        <el-row>
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="model">
              <el-input v-model="modelForm.model" placeholder="请输入规格型号" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="modelForm.unit" placeholder="请输入单位" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitModelForm">确认</el-button>
          <el-button @click="closeModelDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
        <template #tip>
          <div class="el-upload__tip">
            仅支持 xls/xlsx,大小不超过 10MB。
            <el-button link type="primary" @click="importTemplate">下载导入模板</el-button>
          </div>
        </template>
      </el-upload>
    </FormDialog>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {ElMessageBox} from "element-plus";
import { ref, reactive } from "vue";
import { ElMessageBox } from "element-plus";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addOrEditProduct,
  addOrEditProductModel,
  delProduct, delProductModel,
  modelList,
  productTreeList
  delProduct,
  productListPage,
  downloadTemplate,
} from "@/api/basicData/product.js";
const { proxy } = getCurrentInstance()
const tree = ref(null)
const containerRef = ref(null)
import ImportExcel from "./ImportExcel/index.vue";
const productDia = ref(false)
const modelDia = ref(false)
const modelOperationType = ref('')
const search = ref('')
const currentId = ref('')
const currentParentId = ref('')
const operationType = ref('')
const treeLoad = ref(false)
const list = ref([])
const expandedKeys = ref([])
const { proxy } = getCurrentInstance();
const importUploadRef = ref(null);
const modelDia = ref(false);
const importDia = ref(false);
const modelOperationType = ref("");
const tableData = ref([]);
const tableLoading = ref(false);
const selectedRows = ref([]);
const modelFormRef = ref();
const queryForm = reactive({
  productName: "",
  model: "",
  productType: null,
});
const page = reactive({
  current: 1,
  size: 50,
  total: 0,
});
const tableColumn = ref([
  {
    label: '规格型号',
    prop: 'model',
    label: "产品名称",
    prop: "productName",
    minWidth: 200,
  },
  {
    label: '单位',
    prop: 'unit',
    label: "图纸编号",
    prop: "model",
    minWidth: 150,
  },
  {
    label: "规格型号",
    prop: "drawingNumber",
    minWidth: 150,
  },
  {
    label: "单位",
    prop: "unit",
    minWidth: 100,
  },
  {
    label: "产品属性",
    prop: "productType",
    width: 100,
    dataType: "tag",
    formatData: (v) => ({ "1": "自制", "2": "外购", "3": "委外" }[String(v)] ?? v),
    formatType: (v) => {
      const typeMap = { "1": "success", "2": "warning", "3": "info" };
      return typeMap[String(v)] || "info";
    },
  },
  {
    dataType: "action",
    label: "操作",
    align: 'center',
    align: "center",
    width: 100,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openModelDia('edit', row);
          openModelDia("edit", row);
        },
      },
    ],
  },
])
const tableData = ref([])
const tableLoading = ref(false)
const isShowButton = ref(false)
const total = ref(0)
const selectedRows = ref([])
const page = reactive({
  current: 1,
  size: 10,
})
]);
const data = reactive({
  form: {
    productName: '',
  },
  rules: {
    productName: [{ required: true, message: "请输入", trigger: "blur" }],
  },
  modelForm: {
    model: '',
    unit: '',
    productName: "",
    model: "",
    unit: "",
    drawingNumber: "",
    productType: null,
  },
  modelRules: {
    model: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    productName: [
      { required: true, message: "请输入产品名称", trigger: "blur" },
      { max: 50, message: "产品名称不能超过50个字符", trigger: "blur" },
    ],
    model: [{ required: true, message: "请输入图纸编号", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    drawingNumber: [],
    productType: [{ required: true, message: "请选择产品属性", trigger: "change" }],
  },
});
const { modelForm, modelRules } = toRefs(data);
const importUpload = reactive({
  title: "产品导入",
  open: false,
  url: import.meta.env.VITE_APP_BASE_API + "/basic/product/import",
  headers: { Authorization: "Bearer " + getToken() },
  isUploading: false,
  beforeUpload: (file) => {
    const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
    const isLt10M = file.size / 1024 / 1024 < 10;
    if (!isExcel) {
      proxy.$modal.msgError("上传文件只能是 xlsx/xls 格式!");
      return false;
    }
    if (!isLt10M) {
      proxy.$modal.msgError("上传文件大小不能超过 10MB!");
      return false;
    }
    return true;
  },
  onChange: (file, fileList) => {
    console.log('文件状态改变', file, fileList);
  },
  onProgress: (event, file, fileList) => {
    console.log('上传中...', event.percent);
  },
  onSuccess: (response, file, fileList) => {
    console.log('上传成功', response, file, fileList);
    importUpload.isUploading = false;
    if (response.code === 200) {
      proxy.$modal.msgSuccess("导入成功");
      importDia.value = false;
      if (importUploadRef.value) {
        importUploadRef.value.clearFiles();
      }
      getModelList();
    } else {
      proxy.$modal.msgError(response.msg || "导入失败");
    }
  },
  onError: (error, file, fileList) => {
    console.log('上传失败', error, file, fileList);
    importUpload.isUploading = false;
    proxy.$modal.msgError("导入失败");
  }
})
const { form, rules, modelForm, modelRules } = toRefs(data)
// 查询产品树
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList().then(res => {
    list.value = res
    list.value.forEach((a) => {
      expandedKeys.value.push(a.label);
    });
    treeLoad.value = false;
  }).catch(err => {
    treeLoad.value = false;
  })
}
// 过滤产品树
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
}
// 打开产品弹框
const openProDia = (type, data) => {
  operationType.value = type;
  productDia.value = true
  form.value.productName = ''
  if (type === 'edit') {
    form.value.productName = data.productName
  }
}
// 打开规格型号弹框
});
const handleSearch = () => {
  page.current = 1;
  getModelList();
};
const handleReset = () => {
  queryForm.productName = "";
  queryForm.model = "";
  queryForm.productType = null;
  page.current = 1;
  getModelList();
};
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true
  modelForm.value.model = ''
  modelForm.value.model = ''
  modelForm.value.id = ''
  if (type === 'edit') {
    modelForm.value = {...data}
  modelDia.value = true;
  modelForm.value.productName = "";
  modelForm.value.model = "";
  modelForm.value.id = "";
  modelForm.value.unit = "";
  modelForm.value.drawingNumber = "";
  modelForm.value.productType = null;
  if (type === "edit") {
    modelForm.value = { ...data };
  }
}
// 提交产品名称修改
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      if (operationType.value === 'add') {
        form.value.parentId = currentId.value
        form.value.id = ''
      } else if (operationType.value === 'addOne') {
        form.value.id = ''
        form.value.parentId = ''
      } else {
        form.value.id = currentId.value
        form.value.parentId = ''
      }
      addOrEditProduct(form.value).then(res => {
        proxy.$modal.msgSuccess("提交成功")
        closeProDia()
        getProductTreeList()
      })
    }
  })
}
// 关闭产品弹框
const closeProDia = () => {
  proxy.$refs.formRef.resetFields();
  productDia.value = false;
}
// 删除产品
const remove = (node, data) => {
  let ids = []
  ids.push(data.id)
  ElMessageBox.confirm(
      '选中的内容将被删除,是否确认删除?',
      '删除提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    tableLoading.value = true
    delProduct(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getProductTreeList()
    }).finally(() => {
      tableLoading.value = false
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// 选择产品
const handleNodeClick = (val, node, el) => {
  // 判断是否为叶子节点
  isShowButton.value = !(val.children && val.children.length > 0);
  // 只有叶子节点才执行以下逻辑
  currentId.value = val.id
  currentParentId.value = val.parentId
  getModelList()
}
};
// 提交规格型号修改
const submitModelForm = () => {
  proxy.$refs.modelFormRef.validate(valid => {
  modelFormRef.value.validate((valid) => {
    if (valid) {
      modelForm.value.productId = currentId.value
      addOrEditProductModel(modelForm.value).then(res => {
        proxy.$modal.msgSuccess("提交成功")
        closeModelDia()
        getModelList()
      })
      addOrEditProductModel(modelForm.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeModelDia();
        getModelList();
      });
    }
  })
}
// 关闭型号弹框
const closeModelDia = () => {
  proxy.$refs.modelFormRef.resetFields();
  modelDia.value = false;
}
// 表格选择数据
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
  });
};
// 查询规格型号
const pagination = ({ current, limit }) => {
  page.current = current;
  page.size = limit;
  getModelList()
}
const closeModelDia = () => {
  modelFormRef.value.resetFields();
  modelDia.value = false;
};
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
};
const getModelList = () => {
  tableLoading.value = true
  modelList({id: currentId.value}).then(res => {
    tableLoading.value = false
    tableData.value = res
  })
}
// 删除规格型号
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
      '选中的内容将被删除,是否确认删除?',
      '删除提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    tableLoading.value = true
    delProductModel(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getModelList()
    }).finally(() => {
      tableLoading.value = false
    })
  tableLoading.value = true;
  productListPage({
    productName: queryForm.productName.trim(),
    model: queryForm.model.trim(),
    productType: queryForm.productType,
    current: page.current,
    size: page.size,
  }).then((res) => {
    tableData.value = res.data.records;
    page.total = res.data.total;
    tableLoading.value = false;
  }).catch(() => {
    proxy.$modal.msg("已取消")
    tableLoading.value = false;
  });
};
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
}
// 调用tree过滤方法 中文英过滤
const filterNode = (value, data, node) => {
  if (!value) {    //如果数据为空,则返回true,显示所有的数据项
    return true
    .then(() => {
      tableLoading.value = true;
      delProduct(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getModelList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
const handleImport = () => {
  importDia.value = true;
  if (importUploadRef.value) {
    importUploadRef.value.clearFiles();
  }
  // 查询列表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase()
  return chooseNode(val, data, node) // 调用过滤二层方法
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true
  }
  const level = node.level
  // 如果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false
  }
  // 先取当前节点的父节点
  let parentData = node.parent
  // 遍历当前节点的父节点
  let index = 0
  while (index < level - 1) {
    // 如果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
      return true
    }
    // 否则的话再往上一层做匹配
    parentData = parentData.parent
    index++
  }
  // 没匹配到返回false
  return false
}
getProductTreeList()
};
const submitImport = () => {
  importUploadRef.value.submit();
};
const importTemplate = () => {
  proxy.download("/basic/product/downloadTemplate", {}, "产品导入模板.xlsx");
};
getModelList();
</script>
<style scoped>
.product-view {
  display: flex;
  padding: 20px;
  background: #f5f7fa;
  min-height: 100vh;
}
.left {
  width: 380px;
  padding: 16px;
.main-content {
  background: #ffffff;
  border-radius: 8px;
  padding: 24px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.right {
  width: calc(100% - 380px);
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
.search-section {
  margin-bottom: 20px;
  padding-bottom: 20px;
  border-bottom: 1px solid #e4e7ed;
}
.custom-tree-node {
  flex: 1;
.search-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
</style>
.search-form :deep(.el-form-item) {
  margin-bottom: 12px;
}
.action-buttons {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}
:deep(.el-dialog__body) {
  padding: 20px 24px;
}
:deep(.el-form-item__label) {
  font-weight: 500;
  color: #303133;
}
:deep(.el-input__inner) {
  border-radius: 4px;
}
:deep(.el-button) {
  border-radius: 4px;
  font-weight: 500;
}
:deep(.el-upload-dragger) {
  border-radius: 8px;
  border: 2px dashed #dcdfe6;
  transition: all 0.3s;
}
:deep(.el-upload-dragger:hover) {
  border-color: #409eff;
  background-color: #f5f7fa;
}
</style>