文件上传组件

99 阅读3分钟

前言

大家好,这里是藤原豆腐店,文件上传在项目中的使用频率很高,我这里对原来项目中的文件上传组件进行优化,使他支持上传多个文件,同时又不影响原来单文件上传的功能

相关配置项和数据处理

props: {
  // 支持的格式
  allowType:{
    type:String,
      default:'txt/xlsx/xls/docx/doc'
  },
  // 单文件上传
  popFileUrl:{
    type:String,
      default:""
  },
  popFileName:{
    type:String,
      default:""
  },
  // 多文件上传
  multiFileList:{
    type:Array,
      default:() => ([])
  },
  // 文件最大大小
  maxSize:{
    type:Number,
      default:20
  },
  // 按钮文案
  title:{
    type:String,
      default:"点击上传文档"
  },
  // 是否显示图标
  isShowIcon:{
    type:Boolean,
      default:false
  },
  // 是否显示蓝色图标
  isShowBlueIcon:{
    type:Boolean,
      default:false
  },
  // 是否显示提示
  showTip:{
    type:Boolean,
      default:true
  },
  // 是否获取上传文件
  getFile:{
    type:Boolean,
      default:false
  },
  // 按钮的宽度
  width:{
    type:Number,
      default:192
  },
  // 按钮的高度
  height:{
    type:Number,
      default:40
  },
  // 上传文件的数量
  fileNumber:{
    type:Number,
    default:5
  } 
},
data(){
  return {
    fileUrl:'',//文件地址
    fileName:'',//文件名称
    uploadErr:false,//是否上传失败
    tooltip:'',//提示文字
    disabled:false//是否禁用上传
  }
},
watch:{
  popFileUrl: {
    handler(nVal) {
      this.fileUrl = nVal
    },
    immediate: true,
    deep:true
  },
  popFileName: {
    handler(nVal) {
      this.fileName = nVal
    },
    immediate: true
  },
  fileNumber:{
    handler(nVal) {
      this.tooltip = `最多支持上传${nVal}个附件`
    },
    immediate: true
  },
  multiFileList:{
    handler(nVal) {
      // 判断是否超出附件数量
      nVal.length>=this.fileNumber?this.disabled=true:this.disabled=false
    },
    immediate: true
  }
},

文件上传功能实现

 <el-upload
  :before-upload="checkFile"
  :on-success="uploadSuccess"
  action="https://upa.pcauto.com.cn/upload_quick.jsp?application=autopocket&referer=https://priceadmin.pcauto.com.cn:7100/"
  v-show="!fileUrl && multiFileList.length==0"
  :limit="fileNumber"
  multiple
>
  <span class="el-button el-button__uploadfile" :class="isShowBlueIcon ? 'blue-btn' : ''" :style="{width:`${width}px`,height:`${height}px` }"> 
    <img src="@/assets/img/icon-upload.png" v-if="isShowIcon && !isShowBlueIcon"/>
    <img src="@/assets/img/upload-icon.png" v-if=" !isShowIcon && isShowBlueIcon"/> 
    {{title}}
  </span>
  <div slot="tip" class="el-upload__tip">  <span v-if="showTip">支持上传{{allowType}}格式,文件大小请控制在20M以内</span> <span
    style="color:red;margin-left: 10px;" v-if="uploadErr"
  ><i class="el-icon-warning"></i>上传失败,请重新上传</span></div>
</el-upload>

上传文件前检查文件是否符合要求

checkFile(e){
  let file = e
  let size = file.size / 1024 // 转为kb
  // 检查文件格式
  let type = file.name.split('.').pop()//获取文件格式
  let allowFileType = this.allowType.split('/')//支持文件格式
  if(!allowFileType.includes(type)) {
     this.$message(`文件类型只支持${this.allowType}, 请重新上传文件`)
     return false
  }
  // 检测文件大小
  let fileMaxSize = this.maxSize * 1024
  if (size > fileMaxSize) {
    this.$message(`文件不能超过${this.maxSize}M`)
    return false;
  }
  // 图片转 base64 位
  if(type=='png' || type=='PNG' ||type=='jpg' || type=='JPG' ){
    this.toBase64(file)
    return false;
  }
  // 传递文件给父组件
  if(this.getFile){
    this.$emit('getFileObj',file)
  }
}

图片上传做特殊处理

