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

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