Files
zhxg_pc/src/components/affix/index.vue

414 lines
12 KiB
Vue
Raw Normal View History

2025-07-28 15:52:07 +08:00
<template>
2025-10-18 17:13:04 +08:00
<div>
2025-12-12 11:51:17 +08:00
<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()">
<div v-if="downloading" class="el-icon-loading file-upload"
style="margin-left: 0px;margin-right: 3px;font-size: 14px;" />
2025-10-18 17:13:04 +08:00
打包下载
</el-button>
</el-upload>
2025-07-28 15:52:07 +08:00
2025-10-18 17:13:04 +08:00
<div v-for="(item, index) in fileList" :key="index" class="file">
2025-12-12 11:51:17 +08:00
<div class="file-item" :class="[{ 'is-disabled': inputDisabled }]">
2025-10-18 17:13:04 +08:00
<div class="file-name">{{ item.name }}</div>
2025-12-12 11:51:17 +08:00
<div v-if="item.status === 1" class="el-icon-loading file-upload" title="上传中..." />
<div v-if="item.status === 2" class="el-icon-download file-download" title="下载" @click="downloadFile(item)" />
<div v-if="item.status === 2" class="el-icon-delete file-delete" title="删除" @click="deleteFile(item)" />
<div v-if="isImageURL(item.name) && item.status === 2" class="el-icon-picture" title="预览"
@click="preview(item)" />
2025-10-18 17:13:04 +08:00
</div>
2025-07-28 15:52:07 +08:00
</div>
2025-12-12 11:51:17 +08:00
<el-image v-show="false" ref="preview" class="preview" :src="hiddenSrc" :preview-src-list="previewList" />
2025-10-18 17:13:04 +08:00
</div>
2025-07-28 15:52:07 +08:00
</template>
<script>
2025-10-18 17:13:04 +08:00
import { deleteAffix, download, downloadAll, queryAffixs, upload } from '@/api/affix/affix'
2025-07-28 15:52:07 +08:00
export default {
2025-12-12 11:51:17 +08:00
name: 'CmAffix',
inject: {
elForm: {
default: ''
}
},
props: {
notupload: {
type: Boolean,
default: false
},
value: String, // 父组件值
disabled: Boolean,
maxSize: {
type: Number,
default: 20
},
accpet: {
type: String,
default: '*'
},
uploadStyle:
{
type: String,
default: ''
2025-07-28 15:52:07 +08:00
},
2025-12-12 11:51:17 +08:00
},
data() {
return {
uploadCnt: 0,
affixId: '',
fileList: [],
downloading: false,//控制打包下载loading动画
baseurl: process.env.VUE_APP_BASE_API,
hiddenSrc: '',
previewList: [],
// 解决重复提交的核心变量
isQuerying: false, // 请求锁:标记是否正在查询附件
retryCount: 0 // 重试次数:捕获重复提交错误时重试
}
},
computed: {
inputDisabled() {
return this.disabled || (this.elForm || {}).disabled
}
},
watch: {
// 父组件值监听事件
value: {
immediate: true, // 初始化立即执行
handler: function (newVal, oldVal) {
// 新旧值不同时,先清空所有旧数据(核心解决数据残留)
if (newVal !== oldVal) {
this.fileList = [];
this.previewList = [];
this.affixId = '';
this.retryCount = 0;
2025-10-18 17:13:04 +08:00
}
2025-12-12 11:51:17 +08:00
// 满足条件才加载数据
if (newVal && newVal !== '' && newVal !== this.affixId) {
this.loadData();
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
}
}
},
created() {
this.loadData()
},
// 新增组件激活时清空旧数据适配keep-alive页面
activated() {
this.fileList = [];
this.previewList = [];
if (this.value) {
this.loadData();
}
},
methods: {
// 预览
preview(item) {
this.hiddenSrc = item.savePath
this.$refs.preview.showViewer = true
2025-07-28 15:52:07 +08:00
},
2025-12-12 11:51:17 +08:00
isImageURL(url) {
// let imgRegex = /(\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]
console.log(res.savePath)
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.fileList[i].savePaths =[this.baseurl + res.savePath];
// 上传成功后向外传递当前文件的完整信息包含后端返回的res数据
this.$emit('fileUploaded', {
fileId: res.id, // 文件ID
fileName: res.trueName, // 文件名
filePath: res.savePath, // 文件相对路径不含baseurl
fullPath: this.fileList[i].savePath, // 完整路径含baseurl
fileType: this.getFileType(res.trueName), // 文件类型
originalFile: file.file // 原始文件对象(可选)
});
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
}
} else {
this.$message.error(res.message)
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
}).catch(() => {
this.uploadCnt--
})
2025-07-28 15:52:07 +08:00
},
2025-12-12 11:51:17 +08:00
// 获取文件类型的方法Affix组件中新增
getFileType(fileName) {
if (!fileName) return '';
const lastDotIndex = fileName.lastIndexOf('.');
return lastDotIndex > -1 ? fileName.substring(lastDotIndex + 1).toLowerCase() : '';
2025-10-18 17:13:04 +08:00
},
2025-12-12 11:51:17 +08:00
handleBeforeUpload(file) {
2025-07-28 15:52:07 +08:00
2025-12-12 11:51:17 +08:00
if (this.affixId == null || this.affixId === '') {
this.affixId = this.$tool.uuid()
this.$emit('input', this.affixId)
}
2025-07-28 15:52:07 +08:00
2025-12-12 11:51:17 +08:00
let isLt20M = file.size / 1024 / 1024 < this.maxSize
if (!isLt20M) {
this.$message.error('上传大小不能超过 ' + this.maxSize + 'MB!')
} else {
this.fileList.push({ name: file.name, status: 1 })
this.uploadCnt++
}
2025-07-28 15:52:07 +08:00
2025-12-12 11:51:17 +08:00
return isLt20M
},
getFileName(id) {
for (let i = 0; i < this.fileList.length; i++) {
let item = this.fileList[i]
if (item.id == id) {
console.log(item)
return item.name
}
}
},
downloadFile(file) {
download(file.id).then((res) => {
console.log(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) // 创建下载的链接
downloadElement.href = href
downloadElement.download = decodeURI(fileName) // 下载后文件名
document.body.appendChild(downloadElement)
downloadElement.click() // 点击下载
document.body.removeChild(downloadElement) // 下载完成移除元素
window.URL.revokeObjectURL(href) // 释放掉blob对象
}).catch((res) => {
this.$message.error(res.message)
})
},
downloadPack() {
if (this.affixId === null || this.affixId === '') {
this.$message.error('暂无附件!')
return
}
//如果只有一个文件就不打包下载
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) // 创建下载的链接
downloadElement.href = href
downloadElement.download = decodeURI('download.zip') // 下载后文件名
document.body.appendChild(downloadElement)
downloadElement.click() // 点击下载
document.body.removeChild(downloadElement) // 下载完成移除元素
window.URL.revokeObjectURL(href) // 释放掉blob对象
this.downloading = false
}).catch(() => {
this.downloading = false
})
}
},
deleteFile(file) {
this.$confirm('请确认是否删除此文件?', '提示', { type: 'info' }).then(() => {
deleteAffix(file.id).then((res) => {
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) {
this.affixId = ''
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
// 关键:触发自定义事件,传递被删除的文件名
// 事件名建议delete-file参数file.name文件名
this.$emit('delete-file', file.name);
} else {
this.$message.error(res.message)
}
})
})
},
loadData() {
// 1. 请求锁:防止同一时刻重复请求(解决重复提交)
if (this.isQuerying) return;
// 2. 清空旧数据(双重保障解决数据残留)
this.fileList = [];
this.previewList = [];
2025-07-28 15:52:07 +08:00
2025-12-12 11:51:17 +08:00
if (typeof this.value !== 'undefined' && this.value !== null && this.value !== '') {
this.isQuerying = true; // 加锁
// 带重试机制的查询方法
const queryWithRetry = () => {
queryAffixs(this.value).then((res) => {
this.isQuerying = false; // 解锁
if (res.code == 200) {
if (res.data.length > 0) {
this.affixId = this.value;
// 重新构建数组,避免引用污染
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);
}
newFileList.push({
name: res.data[i].trueName,
id: res.data[i].id,
status: 2,
savePath: savePath
});
}
// 直接替换数组,彻底解决残留
this.fileList = newFileList;
this.previewList = newPreviewList;
} else {
this.affixId = '';
}
2025-07-28 15:52:07 +08:00
} else {
2025-12-12 11:51:17 +08:00
// 捕获"重复提交"错误重试1次
if (res.msg.includes('请勿重复提交') && this.retryCount < 1) {
this.retryCount++;
this.isQuerying = true;
setTimeout(() => queryWithRetry(), 500); // 500ms后重试
return;
}
this.$message.error(res.msg);
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
}).catch((err) => {
this.isQuerying = false; // 解锁
// 捕获异常中的重复提交错误重试1次
if (err.message?.includes('请勿重复提交') && this.retryCount < 1) {
this.retryCount++;
setTimeout(() => queryWithRetry(), 500);
return;
}
this.$message.error(err.message || '查询附件失败');
});
};
// 执行查询
queryWithRetry();
} else {
this.affixId = '';
this.isQuerying = false;
}
},
clearData() {
this.affixId = '';
this.fileList = [];
this.previewList = [];
this.isQuerying = false;
this.retryCount = 0;
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
}
2025-07-28 15:52:07 +08:00
}
</script>
<style scoped>
2025-12-12 11:51:17 +08:00
.cm-affix>>>.el-upload {
padding: 0px 10px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-bottom: 10px;
2025-07-28 15:52:07 +08:00
}
2025-12-12 11:51:17 +08:00
.file {
width: 100%;
}
.file-item {
white-space: nowrap;
display: flex;
justify-content: start;
align-items: center;
gap: 8px;
}
.file-item div {
display: inline-block;
white-space: nowrap;
}
.file-upload {
font-size: 18px;
margin-left: 5px;
}
.file-download,
.file-delete {
font-size: 18px;
cursor: pointer;
}
.file-download:hover,
.file-delete:hover {
color: #409EFF;
}
.file-item[class~=is-disabled] .file-delete {
display: none;
}
.download-pack:hover {
background: #409eff;
border-color: #3999a8;
color: #fafbfd;
}
.preview .file-preview {
font-size: 19px;
cursor: pointer;
}
.preview>>>.el-image__inner {
width: 18px;
height: 18px;
}
2025-07-28 15:52:07 +08:00
</style>