外宿申请-修复附件要上传两次

This commit is contained in:
962704835@qq.com
2025-12-15 20:14:49 +08:00
parent 84146cb9e5
commit c75e1a1691

View File

@@ -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.uploadQueue.push(file)
// 若未在上传,则执行队列
if (!this.isUploading) {
this.processUploadQueue()
}
},
// 串行处理上传队列(核心修复并发问题)
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) {
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)
}
// 上传成功后向外传递当前文件的完整信息包含后端返回的res数据
this.$emit('fileUploaded', {
fileId: res.id,
fileName: res.trueName,
filePath: res.savePath,
fullPath: this.fileList[i].savePath,
fileType: this.getFileType(res.trueName),
originalFile: file.file
});
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: savePath,
fileType: this.getFileType(res.trueName),
originalFile: file.file
})
// 更新缓存(深拷贝避免引用问题)
this.cacheData[this.affixId] = {
fileList: JSON.parse(JSON.stringify(this.fileList)),
previewList: JSON.parse(JSON.stringify(this.previewList))
}
}
// 更新缓存
this.cacheData[this.affixId] = {
fileList: [...this.fileList],
previewList: [...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
});
}
this.fileList = newFileList;
this.previewList = newPreviewList;
// 缓存查询结果,后续直接使用
savePath: savePath,
uid: fileUid,
trueName: item.trueName
})
if (this.isImageURL(savePath)) {
newPreviewList.push(savePath)
}
})
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>