外宿申请-修复附件要上传两次
This commit is contained in:
@@ -3,17 +3,19 @@
|
||||
<el-upload :style="uploadStyle" action="" :class="['cm-affix', { 'is-disabled': inputDisabled }]"
|
||||
:disabled="inputDisabled" :multiple="true" :http-request="handleUpload" :file-list="fileList" :accept="accpet"
|
||||
:show-file-list="false" :before-upload="handleBeforeUpload">
|
||||
<el-button v-if="inputDisabled !== true" id="affix1" :disabled="notupload" size="small" type="primary"><i
|
||||
class='el-icon-upload2'></i>点击上传</el-button>
|
||||
<el-button v-if="this.affixId !== null && this.affixId !== '' && this.fileList.length > 0" id="affix2"
|
||||
size="small" @click.stop="downloadPack()">
|
||||
<el-button v-if="inputDisabled !== true" id="affix1" :disabled="notupload" size="small" type="primary">
|
||||
<i class="el-icon-upload2"></i>点击上传
|
||||
</el-button>
|
||||
<el-button v-if="affixId !== null && affixId !== '' && fileList.length > 0" id="affix2" size="small"
|
||||
@click.stop="downloadPack()">
|
||||
<div v-if="downloading" class="el-icon-loading file-upload"
|
||||
style="margin-left: 0px;margin-right: 3px;font-size: 14px;" />
|
||||
打包下载
|
||||
</el-button>
|
||||
</el-upload>
|
||||
|
||||
<div v-for="(item, index) in fileList" :key="`${item.id || item.name}_${index}_${affixId}`" class="file">
|
||||
<div v-for="(item, index) in fileList" :key="`${item.id || item.uid || item.name}_${index}_${affixId}`"
|
||||
class="file">
|
||||
<div class="file-item" :class="[{ 'is-disabled': inputDisabled }]">
|
||||
<div class="file-name">{{ item.name }}</div>
|
||||
<div v-if="item.status === 1" class="el-icon-loading file-upload" title="上传中..." />
|
||||
@@ -29,6 +31,8 @@
|
||||
|
||||
<script>
|
||||
import { deleteAffix, download, downloadAll, queryAffixs, upload } from '@/api/affix/affix'
|
||||
// 若有创建affix主记录的接口,需引入(根据后端实际情况调整)
|
||||
// import { createAffix } from '@/api/affix/affix'
|
||||
|
||||
export default {
|
||||
name: 'CmAffix',
|
||||
@@ -68,7 +72,10 @@ export default {
|
||||
previewList: [],
|
||||
requestLock: false, // 全局请求锁
|
||||
retryTimes: 0, // 重试次数
|
||||
cacheData: {} // 缓存已查询的附件数据
|
||||
cacheData: {}, // 缓存已查询的附件数据
|
||||
uploadQueue: [], // 上传队列(解决并发问题)
|
||||
isUploading: false, // 是否正在上传(串行控制)
|
||||
uploadRetryTimes: 0 // 上传重试次数
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -87,8 +94,8 @@ export default {
|
||||
}
|
||||
// 优先使用缓存,避免重复请求
|
||||
if (this.cacheData[newVal]) {
|
||||
this.fileList = [...this.cacheData[newVal].fileList]
|
||||
this.previewList = [...this.cacheData[newVal].previewList]
|
||||
this.fileList = JSON.parse(JSON.stringify(this.cacheData[newVal].fileList))
|
||||
this.previewList = JSON.parse(JSON.stringify(this.cacheData[newVal].previewList))
|
||||
this.affixId = newVal
|
||||
return
|
||||
}
|
||||
@@ -102,6 +109,8 @@ export default {
|
||||
beforeUnmount() {
|
||||
this.requestLock = false
|
||||
this.retryTimes = 0
|
||||
this.isUploading = false
|
||||
this.uploadQueue = []
|
||||
},
|
||||
methods: {
|
||||
preview(item) {
|
||||
@@ -110,83 +119,192 @@ export default {
|
||||
},
|
||||
|
||||
isImageURL(url) {
|
||||
const regex = /(\jpg|\jpeg|\png|\gif|\webp)$/i
|
||||
// 修复正则:原正则会匹配以这些字符结尾,但写法有问题(少了\.)
|
||||
const regex = /(\.jpg|\.jpeg|\.png|\.gif|\.webp)$/i
|
||||
return regex.test(url)
|
||||
},
|
||||
|
||||
// 处理上传请求(改为加入队列)
|
||||
handleUpload(file) {
|
||||
upload({ 'file': file.file, 'affixId': this.affixId }).then(res => {
|
||||
this.uploadCnt--
|
||||
if (res.code == 200) {
|
||||
for (let i = 0; i < this.fileList.length; i++) {
|
||||
let item = this.fileList[i]
|
||||
if (item.name == res.trueName && item.status == 1) {
|
||||
this.fileList[i].id = res.id
|
||||
this.fileList[i].status = 2
|
||||
this.fileList[i].savePath = this.baseurl + res.savePath
|
||||
if (this.isImageURL(res.savePath)) {
|
||||
this.previewList.push(this.fileList[i].savePath)
|
||||
// 将文件加入上传队列
|
||||
this.uploadQueue.push(file)
|
||||
// 若未在上传,则执行队列
|
||||
if (!this.isUploading) {
|
||||
this.processUploadQueue()
|
||||
}
|
||||
// 上传成功后,向外传递当前文件的完整信息(包含后端返回的res数据)
|
||||
},
|
||||
|
||||
// 串行处理上传队列(核心修复并发问题)
|
||||
async processUploadQueue() {
|
||||
// 队列为空则结束
|
||||
if (this.uploadQueue.length === 0) {
|
||||
this.isUploading = false
|
||||
this.uploadRetryTimes = 0 // 重置重试次数
|
||||
return
|
||||
}
|
||||
|
||||
this.isUploading = true
|
||||
// 取出队列第一个文件
|
||||
const file = this.uploadQueue.shift()
|
||||
const currentFileUid = file.file.uid || Date.now() + Math.random()
|
||||
|
||||
// 确保affixId存在
|
||||
if (!this.affixId) {
|
||||
this.affixId = this.$tool.uuid()
|
||||
// 若后端需要先创建affix主记录,取消注释并调整(根据实际情况)
|
||||
// await this.createAffixRecord(this.affixId)
|
||||
this.$nextTick(() => {
|
||||
this.$emit('input', this.affixId)
|
||||
this.$emit('change', this.affixId)
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用上传接口
|
||||
const res = await upload({ 'file': file.file, 'affixId': this.affixId })
|
||||
this.uploadCnt--
|
||||
|
||||
if (res.code === 200) {
|
||||
// 通过uid匹配文件(解决同名文件问题)
|
||||
const fileIndex = this.fileList.findIndex(item =>
|
||||
item.uid === currentFileUid && item.status === 1
|
||||
)
|
||||
|
||||
if (fileIndex > -1) {
|
||||
const savePath = this.baseurl + res.savePath
|
||||
// 更新文件信息
|
||||
this.fileList[fileIndex] = {
|
||||
...this.fileList[fileIndex],
|
||||
id: res.id,
|
||||
status: 2,
|
||||
savePath: savePath,
|
||||
trueName: res.trueName
|
||||
}
|
||||
|
||||
// 预览列表去重添加
|
||||
if (this.isImageURL(savePath) && !this.previewList.includes(savePath)) {
|
||||
this.previewList.push(savePath)
|
||||
}
|
||||
|
||||
// 向外传递上传成功事件
|
||||
this.$emit('fileUploaded', {
|
||||
fileId: res.id,
|
||||
fileName: res.trueName,
|
||||
filePath: res.savePath,
|
||||
fullPath: this.fileList[i].savePath,
|
||||
fullPath: savePath,
|
||||
fileType: this.getFileType(res.trueName),
|
||||
originalFile: file.file
|
||||
});
|
||||
}
|
||||
}
|
||||
// 更新缓存
|
||||
})
|
||||
|
||||
// 更新缓存(深拷贝避免引用问题)
|
||||
this.cacheData[this.affixId] = {
|
||||
fileList: [...this.fileList],
|
||||
previewList: [...this.previewList]
|
||||
fileList: JSON.parse(JSON.stringify(this.fileList)),
|
||||
previewList: JSON.parse(JSON.stringify(this.previewList))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
this.$message.error(res.message || '上传失败')
|
||||
// 上传失败:重新加入队列(最多重试1次)
|
||||
if (this.uploadRetryTimes < 1) {
|
||||
this.uploadRetryTimes++
|
||||
this.uploadQueue.unshift(file)
|
||||
setTimeout(() => {
|
||||
this.processUploadQueue()
|
||||
}, 1000)
|
||||
return
|
||||
} else {
|
||||
this.uploadRetryTimes = 0
|
||||
// 移除上传失败的文件
|
||||
this.removeFailedFile(currentFileUid)
|
||||
}
|
||||
}).catch(() => {
|
||||
}
|
||||
} catch (err) {
|
||||
this.uploadCnt--
|
||||
})
|
||||
this.$message.error(`文件 ${file.file.name} 上传失败:${err.message || '网络异常'}`)
|
||||
// 上传失败:重新加入队列(最多重试1次)
|
||||
if (this.uploadRetryTimes < 1) {
|
||||
this.uploadRetryTimes++
|
||||
this.uploadQueue.unshift(file)
|
||||
setTimeout(() => {
|
||||
this.processUploadQueue()
|
||||
}, 1000)
|
||||
return
|
||||
} else {
|
||||
this.uploadRetryTimes = 0
|
||||
// 移除上传失败的文件
|
||||
this.removeFailedFile(currentFileUid)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理下一个文件
|
||||
this.processUploadQueue()
|
||||
},
|
||||
|
||||
// 移除上传失败的文件
|
||||
removeFailedFile(uid) {
|
||||
const fileIndex = this.fileList.findIndex(item =>
|
||||
item.uid === uid && item.status === 1
|
||||
)
|
||||
if (fileIndex > -1) {
|
||||
this.fileList.splice(fileIndex, 1)
|
||||
}
|
||||
},
|
||||
|
||||
// 创建affix主记录(根据后端实际情况调整,可选)
|
||||
async createAffixRecord(affixId) {
|
||||
try {
|
||||
// await createAffix({ affixId: affixId })
|
||||
} catch (err) {
|
||||
this.$message.error('创建附件记录失败:' + err.message)
|
||||
}
|
||||
},
|
||||
|
||||
getFileType(fileName) {
|
||||
if (!fileName) return '';
|
||||
const lastDotIndex = fileName.lastIndexOf('.');
|
||||
return lastDotIndex > -1 ? fileName.substring(lastDotIndex + 1).toLowerCase() : '';
|
||||
if (!fileName) return ''
|
||||
const lastDotIndex = fileName.lastIndexOf('.')
|
||||
return lastDotIndex > -1 ? fileName.substring(lastDotIndex + 1).toLowerCase() : ''
|
||||
},
|
||||
|
||||
handleBeforeUpload(file) {
|
||||
if (this.affixId == null || this.affixId === '') {
|
||||
// 生成affixId(若不存在)
|
||||
if (!this.affixId) {
|
||||
this.affixId = this.$tool.uuid()
|
||||
this.$emit('input', this.affixId)
|
||||
// 若后端需要先创建affix主记录,取消注释
|
||||
// this.createAffixRecord(this.affixId).then(() => {
|
||||
// this.$emit('input', this.affixId)
|
||||
// })
|
||||
}
|
||||
let isLt20M = file.size / 1024 / 1024 < this.maxSize
|
||||
|
||||
// 校验文件大小
|
||||
const isLt20M = file.size / 1024 / 1024 < this.maxSize
|
||||
if (!isLt20M) {
|
||||
this.$message.error('上传大小不能超过 ' + this.maxSize + 'MB!')
|
||||
this.$message.error(`上传大小不能超过 ${this.maxSize}MB!`)
|
||||
return false // 显式阻止上传
|
||||
} else {
|
||||
this.fileList.push({ name: file.name, status: 1 })
|
||||
// 为文件添加唯一标识(uid),解决同名文件匹配问题
|
||||
const fileUid = file.uid || Date.now() + Math.random()
|
||||
this.fileList.push({
|
||||
name: file.name,
|
||||
status: 1,
|
||||
uid: fileUid,
|
||||
rawFile: file
|
||||
})
|
||||
this.uploadCnt++
|
||||
return true
|
||||
}
|
||||
return isLt20M
|
||||
},
|
||||
|
||||
getFileName(id) {
|
||||
for (let i = 0; i < this.fileList.length; i++) {
|
||||
let item = this.fileList[i]
|
||||
if (item.id == id) {
|
||||
return item.name
|
||||
}
|
||||
}
|
||||
const item = this.fileList.find(item => item.id === id)
|
||||
return item ? item.name : ''
|
||||
},
|
||||
|
||||
downloadFile(file) {
|
||||
download(file.id).then((res) => {
|
||||
let fileName = this.getFileName(file.id).replace(/"/g, '')
|
||||
var blob = new Blob([res], { type: 'application/octet-stream;' })
|
||||
var downloadElement = document.createElement('a')
|
||||
var href = window.URL.createObjectURL(blob)
|
||||
const blob = new Blob([res], { type: 'application/octet-stream;' })
|
||||
const downloadElement = document.createElement('a')
|
||||
const href = window.URL.createObjectURL(blob)
|
||||
downloadElement.href = href
|
||||
downloadElement.download = decodeURI(fileName)
|
||||
document.body.appendChild(downloadElement)
|
||||
@@ -194,23 +312,23 @@ export default {
|
||||
document.body.removeChild(downloadElement)
|
||||
window.URL.revokeObjectURL(href)
|
||||
}).catch((res) => {
|
||||
this.$message.error(res.message)
|
||||
this.$message.error(res.message || '下载失败')
|
||||
})
|
||||
},
|
||||
|
||||
downloadPack() {
|
||||
if (this.affixId === null || this.affixId === '') {
|
||||
if (!this.affixId) {
|
||||
this.$message.error('暂无附件!')
|
||||
return
|
||||
}
|
||||
if (this.fileList.length == 1) {
|
||||
if (this.fileList.length === 1) {
|
||||
this.downloadFile(this.fileList[0])
|
||||
} else {
|
||||
this.downloading = true
|
||||
downloadAll(this.affixId).then((res) => {
|
||||
var blob = new Blob([res], { type: 'application/octet-stream' })
|
||||
var downloadElement = document.createElement('a')
|
||||
var href = window.URL.createObjectURL(blob)
|
||||
const blob = new Blob([res], { type: 'application/octet-stream' })
|
||||
const downloadElement = document.createElement('a')
|
||||
const href = window.URL.createObjectURL(blob)
|
||||
downloadElement.href = href
|
||||
downloadElement.download = decodeURI('download.zip')
|
||||
document.body.appendChild(downloadElement)
|
||||
@@ -220,6 +338,7 @@ export default {
|
||||
this.downloading = false
|
||||
}).catch(() => {
|
||||
this.downloading = false
|
||||
this.$message.error('打包下载失败')
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -227,100 +346,116 @@ export default {
|
||||
deleteFile(file) {
|
||||
this.$confirm('请确认是否删除此文件?', '提示', { type: 'info' }).then(() => {
|
||||
deleteAffix(file.id).then((res) => {
|
||||
if (res.code == 200) {
|
||||
if (res.code === 200) {
|
||||
// 过滤删除的文件
|
||||
this.fileList = this.fileList.filter(item => item.id !== file.id)
|
||||
// 过滤预览列表
|
||||
this.previewList = this.previewList.filter(item => item !== file.savePath)
|
||||
if (this.fileList.length == 0) {
|
||||
// 若文件列表为空,清空affixId和缓存
|
||||
if (this.fileList.length === 0) {
|
||||
delete this.cacheData[this.affixId]
|
||||
this.affixId = ''
|
||||
delete this.cacheData[this.affixId] // 清空缓存
|
||||
this.$emit('input', '')
|
||||
} else {
|
||||
// 更新缓存
|
||||
// 更新缓存(深拷贝)
|
||||
this.cacheData[this.affixId] = {
|
||||
fileList: [...this.fileList],
|
||||
previewList: [...this.previewList]
|
||||
fileList: JSON.parse(JSON.stringify(this.fileList)),
|
||||
previewList: JSON.parse(JSON.stringify(this.previewList))
|
||||
}
|
||||
}
|
||||
// 关键:触发自定义事件,传递被删除的文件名
|
||||
// 事件名建议:delete-file,参数:file.name(文件名)
|
||||
this.$emit('delete-file', file.name);
|
||||
// 触发删除事件
|
||||
this.$emit('delete-file', file.name)
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
this.$message.error(res.message || '删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error('删除失败')
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 核心修改:静默处理"重复提交"错误,最多重试1次,不提示用户
|
||||
// 加载附件数据
|
||||
async loadData(targetVal) {
|
||||
// 加锁防止重复执行
|
||||
if (this.requestLock || !targetVal) return;
|
||||
this.requestLock = true;
|
||||
if (this.requestLock || !targetVal) return
|
||||
this.requestLock = true
|
||||
|
||||
// 若缓存存在但文件列表为空,清空缓存
|
||||
if (this.cacheData[targetVal] && this.cacheData[targetVal].fileList.length === 0) {
|
||||
delete this.cacheData[targetVal]
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await queryAffixs(targetVal);
|
||||
if (res.code == 200) {
|
||||
this.affixId = targetVal;
|
||||
const newFileList = [];
|
||||
const newPreviewList = [];
|
||||
for (var i = 0; i < res.data.length; i++) {
|
||||
const savePath = this.baseurl + res.data[i].savePath;
|
||||
if (this.isImageURL(savePath)) {
|
||||
newPreviewList.push(savePath);
|
||||
}
|
||||
const res = await queryAffixs(targetVal)
|
||||
if (res.code === 200) {
|
||||
this.affixId = targetVal
|
||||
const newFileList = []
|
||||
const newPreviewList = []
|
||||
|
||||
res.data.forEach(item => {
|
||||
const savePath = this.baseurl + item.savePath
|
||||
// 为历史文件添加uid
|
||||
const fileUid = Date.now() + Math.random() + item.id
|
||||
newFileList.push({
|
||||
name: res.data[i].trueName,
|
||||
id: res.data[i].id,
|
||||
name: item.trueName,
|
||||
id: item.id,
|
||||
status: 2,
|
||||
savePath: savePath
|
||||
});
|
||||
savePath: savePath,
|
||||
uid: fileUid,
|
||||
trueName: item.trueName
|
||||
})
|
||||
if (this.isImageURL(savePath)) {
|
||||
newPreviewList.push(savePath)
|
||||
}
|
||||
this.fileList = newFileList;
|
||||
this.previewList = newPreviewList;
|
||||
// 缓存查询结果,后续直接使用
|
||||
})
|
||||
|
||||
this.fileList = newFileList
|
||||
this.previewList = newPreviewList
|
||||
// 缓存数据(深拷贝)
|
||||
this.cacheData[targetVal] = {
|
||||
fileList: [...newFileList],
|
||||
previewList: [...newPreviewList]
|
||||
};
|
||||
this.retryTimes = 0; // 重置重试次数
|
||||
fileList: JSON.parse(JSON.stringify(newFileList)),
|
||||
previewList: JSON.parse(JSON.stringify(newPreviewList))
|
||||
}
|
||||
this.retryTimes = 0
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
this.$message.error(res.msg || '查询附件失败')
|
||||
}
|
||||
} catch (err) {
|
||||
// 关键:只过滤"数据正在处理,请勿重复提交"错误,不提示用户
|
||||
// 处理重复提交错误
|
||||
if (err.message?.includes('数据正在处理,请勿重复提交')) {
|
||||
// 最多重试1次,1秒后重新请求
|
||||
if (this.retryTimes < 1) {
|
||||
this.retryTimes++;
|
||||
this.retryTimes++
|
||||
setTimeout(() => {
|
||||
this.requestLock = false; // 释放锁
|
||||
this.loadData(targetVal); // 重试
|
||||
}, 1000);
|
||||
this.requestLock = false
|
||||
this.loadData(targetVal)
|
||||
}, 1000)
|
||||
} else {
|
||||
// 重试失败后清空锁,避免卡死
|
||||
this.requestLock = false;
|
||||
this.retryTimes = 0;
|
||||
this.requestLock = false
|
||||
this.retryTimes = 0
|
||||
this.$message.warning('附件处理中,请稍后再试')
|
||||
}
|
||||
} else {
|
||||
// 其他错误正常提示
|
||||
this.$message.error(err.message || '查询附件失败');
|
||||
this.requestLock = false;
|
||||
this.$message.error(err.message || '查询附件失败')
|
||||
this.requestLock = false
|
||||
}
|
||||
} finally {
|
||||
// 非重试场景,最终释放锁
|
||||
if (this.retryTimes === 0) {
|
||||
this.requestLock = false;
|
||||
this.requestLock = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 清空数据
|
||||
clearData() {
|
||||
this.affixId = '';
|
||||
this.fileList = [];
|
||||
this.previewList = [];
|
||||
this.requestLock = false;
|
||||
this.retryTimes = 0;
|
||||
// 清空当前affixId的缓存
|
||||
delete this.cacheData[this.affixId];
|
||||
this.affixId = ''
|
||||
this.fileList = []
|
||||
this.previewList = []
|
||||
this.requestLock = false
|
||||
this.retryTimes = 0
|
||||
this.uploadQueue = []
|
||||
this.isUploading = false
|
||||
// 清空缓存
|
||||
delete this.cacheData[this.affixId]
|
||||
this.$emit('input', '')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,6 +481,8 @@ export default {
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
/* 增加间距,优化显示 */
|
||||
}
|
||||
|
||||
.file-item div {
|
||||
@@ -359,13 +496,17 @@ export default {
|
||||
}
|
||||
|
||||
.file-download,
|
||||
.file-delete {
|
||||
.file-delete,
|
||||
.el-icon-picture {
|
||||
/* 新增预览图标样式 */
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-download:hover,
|
||||
.file-delete:hover {
|
||||
.file-delete:hover,
|
||||
.el-icon-picture:hover {
|
||||
/* 预览图标hover效果 */
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
@@ -377,4 +518,12 @@ export default {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* 优化文件名显示,超出省略 */
|
||||
.file-name {
|
||||
max-width: 200px;
|
||||
/* 可根据需求调整 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user