gaoluyang
6 天以前 2b72d8220cf274e9b6586c7d4d9fae612fec3a14
src/views/basicData/product/index.vue
@@ -96,19 +96,17 @@
            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
          <el-select
            v-model="modelForm.unit"
            placeholder="请输入单位"
            placeholder="请选择单位"
            clearable
          />
            style="width: 100%"
          >
            <el-option label="件" value="件" />
            <el-option label="套" value="套" />
            <el-option label="台" value="台" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品属性" prop="productType">
          <el-select
@@ -121,6 +119,43 @@
            <el-option label="外购" :value="2" />
            <el-option label="委外" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item label="工艺路线" prop="routeId">
          <el-select
            v-model="modelForm.routeId"
            placeholder="请选择工艺路线"
            clearable
            style="width: 100%"
            filterable
          >
            <el-option
              v-for="item in processRouteList"
              :key="item.id"
              :label="item.processRouteName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="上传图纸" prop="drawingFile">
          <el-upload
            v-model:file-list="drawingFileList"
            :action="upload.url"
            :headers="upload.headers"
            :data="upload.data"
            :on-success="handleDrawingUploadSuccess"
            :on-remove="handleDrawingRemove"
            :before-upload="handleDrawingBeforeUpload"
            :limit="5"
            accept=".pdf,.jpg,.jpeg,.png,.dwg"
            list-type="picture-card"
          >
            <el-icon class="avatar-uploader-icon"><Plus /></el-icon>
            <template #tip>
              <div class="el-upload__tip">
                支持 pdf、jpg、jpeg、png、dwg 格式,大小不超过 10MB
              </div>
            </template>
          </el-upload>
        </el-form-item>
      </el-form>
    </FormDialog>
@@ -137,6 +172,7 @@
        :limit="1"
        accept=".xlsx,.xls"
        :action="importUpload.url"
        :http-request="importUpload.httpRequest"
        :headers="importUpload.headers"
        :before-upload="importUpload.beforeUpload"
        :on-success="importUpload.onSuccess"
