| src/components/Dialog/ImportDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/product/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productStructure/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/components/Dialog/ImportDialog.vue
@@ -8,17 +8,16 @@ > <el-upload ref="uploadRef" v-model:file-list="fileList" :limit="limit" :accept="accept" :headers="headers" :action="action" :disabled="disabled" :before-upload="beforeUpload" :on-progress="onProgress" :on-success="onSuccess" :on-error="onError" :on-change="onChange" :auto-upload="autoUpload" :http-request="handleHttpRequest" :on-change="onChange || (() => {})" :auto-upload="false" drag > <el-icon class="el-icon--upload"><UploadFilled /></el-icon> @@ -39,7 +38,7 @@ </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleConfirm">确 定</el-button> <el-button type="primary" @click="handleConfirm" :loading="uploading">确 定</el-button> <el-button @click="handleCancel">取 消</el-button> </div> </template> @@ -49,6 +48,9 @@ <script setup> import { computed, ref } from 'vue' import { UploadFilled } from '@element-plus/icons-vue' import axios from 'axios' import { getToken } from '@/utils/auth' import { ElMessage } from 'element-plus' const props = defineProps({ modelValue: { @@ -84,10 +86,6 @@ required: true }, disabled: { type: Boolean, default: false }, autoUpload: { type: Boolean, default: false }, @@ -129,19 +127,96 @@ }) const uploadRef = ref(null) const fileList = ref([]) const uploading = ref(false) const handleClose = () => { emit('close') } const handleConfirm = () => { emit('confirm') const handleHttpRequest = async (options) => { const { file, onProgress, onSuccess, onError } = options uploading.value = true const formData = new FormData() formData.append('file', file) try { const response = await axios({ url: props.action, method: 'post', data: formData, headers: { 'Authorization': 'Bearer ' + getToken(), ...props.headers, 'Content-Type': 'multipart/form-data' }, responseType: 'blob', onUploadProgress: (progressEvent) => { const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total) if (onProgress) { onProgress({ percent }) } if (props.onProgress) { props.onProgress({ percent }, file) } } }) uploading.value = false const blob = response.data // 先尝试将 blob 解析为 JSON(无论 content-type 是什么) try { const text = await blob.text() const jsonResponse = JSON.parse(text) // 成功解析为 JSON if (jsonResponse.code === 200) { if (onSuccess) onSuccess(jsonResponse) if (props.onSuccess) props.onSuccess(jsonResponse, file) } else { const error = new Error(jsonResponse.msg || '导入失败') error.response = { data: jsonResponse } if (onError) onError(error) if (props.onError) props.onError(jsonResponse, file) } } catch (e) { // 不是 JSON,是文件流,下载错误文件 downloadBlob(blob, '导入错误数据.xlsx') if (onError) { const error = new Error('导入失败,请查看下载的错误文件') error.response = { data: blob } onError(error) } if (props.onError) { props.onError(blob, file) } } } catch (error) { uploading.value = false if (options.onError) options.onError(error) if (props.onError) props.onError(error, file) } } const submit = () => { if (uploadRef.value) { uploadRef.value.submit() const downloadBlob = (blob, filename) => { 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 handleConfirm = () => { if (!fileList.value || fileList.value.length === 0) { ElMessage.warning('请选择文件') return } uploadRef.value.submit() } const handleCancel = () => { @@ -153,14 +228,15 @@ emit('download-template') } const clearFiles = () => { if (uploadRef.value) { uploadRef.value.clearFiles() } } defineExpose({ uploadRef, submit, clearFiles: () => { if (uploadRef.value) { uploadRef.value.clearFiles() } } clearFiles }) </script> @@ -169,4 +245,3 @@ text-align: center; } </style> src/views/basicData/product/index.vue
@@ -172,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" @@ -198,6 +199,7 @@ <script setup> 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"; @@ -314,12 +316,79 @@ }); 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: "产品导入", open: false, 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; @@ -342,7 +411,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) { @@ -621,11 +690,6 @@ } :deep(.el-upload--picture-card) { width: 148px; height: 148px; } :deep(.el-upload-list__item) { width: 148px; height: 148px; } src/views/productionManagement/productStructure/index.vue
@@ -45,7 +45,7 @@ <!-- 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" :on-success="handleFileSuccess" :on-error="handleFileError" :show-download-template="true" @confirm="submitFileForm" @download-template="handleDownloadTemplate" @close="handleImportClose" /> </div> </template> @@ -334,7 +334,7 @@ }; // 文件上传成功处理 const handleFileSuccess = (response, file, fileList) => { const handleFileSuccess = (response, file) => { upload.open = false; upload.isUploading = false; proxy.$refs["uploadRef"].clearFiles(); @@ -342,13 +342,42 @@ 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 }); proxy.$modal.msgError(response.msg || "导入失败"); } }; // 提交上传文件 // 文件上传失败处理 - 后端返回错误文件流 const handleFileError = (error, file) => { upload.open = false; upload.isUploading = false; proxy.$refs["uploadRef"].clearFiles(); // error 可能是 Blob 对象(后端返回的错误文件) if (error instanceof Blob) { // 下载错误文件 const blob = error; const downloadElement = document.createElement('a'); const href = window.URL.createObjectURL(blob); downloadElement.href = href; downloadElement.download = "导入错误数据.xlsx"; document.body.appendChild(downloadElement); downloadElement.click(); document.body.removeChild(downloadElement); window.URL.revokeObjectURL(href); proxy.$modal.msgError("导入失败,请查看下载的错误文件"); } else if (error && error.msg) { // 后端返回的错误信息 proxy.$modal.msgError(error.msg); } else { // 普通错误 proxy.$modal.msgError("导入失败"); } }; // 提交上传文件 - 现在由 ImportDialog 内部处理 const submitFileForm = () => { proxy.$refs["uploadRef"].submit(); // ImportDialog 的 handleConfirm 会调用 uploadRef.value.submit() // 这里不需要额外操作 }; // 导出按钮操作