/* 图片转 base64 位*/
async toBase64(e) {
  const self = this;
  let file = e;
  let reader = new FileReader();
  // 获取文件类型
  let fileType = file.name.split('.').pop()
  
  // 读取文件并将其转换为 base64
  reader.readAsDataURL(file);
  //文件读取完成时触发
  reader.onload = async (e1)=> {
    let image = new Image()
    
    // 设置图片源为 base64 数据
    image.src = e1.target.result
    
    // 加载图片后的操作
    let resData = await self.loadImage(image)
    
    // 设置图片的 URL
    self.urlPic = e1.target.result
    
    // 将文件上传到阿里云
    let res = await self.aliyunUploadImg(fileType,file)
    
    if(res.retCode==0){
      let fileImg = {}
      // 在上传的文件列表中找到原始图片
      res.files.forEach(item => {
          if (item.isorg==1) {
              fileImg = item
          }
      })
      
      let fileUrl=""
      
      // 根据是否为小程序选择不同尺寸的 URL
      if(this.isMini){
        fileUrl = fileImg.url.slice(0,fileImg.url.lastIndexOf(".")) +'_180x320'+fileImg.url.slice(fileImg.url.lastIndexOf("."),fileImg.url.length)
      }else{
        fileUrl = fileImg.url
      }
      
      // 替换 URL 中的 http 为 https
      this.fileUrl = fileUrl.replace('http:','https:')
      
      // 设置文件名
      this.fileName = fileImg.name
      
      // 发送文件信息给父组件
      this.$emit('getFileObj',{
        fileUrl: fileUrl.replace('http:','https:'),
        fileName: fileImg.name
      })
    }else{
      
      // 弹出上传失败的提示框
      this.$confirm('图片上传失败', '', {
          confirmButtonText: '确定',
          showCancelButton:false,
      });
    }
  }
}
// 返回图像的宽高
async loadImage(imgObj){
  return new Promise( async (resolve,reject)=>{
    imgObj.onload = ()=>{
      let obj = {
        width:imgObj.width,
        height:imgObj.height
      }
      resolve(obj)
    }
  })
}
/* 阿里云OCC 上传 获取url*/
async aliyunUploadImg(fileType,file){
  const params ={
    fileExt : fileType || 'jpg',
    appCode,
    watermarkProcess:0
  }
  let res = await getUploadVoucher(Qs.stringify(params));
  // 图片数据
  let imgFile = {
      retCode: -1,
      files: []
  }
  if(res.status==200 && res.data){
      const formData = new FormData()
      formData.append('key', res.data.key)
      formData.append('policy', res.data.policy)
      formData.append('OSSAccessKeyId', res.data.ossAccessKeyId)
      formData.append('success_action_status', 200)
      formData.append('signature', res.data.signature)
      formData.append('size', file.size)
      formData.append('file', file)
      let resImg = await getUploadAliYun(res.data.hostUrl.replace('http:','https:'), formData);
      if(resImg.status==200&& resImg.data==""){
          // 获取图片信息
          let resinfo = await getAliYunImgInfo(`${res.data.publicUrl.replace('http:','https:')}?x-oss-process=image/info`)
          console.log('resinfo',res.data);
          imgFile.retCode = 0
          imgFile.files.push({
              isorg: 1,
              url: res.data.publicUrl,
              orgFileName: 'ali_' + res.data.imageId,
              name: file.name,
              width: resinfo.data.ImageWidth.value || '',
              height: resinfo.data.ImageHeight.value || ''
          })
      }
      return imgFile
  }else{
      this.$confirm('签名获取失败,请重新上传', '提示信息', {
          type: 'error',
          showCancelButton:false,
          showClose:false,
      });
      return imgFile
  }
},

图片上传成功

uploadSuccess(e){
  if(e.retCode==0){
    this.fileUrl=e.files[0].url
    this.fileName=e.files[0].orgFileName
    this.uploadErr = false
    if(this.getFile)return
    this.$emit('getFileObj',{
      fileUrl: e.files[0].url,
      fileName: e.files[0].orgFileName
    })
  }else{
    this.uploadErr = true
  }
},

支持单文件

上传后通过点击重新上传,重新上传文件

 <!-- 单文件上传 -->
<div class="upload-demo upload-video" v-else-if="fileUrl">
  <!-- 已上传文件 -->
  <div class="file-name fl" :class="isShowBlueIcon ? 'blue-btn' : ''">
    <img src="@/assets/img/icon-att-link.png" v-if="isShowIcon || isShowBlueIcon"/>
    <span> {{fileName}} </span> 
    <img src="@/assets/img/icon-delete-white.png" style="cursor: pointer;" v-if="isShowIcon || isShowBlueIcon" @click="deleteFile"/>
  </div>
  <!-- 重新上传 -->
  <el-upload
    :before-upload="checkFile"
    :on-success="uploadSuccess"
    action="上传地址"
    v-show="fileUrl"
    class="fl"
  >
    <span class="upload-again" :class="isShowBlueIcon ? 'blue' : ''">重新上传 </span>
  </el-upload>
