<template>
|
<el-dialog
|
:title="title"
|
v-model="dialogVisible"
|
:width="width"
|
:append-to-body="appendToBody"
|
@close="handleClose"
|
>
|
<el-upload
|
ref="uploadRef"
|
v-model:file-list="fileList"
|
:limit="limit"
|
:accept="accept"
|
:headers="headers"
|
:action="action"
|
:disabled="disabled"
|
:before-upload="beforeUpload"
|
:http-request="handleHttpRequest"
|
:on-change="onChange || (() => {})"
|
:auto-upload="false"
|
drag
|
>
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
<template #tip>
|
<div class="el-upload__tip text-center">
|
<span>{{ tipText }}</span>
|
<el-link
|
v-if="showDownloadTemplate"
|
type="primary"
|
:underline="false"
|
style="font-size: 12px; vertical-align: baseline; margin-left: 5px;"
|
@click="handleDownloadTemplate"
|
>下载模板</el-link
|
>
|
</div>
|
</template>
|
</el-upload>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary" @click="handleConfirm" :loading="uploading">确 定</el-button>
|
<el-button @click="handleCancel">取 消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<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: {
|
type: Boolean,
|
default: false
|
},
|
title: {
|
type: String,
|
default: '导入'
|
},
|
width: {
|
type: String,
|
default: '400px'
|
},
|
appendToBody: {
|
type: Boolean,
|
default: true
|
},
|
limit: {
|
type: Number,
|
default: 1
|
},
|
accept: {
|
type: String,
|
default: '.xlsx, .xls'
|
},
|
headers: {
|
type: Object,
|
default: () => ({})
|
},
|
action: {
|
type: String,
|
required: true
|
},
|
disabled: {
|
type: Boolean,
|
default: false
|
},
|
tipText: {
|
type: String,
|
default: '仅允许导入xls、xlsx格式文件。'
|
},
|
showDownloadTemplate: {
|
type: Boolean,
|
default: true
|
},
|
beforeUpload: {
|
type: Function,
|
default: null
|
},
|
onProgress: {
|
type: Function,
|
default: null
|
},
|
onSuccess: {
|
type: Function,
|
default: null
|
},
|
onError: {
|
type: Function,
|
default: null
|
},
|
onChange: {
|
type: Function,
|
default: null
|
}
|
})
|
|
const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel', 'download-template'])
|
|
const dialogVisible = computed({
|
get: () => props.modelValue,
|
set: (val) => emit('update:modelValue', val)
|
})
|
|
const uploadRef = ref(null)
|
const fileList = ref([])
|
const uploading = ref(false)
|
|
const handleClose = () => {
|
emit('close')
|
}
|
|
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 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 = () => {
|
emit('cancel')
|
dialogVisible.value = false
|
}
|
|
const handleDownloadTemplate = () => {
|
emit('download-template')
|
}
|
|
const clearFiles = () => {
|
if (uploadRef.value) {
|
uploadRef.value.clearFiles()
|
}
|
}
|
|
defineExpose({
|
uploadRef,
|
clearFiles
|
})
|
</script>
|
|
<style scoped>
|
.dialog-footer {
|
text-align: center;
|
}
|
</style>
|