smart-bid-web/src/views/common/UploadMoreFile.vue

489 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="upload-container">
<el-upload ref="upload" class="upload-demo" drag action="#" multiple :show-file-list="true"
:before-upload="beforeUpload" :on-remove="handleRemove" :on-change="handleFileChange"
:on-exceed="handleExceed" :file-list="files" :accept="accept" :limit="limitUploadNum"
:auto-upload="autoUpload" :http-request="customUpload" :disabled="disabled">
<div class="upload-content">
<!-- 默认上传区域 -->
<div class="upload-text">
<div class="main-text"> + 点击或将文件拖拽到这里上传</div>
<div class="tip-text">
<div>最多可上传 {{ limitUploadNum }} 个文件</div>
<div>单份文件大小上限 {{ maxFileTips }}</div>
<div>支持文件类型{{ uploadType }}</div>
</div>
</div>
</div>
</el-upload>
</div>
</template>
<script>
import {
uploadSmallFileByOcr,
uploadLargeFileByOcr,
uploadSmallFile,
uploadLargeFile,
} from '@/api/common/uploadFile.js'
export default {
name: 'UploadFile',
props: {
fileList: {
type: Array,
default: () => [],
},
maxFileTips: {
type: String,
default: '20MB',
},
uploadType: {
type: String,
default: 'png、jpg、jpeg',
},
limitUploadNum: {
type: Number,
default: 5,
},
fileUploadRule: {
type: Object,
default: () => ({}),
},
autoUpload: {
type: Boolean,
default: true,
},
type: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
files: [],
isUploading: false,
uploadingFiles: new Set(),
defaultFileSize: 1024 * 1024 * 5,
uploadControllers: new Map(),
}
},
computed: {
accept() {
return this.uploadType
.split('、')
.map((type) => `.${type}`)
.join(',')
},
allowedTypes() {
return this.uploadType.split('、')
},
maxSizeMB() {
const sizeStr = this.maxFileTips.toLowerCase()
if (sizeStr.includes('mb')) {
return parseFloat(sizeStr)
} else if (sizeStr.includes('kb')) {
return parseFloat(sizeStr) / 1024
} else if (sizeStr.includes('gb')) {
return parseFloat(sizeStr) * 1024
}
return 20
},
mimeTypes() {
const typeMap = {
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
pdf: 'application/pdf',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}
return this.allowedTypes.map((type) => typeMap[type] || '')
},
},
watch: {
fileList: {
handler(newVal) {
if (this.files.length === 0 && newVal.length > 0) {
this.$nextTick(() => {
if (this.$refs.upload) {
this.$refs.upload.uploadFiles = this.formatFileList(newVal)
}
this.files = this.formatFileList(newVal)
})
}
},
immediate: true,
deep: true
},
},
methods: {
beforeUpload(file) {
const fileExtension = file.name.split('.').pop().toLowerCase()
const isAllowedType = this.allowedTypes.includes(fileExtension)
const isAllowedMimeType = this.mimeTypes.includes(file.type)
const isLtMaxSize = file.size / 1024 / 1024 < this.maxSizeMB
if (!isAllowedType || !isAllowedMimeType) {
this.$message.error(`只能上传 ${this.uploadType} 格式的文件!`)
return false
}
if (!isLtMaxSize) {
this.$message.error(`文件大小不能超过 ${this.maxFileTips}!`)
return false
}
if (this.files.length >= this.limitUploadNum) {
this.$message.error(`最多只能上传 ${this.limitUploadNum} 个文件!`)
return false
}
return true
},
handleFileChange(file, fileList) {
if (file.status === 'removed' || file.status === 'fail' || file.status === 'success') {
return
}
this.files = this.formatFileList(fileList)
if (this.autoUpload && file.status === 'ready' && !this.isUploading) {
this.$nextTick(() => {
this.uploadFile(file)
})
}
},
async uploadFile(file) {
if (this.uploadingFiles.has(file.uid)) {
return
}
const controller = new AbortController()
this.uploadControllers.set(file.uid, controller)
this.uploadingFiles.add(file.uid)
this.isUploading = true
const formData = new FormData()
formData.append('file', file.raw)
formData.append('params', JSON.stringify(this.fileUploadRule))
this.updateFileStatus(
file.uid,
'uploading',
this.fileUploadRule.fields_json ? '识别中' : '上传中',
null,
0,
)
try {
this.$bus.$emit(
'startUpload',
this.fileUploadRule.fields_json ? '识别中' : '上传中',
)
let res = null
if (this.defaultFileSize < file.size) {
if (this.fileUploadRule.fields_json) {
res = await uploadLargeFileByOcr(formData, {
signal: controller.signal,
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
const percentComplete = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
this.updateFileStatus(file.uid, 'uploading', '', null, percentComplete)
}
}
})
} else {
res = await uploadLargeFile(formData, {
signal: controller.signal,
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
const percentComplete = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
this.updateFileStatus(file.uid, 'uploading', '', null, percentComplete)
}
}
})
}
} else {
if (this.fileUploadRule.fields_json) {
res = await uploadSmallFileByOcr(formData, {
signal: controller.signal
})
} else {
res = await uploadSmallFile(formData, {
signal: controller.signal
})
}
}
if (res.code === 200) {
this.handleSuccess(res, file)
} else {
this.handleError(new Error(res.message || '上传失败'), file)
}
} catch (err) {
if (err.name === 'AbortError') {
console.log('上传已取消:', file.name)
} else {
this.handleError(err, file)
}
} finally {
this.uploadingFiles.delete(file.uid)
this.uploadControllers.delete(file.uid)
this.isUploading = this.uploadingFiles.size > 0
}
},
async customUpload(options) {
const { file } = options
const uploadFileObj = this.findFileByRawFile(file)
if (!uploadFileObj) {
console.error('未找到对应的文件对象')
return
}
this.uploadFile(uploadFileObj)
},
handleSuccess(response, file) {
this.$bus.$emit('endUpload')
this.updateFileStatus(file.uid, 'success', '', response.data, 100)
this.$emit('file-change', this.getCurrentFiles(), this.type)
},
handleError(error, file) {
console.error('上传失败:', error)
this.$bus.$emit('endUpload')
this.updateFileStatus(file.uid, 'fail', error.message)
this.$emit('file-change', this.getCurrentFiles(), this.type)
},
handleExceed(files, fileList) {
this.$message.warning(`最多只能上传 ${this.limitUploadNum} 个文件,已自动截取前 ${this.limitUploadNum} 个文件`);
// 将 FileList 转换为数组
const filesArray = Array.from(files);
const remainingSlots = this.limitUploadNum - this.files.length;
if (remainingSlots > 0) {
const filesToAdd = filesArray.slice(0, remainingSlots);
filesToAdd.forEach(file => {
const newFileObj = this.createFileObject(file);
this.files.push(newFileObj);
if (this.autoUpload) {
this.$nextTick(() => {
this.uploadFile(newFileObj);
});
}
});
}
},
handleRemove(file, fileList) {
if (file == null) {
return true
}
if (file.status === 'uploading') {
this.cancelUpload(file)
}
const delFileObj = this.findFileByRawFile(file.raw)
if (delFileObj) {
delFileObj.response = delFileObj.res
this.$emit('del-file', delFileObj)
}
this.files = this.formatFileList(fileList)
this.$emit('file-change', this.getCurrentFiles(), this.type)
return true
},
cancelUpload(file) {
const controller = this.uploadControllers.get(file.uid)
if (controller) {
controller.abort()
}
this.updateFileStatus(file.uid, 'fail', '上传已取消')
this.uploadingFiles.delete(file.uid)
this.uploadControllers.delete(file.uid)
},
createFileObject(file) {
return {
name: file.name,
size: file.size,
type: file.type,
raw: file,
uid: Date.now() + Math.random(),
status: 'ready',
percentage: 0,
}
},
findFileByRawFile(rawFile) {
return this.files.find(item =>
item.raw === rawFile ||
(item.name === rawFile.name && item.size === rawFile.size)
)
},
updateFileStatus(fileUid, status, statusText, responseData = null, percentage = null) {
const fileIndex = this.files.findIndex(item => item.uid === fileUid)
if (fileIndex !== -1) {
const updatedFile = {
...this.files[fileIndex],
status: status,
}
if (statusText) {
updatedFile.statusText = statusText
}
if (percentage !== null && percentage >= 0 && percentage <= 100) {
updatedFile.percentage = percentage
} else if (status === 'uploading') {
updatedFile.percentage = 0
} else if (status === 'success') {
updatedFile.percentage = 100
}
if (responseData) {
updatedFile.response = responseData
updatedFile.response.businessType = this.fileUploadRule?.fileUploadType
updatedFile.res = updatedFile.response
}
this.$set(this.files, fileIndex, updatedFile)
}
},
formatFileList(fileList) {
return fileList.map((file) => {
const formattedFile = {
uid: file.uid,
name: file.name,
size: file.size,
type: file.type,
status: file.status,
raw: file.raw,
}
if (file.percentage !== undefined && file.percentage !== null) {
formattedFile.percentage = Math.max(0, Math.min(100, file.percentage))
} else if (file.status === 'uploading') {
formattedFile.percentage = 0
} else if (file.status === 'success') {
formattedFile.percentage = 100
}
if (file.response) {
formattedFile.response = file.response
formattedFile.res = file.response
}
return formattedFile
})
},
getCurrentFiles() {
return this.files.map((file) => {
const fileObj = {
uid: file.uid,
name: file.name,
size: file.size,
type: file.type,
status: file.status,
}
if (file.raw) {
fileObj.raw = file.raw
}
if (file.response) {
fileObj.response = file.response
fileObj.response.businessType = this.fileUploadRule?.fileUploadType
}
return fileObj
})
},
},
}
</script>
<style scoped lang="scss">
.upload-container {
width: 100%;
.upload-demo {
width: 100%;
::v-deep .el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 160px;
border: 2px dashed #dcdfe6;
border-radius: 8px;
background-color: #fafafa;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&:hover {
border-color: #409eff;
background-color: #f5f7fa;
}
}
}
}
.upload-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
.upload-text {
text-align: center;
.main-text {
font-size: 16px;
color: #1f72ea;
margin-bottom: 8px;
font-weight: 500;
}
.tip-text {
font-size: 14px;
color: #909399;
line-height: 1.4;
div {
margin-bottom: 2px;
}
}
}
}
}
</style>