el-upload 上传文件并实现进度条

<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>
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值