@@ -162,10 +198,11 @@
</template>
<script setup>
import { ref, reactive } from "vue";
import { ref, reactive, onMounted } from "vue";
import axios from "axios";
import { ElMessageBox } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  addOrEditProductModel,
@@ -173,6 +210,7 @@
  productListPage,
  downloadTemplate,
} from "@/api/basicData/product.js";
import { listPage as getProcessRouteList } from "@/api/productionManagement/processRoute.js";
import ImportExcel from "./ImportExcel/index.vue";
const { proxy } = getCurrentInstance();
@@ -185,6 +223,14 @@
const tableLoading = ref(false);
const selectedRows = ref([]);
const modelFormRef = ref();
const processRouteList = ref([]);
const drawingFileList = ref([]);
const upload = reactive({
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  headers: { Authorization: "Bearer " + getToken() },
  data: { type: 13 },
});
const queryForm = reactive({
  productName: "",
@@ -210,13 +256,18 @@
    minWidth: 150,
  },
  {
    label: "规格型号",
    prop: "drawingNumber",
    minWidth: 150,
  },
  {
    label: "单位",
    prop: "unit",
    minWidth: 100,
  },
  {
    label: "工艺路线",
    prop: "routeName",
    minWidth: 100,
  },
  {
    label: "子项数量",
    prop: "subItemCount",
    minWidth: 100,
  },
  {
@@ -230,6 +281,16 @@
      return typeMap[String(v)] || "info";
    },
  },
   {
      label: "创建时间",
      prop: "createTime",
      width: 120,
   },
   {
      label: "修改时间",
      prop: "updateTime",
      width: 120,
   },
  {
    dataType: "action",
    label: "操作",
@@ -252,8 +313,11 @@
    productName: "",
    model: "",
    unit: "",
    drawingNumber: "",
    productType: null,
    routeId: null,
    drawingFile: "",
    tempFileIds: [],
    salesLedgerFiles: [],
  },
  modelRules: {
    productName: [
@@ -261,12 +325,34 @@
      { max: 50, message: "产品名称不能超过50个字符", trigger: "blur" },
    ],
    model: [{ required: true, message: "请输入图纸编号", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    drawingNumber: [],
    unit: [{ required: true, message: "请选择单位", trigger: "change" }],
    productType: [{ required: true, message: "请选择产品属性", trigger: "change" }],
  },
});
const { modelForm, modelRules } = toRefs(data);
const downloadImportErrorFile = (blob, filename = "import-error.xlsx") => {
  const downloadElement = document.createElement("a");
  const href = window.URL.createObjectURL(blob);
  downloadElement.href = href;
  downloadElement.download = filename;
  document.body.appendChild(downloadElement);
  downloadElement.click();
  document.body.removeChild(downloadElement);
  window.URL.revokeObjectURL(href);
};
const tryParseJsonBlob = async (blob) => {
  try {
    const text = await blob.text();
    if (!text || !text.trim()) {
      return null;
    }
    return JSON.parse(text);
  } catch (_) {
    return null;
  }
};
const importUpload = reactive({
  title: "产品导入",
@@ -274,6 +360,50 @@
  url: import.meta.env.VITE_APP_BASE_API + "/basic/product/import",
  headers: { Authorization: "Bearer " + getToken() },
  isUploading: false,
  httpRequest: async (options) => {
    const { file, onProgress, onSuccess, onError } = options;
    importUpload.isUploading = true;
    const formData = new FormData();
    formData.append("file", file);
    try {
      const response = await axios({
        url: importUpload.url,
        method: "post",
        headers: {
          ...importUpload.headers,
          "Content-Type": "multipart/form-data",
        },
        data: formData,
        responseType: "blob",
        onUploadProgress: (progressEvent) => {
          const total = progressEvent.total || 1;
          const percent = Math.round((progressEvent.loaded * 100) / total);
          onProgress?.({ percent }, file);
        },
      });
      importUpload.isUploading = false;
      const blob = response.data;
      // Contract: success => empty response body; failure => binary error file.
      if (!blob || blob.size === 0) {
        onSuccess?.({ code: 200, msg: "import success" }, file);
        return;
      }
      const json = await tryParseJsonBlob(blob);
      if (json) {
        if (String(json.code) === "200" || json.success === true) {
          onSuccess?.(json, file);
        } else {
          onError?.(new Error(json.msg || json.message || "import failed"), file);
        }
        return;
      }
      downloadImportErrorFile(blob);
      onError?.(new Error("import failed, error file downloaded"), file);
    } catch (error) {
      importUpload.isUploading = false;
      onError?.(error, file);
    }
  },
  beforeUpload: (file) => {
    const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
    const isLt10M = file.size / 1024 / 1024 < 10;
@@ -296,7 +426,7 @@
  onSuccess: (response, file, fileList) => {
    console.log('上传成功', response, file, fileList);
    importUpload.isUploading = false;
    if (response.code === 200) {
    if (String(response?.code) === "200" || response?.success === true) {
      proxy.$modal.msgSuccess("导入成功");
      importDia.value = false;
      if (importUploadRef.value) {
@@ -334,17 +464,42 @@
  modelForm.value.model = "";
  modelForm.value.id = "";
  modelForm.value.unit = "";
  modelForm.value.drawingNumber = "";
  modelForm.value.productType = null;
  modelForm.value.routeId = null;
  modelForm.value.drawingFile = "";
  modelForm.value.tempFileIds = [];
  modelForm.value.salesLedgerFiles = [];
  drawingFileList.value = [];
  if (type === "edit") {
    modelForm.value = { ...data };
    modelForm.value.tempFileIds = data.tempFileIds || [];
    modelForm.value.salesLedgerFiles = data.salesLedgerFiles || [];
    // 处理图纸文件反显
    if (data.salesLedgerFiles && data.salesLedgerFiles.length > 0) {
      drawingFileList.value = data.salesLedgerFiles.map(file => ({
        id: file.id,  // 带上id用于删除时调用接口
        name: file.name,
        url: file.url
      }));
    } else if (data.drawingFile) {
      drawingFileList.value = [{
        name: data.drawingFile.split('/').pop(),
        url: data.drawingFile
      }];
    }
  }
};
const submitModelForm = () => {
  modelFormRef.value.validate((valid) => {
    if (valid) {
      addOrEditProductModel(modelForm.value).then((res) => {
      // 构建提交数据,确保 routeId 为空时传 null,同时清空 routeName
      const submitData = {
        ...modelForm.value,
        routeId: modelForm.value.routeId || 0,
        routeName: modelForm.value.routeId ? modelForm.value.routeName : null
      };
      addOrEditProductModel(submitData).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeModelDia();
        getModelList();
@@ -429,7 +584,78 @@
  proxy.download("/basic/product/downloadTemplate", {}, "产品导入模板.xlsx");
};
getModelList();
const getProcessRouteListData = () => {
  getProcessRouteList({ current: 1, size: 1000 }).then((res) => {
    processRouteList.value = res.data.records || [];
  }).catch(() => {
    processRouteList.value = [];
  });
};
const handleDrawingBeforeUpload = (file) => {
  const isAllowed = [
    'application/pdf',
    'image/jpeg',
    'image/jpg',
    'image/png',
    'application/dwg'
  ].includes(file.type) || file.name.endsWith('.dwg');
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isAllowed) {
    proxy.$modal.msgError("只能上传 pdf、jpg、jpeg、png、dwg 格式的文件!");
    return false;
  }
  if (!isLt10M) {
    proxy.$modal.msgError("上传文件大小不能超过 10MB!");
    return false;
  }
  return true;
};
const handleDrawingUploadSuccess = (response, file, fileList) => {
  console.log('上传成功响应', response);
  console.log('response.data', response.data);
  if (response.code === 200) {
    // 支持多文件,追加到数组
    modelForm.value.tempFileIds.push(response.data?.tempId);
    modelForm.value.salesLedgerFiles.push({
      tempId: response.data?.tempId,
      originalName: response.data?.originalName || file.name,
      tempPath: response.data?.tempPath,
      type: response.data?.type || 13
    });
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(response.msg || "上传失败");
  }
};
const handleDrawingRemove = (file) => {
  // 如果是编辑模式下已存在的文件(带有id),调用删除接口
  if (file.id) {
    delLedgerFile({ id: file.id }).then(res => {
      if (res.code === 200) {
        proxy.$modal.msgSuccess("删除成功");
      }
    }).catch(err => {
      console.error("删除文件失败:", err);
    });
  }
  // 从数组中移除对应的文件
  const index = modelForm.value.salesLedgerFiles.findIndex(item =>
    item.tempId === file.response?.data?.tempId || item.tempId === file.tempId
  );
  if (index > -1) {
    modelForm.value.tempFileIds.splice(index, 1);
    modelForm.value.salesLedgerFiles.splice(index, 1);
  }
};
onMounted(() => {
  getModelList();
  getProcessRouteListData();
});
</script>
<style scoped>
@@ -469,6 +695,26 @@
  margin-top: 16px;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 148px;
  height: 148px;
  text-align: center;
  line-height: 148px;
}
:deep(.el-upload--picture-card) {
  width: 148px;
  height: 148px;
}
:deep(.el-upload__tip) {
  font-size: 12px;
  color: #909399;
  margin-top: 8px;
}
:deep(.el-dialog__body) {
  padding: 20px 24px;
}