</div>
/deep/.upload-file-wrap .upload-demo .file-name{
  @include font_color('input_font');
  display: flex;
  align-items: center;
  height: 40px;
  line-height: 40px;
  padding: 0 20px 0 20px;
  @include bg_color("table_th_bg");
  border-radius: 4px;

  img{
    width: 16px;
    height: 16px;
    display: inline-block;
    vertical-align: bottom;
    margin-right: 4px;
  }
  img:last-child{
    margin-left: 10px;
  }
  span{
    max-width: 300px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}
.fl {
  float: left;
}

子组件内单文件删除

 // 单文件删除
deleteFile(){
  this.fileName = ''
  this.fileUrl = ''
  this.$emit('deleteFileObj')
},

父组件调用通过配置项定制文件大小,格式等

<el-form-item class="el-form-item__fileup upload-att" label="">
  <uploadFiles
    title="上传文件"
    :showTip="false"
    :maxSize="50"
    :popFileName="queryform.attachmentName"
    :popFileUrl="queryform.attachment"
    allowType="doc/docx/xls/xlsx/ppt/pdf/jpg/png"
    @getFileObj="getFileObj"
    @deleteFileObj="deleteFileObj"
  ></uploadFiles>
</el-form-item>

通过两个自定义方法获取和删除文件

// 获取文件
getFileObj(obj) {
  this.queryform.attachment = obj.fileUrl.replace("http:", "https:");
  this.queryform.attachmentName = obj.fileName;
},
// 删除文件
deleteFileObj() {
  this.queryform.attachment = "";
  this.queryform.attachmentName = "";
},

支持多文件

到达文件上限时,禁用上传按钮同时移到上面会有提示

<!-- 多文件上传 -->
<div class="multifile" v-if="multiFileList.length!==0">
  <!-- 已上传文件 -->
  <div class="upload-demo upload-video">
    <div v-for="(item,index) in multiFileList">
      <div class="file-name fl" :class="isShowBlueIcon ? 'blue-btn' : ''" style="margin:0 10px 20px 0"> 
        <img src="@/assets/img/icon-att-link.png" v-if="isShowIcon || isShowBlueIcon"/>
        <span> {{item.attachmentName}} </span> 
        <img src="@/assets/img/icon-delete-white.png" style="cursor: pointer;" v-if="isShowIcon || isShowBlueIcon" @click="deleteFileList(index)"/>
      </div>
    </div>
  </div>
  <!-- 继续上传 -->
  <div>
    <el-upload
      :before-upload="checkFile"
      :on-success="uploadSuccess"
      action="https://upa.pcauto.com.cn/upload_quick.jsp?application=autopocket&referer=https://priceadmin.pcauto.com.cn:7100/"
      v-show="multiFileList.length!==0"
      :disabled="disabled"
    >
      <el-tooltip :effect="curTheme == 'dark' ? 'dark' : 'light'" :content="tooltip" placement="bottom" :disabled="!disabled">
        <span class="el-button" :class="[isShowBlueIcon ? 'blue-btn' : '',disabled ? 'el-button__uploadfile__keep_disable':'el-button__uploadfile__keep']"> 
          <img src="@/assets/img/icon-upload.png" v-if="isShowIcon && !isShowBlueIcon && !disabled"/>
          <img src="@/assets/img/icon-upload-grey.png" v-if=" isShowIcon  && !isShowBlueIcon && disabled"/>
          <img src="@/assets/img/upload-icon.png" v-if=" !isShowIcon && isShowBlueIcon"/>
          <span class="upload_keep">继续上传</span>
        </span>
      </el-tooltip>
    </el-upload>
  </div>
</div>

设置不同状态下上传按钮的样式

/deep/.upload-file-wrap .el-button__uploadfile__keep{
  @include font_color('input_font_orange');
  @include bg_color("menuS_bg_main");
  @include border_color('comm_font_main_active');
  width: 100px;
  height: 40px;
  border-radius: 4px;
}
/deep/.upload-file-wrap .el-button__uploadfile__keep_disable{
  @include bg_color("menuS_bg_main");
  color: #999999;
  border: 1px solid #999999;
  width: 100px;
  height: 40px;
  border-radius: 4px;
} 

多文件删除

// 多文件删除
deleteFileList(index){
  this.fileName = ''
  this.fileUrl = ''
  this.$emit('deleteFileList',index)
},

父组件调用

<el-form-item class="el-form-item__fileup upload-att" label="">
  <uploadFiles
    title="上传文件"
    :isShowIcon="true"
    :showTip="false"
    :maxSize="50"
    :multiFileList="queryform.file"
    @getFileObj="getFileObj"
    allowType="doc/docx/xls/xlsx/ppt/pdf/jpg/png"
    @deleteFileList="deleteFileList"
  ></uploadFiles>
</el-form-item>
// 获取上传文件
getFileObj(obj) {
  const fileArr = {
    attachment: obj.fileUrl.replace("http:", "https:"),
    attachmentName : obj.fileName
  }
  this.queryform.file.push(fileArr)
}
// 删除文件
deleteFileList(index) {
  this.queryform.file.splice(index,1)
},

结尾

这个组件还有很多缺漏,比如不支持一次性上传多个文件等,如果有什么建议欢迎在评论区交流,最后看到这里的朋友如果觉得文章还可以的话,可以给我点点赞吗,万分感谢!!