1、目前的element的上传组件el-uload支持同时上传多个文件的功能,但是是一个文件上传需要后台调一次接口,这样是严重消耗性能的,不符合科学性。
2、于是针对目前在做的项目,自行封装一个组件,达到只调一次接口就可以上传多个文件,前提是接口支持多文件同时上传到服务器。
3、封装的一个弹框组件,代码如下:
<!--支持多文件上传 -->
<template>
<div>
<el-dialog
v-el-drag-dialog
append-to-body
:visible.sync="uploadVisible"
title="Upload"
width="600px"
:before-close="cancelForm"
>
<el-form
ref="form"
:model="uploadPayload"
:rules="rules"
size="small"
label-width="85px"
label-position="right"
>
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :lg="24">
<el-form-item :label="$t('steamship.selectFiles')+ ':'" prop="name">
<span slot="label">
<span style="color:red">*</span>
<span>{{ $t('steamship.selectFiles')+ ':' }}</span>
</span>
<el-upload
ref="upload"
class="upload-demo"
:action="`${urlPrefix}${uploadUrl}`"
:on-change="handleUploadChange"
:on-remove="remove"
:headers="headers"
:with-credentials="true"
:file-list="fileList"
:data="uploadPayload"
:auto-upload="false"
multiple
>
<el-button type="primary"><em class="el-icon-folder" />
{{ $t('steamship.selectFiles') }}
</el-button>
<div slot="tip" class="el-upload__tip">{{ uploadTip }}</div>
</el-upload>
<span v-if="showRemind" style="color:#ff4949">{{ remindTips }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<template v-if="ifHaveFilter">
<el-col :xs="24" :sm="24" :lg="12">
<el-form-item
:label="$t('steamship.selectVesselType')+ ':'"
prop="vesselType"
>
<el-select
v-model="uploadPayload.vesselType"
multiple
placeholder="Select vessel type"
>
<el-option
v-for="item in enums.vesselTypeEnum"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :lg="12">
<el-form-item :label="$t('steamship.selectFileType')+ ':'" prop="type">
<el-select v-model="uploadPayload.type" placeholder="Select report type">
<el-option
v-for="item in enums.reportTypeEnum"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
</template>
<el-col v-if="fromSource !== 'reportParsing'" :xs="24" :sm="24" :lg="24">
<el-form-item label="备注信息:" prop="remark">
<el-input
v-model="uploadPayload.remark"
type="textarea"
placeholder="请输入备注信息"
maxlength="300"
:autosize="{ minRows: 3, maxRows: 5}"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button plain size="small" @click="cancelForm">取消</el-button>
<el-button
:loading="loading"
type="primary"
style="margin-left: 10px;"
size="small"
@click="submitUpload"
>确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { upload } from '@/api/upload'
import { getDictionary, Es6duplicate } from '@/utils/commonTools'
import { getToken } from '@/utils/auth'
import store from '@/store'
import { getWXReportList, getRepParsList } from '@/api/system/report'
const reportParsingTip = 'Only .xls / .xlsx / .csv files can be uploaded, and no more than 20MB'
const otherTip = 'Only PDF / Excel / Docoment files can be uploaded, and no more than 20MB'
export default {
props: {
total: {
type: Number,
default: 10
},
fromSource: {
type: String,
default: ''
},
openDialog: {
type: Boolean,
default: false
},
ifHaveFilter: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
uploadTip: '',
uploadUrl: '',
showRemind: false,
remindTips: '',
uploadVisible: false,
headers: {
Authorization: getToken()
},
urlPrefix: process.env.VUE_APP_BASE_API,
fileList: [],
uploadPayload: {
vesselType: '',
type: '',
remark: ''
},
rules: {
vesselType: [{ required: true, message: '请选择船型', trigger: 'blur' }],
type: [{ required: true, message: '请选择报表类型', trigger: 'blur' }]
},
enums: {
vesselTypeEnum: [],
reportTypeEnum: []
},
totalList: [],
timer: null
}
},
watch: {
fromSource: {
handler(newVal) {
if (newVal === 'reportParsing') {
this.uploadTip = reportParsingTip
this.uploadUrl = '/parse/fileUpload'
this.uploadPayload = {
file: ''
}
} else {
this.uploadTip = otherTip
this.uploadUrl = '/report/wxupload'
this.uploadPayload = {
vesselType: '',
type: '',
remark: ''
}
}
}, immediate: true
},
openDialog(visible) {
if (visible) {
this.uploadVisible = visible
}
}
},
created() {
this.getEnums()
},
methods: {
getEnums() {
const enums = getDictionary(
{
vesselTypeEnum: 'vessel type', reportTypeEnum: 'report type'
}
)
Object.assign(this.enums, enums)
//这里是项目中会用的字典映射
},
// 上传弹框确认按钮
async submitUpload() {
console.log('fileList', this.fileList)
this.showRemind = false
// 如果没有选择文件时
if (this.fileList.length === 0) {
this.showRemind = true
this.remindTips = '请选择附件'
return
}
// 如果上传的文件格式或者文件大小不符合要求时
if (!this.beforeAvatarUpload()) return
// 如果上传的文件已经存在于服务器时
if (await this.ifFileNameExist(this.fileList)) return
this.$refs.form.validate(valid => {
if (valid) {
const formData = new FormData() // 用FormData存放上传文件
if (this.fromSource === 'reportParsing') {
this.fileList.forEach(file => {
formData.append('part', file.raw)
formData.append('file', file.name)
})
} else {
this.fileList.forEach(file => {
formData.append('part', file.raw)
})
if (!this.ifHaveFilter) {
this.uploadPayload.type = 2
this.uploadPayload.vesselType = store.state.settings.vesselType
}
formData.append('vesselType', this.uploadPayload.vesselType)
formData.append('type', this.uploadPayload.type)
formData.append('remark', this.uploadPayload.remark)
}
// this.$refs.upload.submit()
this.loading = true
upload(this.uploadUrl, formData).then(res => {
this.onSuccess(res)
this.loading = false
}).catch(errs => {
this.onFail(errs)
this.loading = false
})
this.$refs.form.resetFields()
}
})
},
// 如果上传的文件已经存在于服务器时
async ifFileNameExist(files) {
const params = {
pageNo: 1,
pageSize: this.total
}
const fn = this.fromSource === 'reportParsing' ? getRepParsList : getWXReportList
this.loading = true
await fn(params).then(res => {
this.totalList = res.data.dataList
this.loading = false
}).catch(errs => {
this.loading = false
})
const arr = []
files.map(item => {
if (this.totalList.some(sonItem => sonItem.fileName === item.name)) {
arr.push(item.name)
}
})
if (arr.length) {
this.showRemind = true
const str = arr.join('、')
this.remindTips = `${str} 在服务器中已经存在!`
}
return arr.length
},
// 上传弹框取消按钮
cancelForm() {
this.uploadVisible = false
this.showRemind = false
this.$refs.upload.clearFiles()
this.$refs.form.resetFields()
this.$emit('childComCallBack', false, 'cancel')
},
// 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
handleUploadChange(file, fileList) {
this.showRemind = false
this.fileList = fileList
// 上传文件去重
if (this.fileList.length > 1) {
this.fileList = Es6duplicate(this.fileList, 'name')
}
},
// 文件列表移除文件时的钩子
remove(file, fileList) {
this.fileList = fileList
},
// 上传失败
onFail() {
this.$message.error('Upload failed')
return false
},
// 上传成功
onSuccess(result, file, fileList) {
let type = ''
if (result.code === 0) { // 上传文件全部成功
this.uploadVisible = false
this.$message({
message: result.message,
type: 'success'
})
this.$refs.upload.clearFiles()
this.fileList = []
type = 'success'
} else if (result.code === 200) { // 同时上传多个文件,出现有成功的、有失败的情况
this.uploadVisible = false
this.$message({
message: result.message,
type: 'warning'
})
this.$refs.upload.clearFiles()
this.fileList = []
type = 'success'
} else { // 上传文件全部失败
this.$message({
message: result.message,
type: 'error'
})
this.fileList = []
type = 'error'
}
this.$emit('childComCallBack', false, type)
},
// 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
beforeAvatarUpload() {
const typeArr = []
const sizeArr = []
this.fileList.forEach(item => {
if (item.name.endsWith('.pdf')) {
typeArr.push('isPDF')
} else if (item.name.endsWith('.csv') || item.name.endsWith('.CSV')) {
typeArr.push('isCSV')
} else if (item.name.endsWith('.xlsx') || item.name.endsWith('.xls')) {
typeArr.push('isExcel')
} else if (item.name.endsWith('.docx') || item.name.endsWith('.doc')) {
typeArr.push('isWord')
} else {
typeArr.push('other')
}
sizeArr.push(item.size / 1024 / 1024 < 20)
})
let ifHasTrue = false
let isCheckOK = true
if (this.fromSource === 'reportParsing') { // 文件解析只支持xsl,xslx,csv,CSV
ifHasTrue = typeArr.every(item => {
return ['isExcel', 'isCSV'].some(e => e === item)
})
} else { // 文件管理除了csv,CSV外,其他的都支持
ifHasTrue = typeArr.every(item => {
return ['isPDF', 'isExcel', 'isWord'].some(e => e === item)
})
}
// 上传的文件格式不符合要求时
if (!ifHasTrue) {
this.showRemind = true
this.remindTips = this.fromSource === 'reportParsing' ? reportParsingTip : otherTip
isCheckOK = false
}
if (sizeArr.some(item => !item)) {
this.showRemind = true
this.remindTips = 'The size of the uploaded file cannot exceed 20MB!'
isCheckOK = false
}
return isCheckOK
}
}
}
</script>
<style lang='scss' scoped>
</style>
4、用到了我所在项目的数据字典映射,这个可以依情况是否需要使用,接口也是根据自己项目而定,其中的逻辑可以用来参考。