<template>
<div>
<el-dialog
:visible.sync="visible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:key="dialogKey"
append-to-body
width="870px"
@close="hide">
<div slot="title">多文件上传</div>
<div class="upload-container">
<p>文件大小应小于5G,支持zip格式文件</p>
<el-upload
class="invoice-drop-zone"
ref="upload"
:multiple="true"
:on-change="onChange"
:show-file-list="false"
:on-remove="handleRemove"
:max-size="maxFileSize"
:drag ="true"
accept=".zip"
:file-list="fileList"
:http-request="uploadRequest"
:auto-upload="false">
<div slot="trigger">
<i class="el-icon-upload"></i>
<div class="txt">将文件拖放到这里或</div>
<el-button size="small" type="primary">点击选择文件</el-button>
</div>
</el-upload>
<div class="upbtncan" v-if="fileList.length>0">
<el-button type="primary" :loading="isUploading" @click="submitUpload">{{isUploading?'上传中...':'上传'}}</el-button>
<el-button type="primary" @click="cancelUpload">取消</el-button>
</div>
<!-- 上传队列 -->
<div class="upprot" v-if="isUploading && fileList.length > 0">
<div class="ht">
<span>文件上传中...{{totalProgress}}%</span>
<span>{{uploadedCount}}/{{ fileList.length }} 个</span>
</div>
<el-progress :text-inside="false" :stroke-width="10" :show-text="false" :percentage="totalProgress"></el-progress>
</div>
<div class="upload-list-container">
<div id="uploader-list">
<div v-for="(file,index) in fileList" :key="file.id" class="upload-item">
<div class="name">{{ file.name }} </div>
<div class="size">{{ file.size }}</div>
<div class="fr">
<div class="pro">
<el-progress :percentage="file.percentage"></el-progress>
<span class="status" :class="{'wait': file.percentage === 0, 'success': file.status === 'success', 'error': file.status === 'error'}">
<i v-if="file.status === 'success'" class="el-icon-success"></i>
<i v-if="file.status === 'error'" class="el-icon-error"></i>
{{file.statusText}}
</span>
</div>
<el-button type="text" @click="removeFile(index)" class="remove" style="padding:0; font-size:12px; color:#409EFF" v-if="file.status !== 'uploading'">取消</el-button>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
dialogKey: 0,
fileList: [],
isUploading: false,
uploadedCount: 0,
maxFileSize: 5 * 1024 * 1024 * 1024, // 5GB
currentFileIndex: 0
}
},
computed: {
totalProgress() {
if (this.fileList.length === 0) return 0;
const sum = this.fileList.reduce((total, file) => total + file.percentage, 0);
return Math.round(sum / this.fileList.length);
}
},
methods: {
/**
* 显示方法
*/
async show() {
this.visible = true;
},
onChange(file, fileList) {
// 使用Map来跟踪已处理的文件,避免重复添加
const processedFiles = new Map();
// 处理所有新选择的文件
fileList.forEach(newFile => {
const fileKey = `${newFile.name}-${newFile.raw.size}`;
// 跳过已处理的文件
if (processedFiles.has(fileKey)) return;
processedFiles.set(fileKey, true);
// 检查是否存在同名同大小的文件(包括已上传、正在上传和准备上传的)
const isDuplicate = this.fileList.some(existingFile =>
existingFile.name === newFile.name &&
existingFile.rawSize === newFile.raw.size &&
// 忽略已标记为duplicate的文件,避免重复计数
existingFile.status !== 'duplicate'
);
// 只有非重复文件才添加到列表中
if (!isDuplicate) {
this.fileList.push({
id: Date.now() + '_' + newFile.name,
name: newFile.name,
status: 'ready',
statusText: '准备上传',
rawSize: newFile.raw.size,
size: this.formatFileSize(newFile.raw.size),
percentage: 0,
raw: newFile.raw
});
}
});
},
// 格式化文件大小
formatFileSize(bytes) {
if (bytes === 0) return '0B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
submitUpload() {
if (this.fileList.length === 0) {
return this.$message.error('请选择文件');
}
if (this.isUploading) {
return this.$message.info('正在上传中...');
}
this.isUploading = true;
this.uploadedCount = 0;
this.currentFileIndex = 0;
// 开始上传第一个文件
this.uploadNextFile();
},
// 上传请求
uploadRequest(options) {
// 此方法不使用,改用uploadNextFile
},
// 上传下一个文件
uploadNextFile() {
// 跳过已上传成功的文件
while (this.currentFileIndex < this.fileList.length && (this.fileList[this.currentFileIndex].status === 'success' || this.fileList[this.currentFileIndex].status === 'duplicate')) {
this.currentFileIndex++;
}
if (this.currentFileIndex >= this.fileList.length) {
// 所有需要上传的文件都已完成
this.isUploading = false;
if (this.uploadedCount > 0) {
this.$message.success('全部文件上传完成');
} else {
this.$message.info('没有需要上传的文件');
}
return;
}
const file = this.fileList[this.currentFileIndex];
file.status = 'uploading';
file.statusText = '上传中...';
const formData = new FormData();
formData.append('file', file.raw);
// 模拟上传进度
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
// 添加请求头配置
xhr.setRequestHeader('Authorization', `Basic 234234324`);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percentage = Math.round((e.loaded / e.total) * 100);
file.percentage = percentage;
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
file.status = 'success';
file.statusText = '上传成功';
this.uploadedCount++;
} else {
file.status = 'error';
file.statusText = `上传失败 (${xhr.status})`;
}
// 继续上传下一个文件
this.currentFileIndex++;
this.uploadNextFile();
};
xhr.onerror = () => {
file.status = 'error';
file.statusText = '上传失败 (网络错误)';
this.currentFileIndex++;
this.uploadNextFile();
};
xhr.send(formData);
},
handleRemove(file, fileList) {
console.log('文件已移除:', file.name);
},
removeFile(index) {
const file = this.fileList[index];
if (file.status === 'uploading') {
return this.$message.warning('正在上传的文件无法取消');
}
this.fileList.splice(index, 1);
},
cancelUpload() {
if (this.isUploading) {
return this.$message.warning('上传进行中,无法取消');
}
this.fileList = [];
},
/**
* 关闭弹窗
*/
hide() {
this.fileList = [];
this.isUploading = false;
this.visible = false;
},
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-dialog__header {
border-bottom: 1px solid #e4e7ed;
}
::v-deep .invoice-drop-zone .el-upload{ display: block;}
::v-deep .invoice-drop-zone .el-upload-dragger{
margin-top:20px; background:#fcf4eb; width:100%; height:auto; padding:0px 0 30px 0; display: flex; justify-content: center; align-items: center; flex-direction: column; border:2px dotted #e9b375; border-radius:5px; text-align: center;
.el-icon-upload{ color:#e9b375; font-size:80px; }
.txt{ color:#888; font-weight: bold; font-size:14px; margin-top:2px;}
.el-button{ background:#409EFF !important; padding:8px 10px !important; width:120px; display: block; margin:0 auto; margin-top:10px;}
}
::v-deep .upbtncan{ display: flex; justify-content: center; padding:20px 0; border-bottom:1px solid #eee;
.el-button{ width:120px; margin:0 5px; }
}
.upprot{ margin-top:15px; margin-bottom:15px;
.ht{ display: flex; justify-content: space-between; font-size:12px; font-weight: bold; margin-bottom:5px;}
}
.upload-list-container {
max-height: 300px;
overflow-y: auto;
margin-top: 15px;
/* 自定义滚动条样式 */
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
}
#uploader-list {
padding-right: 5px;
}
.upload-item{
border-bottom:1px solid #f5f5f5;
padding:10px 0;
display: flex;
align-items: center;
font-size:12px;
transition: all 0.2s ease;
&:hover {
background-color: #fafafa;
}
.name{
width: 40%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.size{
padding:0 10px;
width:120px;
color: #606266;
}
.fr{
display: flex;
align-items: center;
flex:1;
.pro{
flex:1;
margin-right:20px;
}
.status {
display: inline-block;
padding-left: 5px;
}
.wait{ color: #909399; }
.success{ color:#67c23a; }
.error{ color:#f56c6c; }
}
}
</style>
el-upload 上传文件并实现进度条
于 2025-07-10 20:48:57 首次发布