vue-simple-uploader組件的一些使用心得

前言

因為項目需要上傳大文件,考慮使用分片上傳锋恬、斷點續(xù)傳這些功能故選用vue-simple-uploader屯换,期間踩的一些坑及解決方法記錄一下。

git文檔鏈接

1.https://github.com/simple-uploader/vue-uploader/blob/master/README_zh-CN.md
2.https://github.com/simple-uploader/Uploader/blob/develop/README_zh-CN.md

使用vue-simple-uploader

1.安裝插件:

npm install vue-simple-uploader --save

2.main.js中初始化

import uploader from 'vue-simple-uploader'
Vue.use(uploader)

3.在*.vue文件中使用
代碼僅供參考

        <uploader 
          ref="uploader"
          class="avatar-uploader"
          :options="options"   
          :file-status-text="statusText"
          @file-added="onFileAdded"
          @file-success="onFileSuccess"
          @file-progress="onFileProgress"
          @file-error="onFileError"
        >
          <!-- :autoStart=true -->
          <!-- @file-removed="fileRemoved" -->
          <uploader-unsupport></uploader-unsupport>
          <uploader-btn
          :single=true
          title="(800M以上)"
          >大文件上傳</uploader-btn>
        </uploader>

        <el-collapse-item v-for="f in fileList" :key="f.uid" :name="f.uid">
          <template slot="title">
            <el-col :span="6">
              <span class="pull-left name" :title="f.name">{{f.name}}</span>
              <span class="pull-left status" v-bind:class="{error: f.state === 2}" v-text="statusStr(f.state)"></span>
            </el-col>
            <el-col :span="6" v-if="f.progress < 100 && f.state !== 2">
              <el-progress class="progress" :text-inside="true" :stroke-width="15" :percentage="f.progress"></el-progress>
            </el-col>
            <i class="el-icon-my-close close-btn" title="刪除" @click="delFile($event, f)"></i>
            <span v-show="f.showPP">
            <i class="el-icon-my-play play-btn" v-show='!f.isPlayOrPause' title="開始" @click="playFile($event, f)"></i>
            <i class="el-icon-my-pause pause-btn" v-show='f.isPlayOrPause' title="暫停" @click="pauseFile($event, f)"></i>
            </span>
          </template>
          <el-row ref="formWrapper">
            <form-upload :info.sync="formObj" :file.sync="f" @deleteFile="delFileByFileuid" @expand="expandCollapse"></form-upload>
          </el-row>
        </el-collapse-item>

4.data中參數(shù)定義

      options:{
        target:"/file/fdfs/multipart-upload/chunkUpload",//即分片上傳的URL
        chunkSize: 10 * 1024 * 1024,//分片大小
        simultaneousUploads:3,//并發(fā)上傳數(shù)与学,默認(rèn) 3
        testChunks: true,//是否開啟服務(wù)器分片校驗
        checkChunkUploadedByResponse: function (chunk, res) {// 服務(wù)器分片校驗函數(shù)彤悔,秒傳及斷點續(xù)傳基礎(chǔ)
        //需后臺給出對應(yīng)的查詢分片的接口進行分片文件驗證
          let objMessage = JSON.parse(res);//skipUpload、uploaded 需自己跟后臺商量好參數(shù)名稱
          if (objMessage.skipUpload) {
            return true;
          }
          return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
        },
        maxChunkRetries: 2, //最大自動失敗重試上傳次數(shù)
        headers: {//在header中添加token驗證索守,Authorization請根據(jù)實際業(yè)務(wù)來
          getUploadHeaders()
        },
        processParams(params) {//自定義每一次分片傳給后臺的參數(shù)晕窑,params是該方法返回的形參,包含分片信息,
          //若不自定義則默認(rèn)會把文件所有參數(shù)傳給后臺卵佛,自己可以通過接口查看插件本身傳遞的參數(shù)
          return {//返回一個對象杨赤,會添加到每一個分片的請求參數(shù)里面
            totalChunks: params.totalChunks,
            identifier:params.identifier,
            chunkNumber: params.chunkNumber,
            chunkSize: 10 * 1024 * 1024,
            //  這是我跟后臺約定好的參數(shù)蓝丙,可根據(jù)自己項目實際情況改變
          };
        },
      }

文件鉤子的使用

主要講一下幾個文件上傳鉤子和實例方法的使用,由于官方文檔寫的也不是很清楚望拖,踩了很多坑
代碼僅供參考

    onFileAdded(file,fileList) {
      file.pause()
      if(file.getType()!='zip'){
        EventBus.$emit('alert.show', {type: 'warning', msg: `只能上傳.zip格式的文件`});
        file.ignored = true//文件校驗渺尘,不符規(guī)則的文件過濾掉
        // file.cancel()
        // return false;
      }
      else if(file.size<=800*1024*1024){
        EventBus.$emit('alert.show', {type: 'warning', msg: `請上傳800M以上的文件`});
        file.ignored = true//文件過濾

      }
      else if(this.fileList.length >= 10){
        EventBus.$emit('alert.show', {type: 'warning', msg: `最多上傳10份文件`});
        file.ignored = true//文件過濾
      }else{
        // 新增文件的時候觸發(fā),計算MD5
        this.myMD5(file);
      }
    },
    onFileSuccess(rootFile, file, response, chunk){
      //文件成功的時候觸發(fā)
      let index = this.findFileById(file.uniqueIdentifier);
      let res = JSON.parse(response)
      if(res.result==="上傳成功"){
        this.uploadingFileStr(res.path,getUploadHeaders()).then(ress=>{
          if(ress.data.msgCode===1){
            if(index > -1){
              this.fileList[index].id = ress.data.data.id;
              this.fileList[index].resName = file.name.replace(/\.\w+$/g, '');
              this.fileList[index].name = file.name;
              this.fileList[index].filePath = null;
              this.fileList[index].coverPath = null;
              this.fileList[index].progress = 100;
              this.fileList[index].status = "success";
              this.fileList[index].state = 1;
              this.fileList[index].isPlayOrPause=false;
              this.fileList[index].showPP = false;
              this.expandCollapse(file.uniqueIdentifier);
            }
          }else{
            EventBus.$emit('alert.show', {type: 'error', msg: res.result});
            if(index > -1){
              this.fileList[index].status = 'fail';
              this.fileList[index].state = 2;
              this.fileList[index].isPlayOrPause=false;
              this.fileList[index].showPP = false;
            }
          }
        })
      }
    },
    onFileError(rootFile, file, response, chunk){
      let res = JSON.parse(response)
      EventBus.$emit('alert.show', {type: 'error', msg: res.result});
      let index = this.findFileById(file.uniqueIdentifier);
      if(index > -1){
        this.fileList[index].status = 'fail';
        this.fileList[index].state = 2;
        this.fileList[index].isPlayOrPause=false;
        this.fileList[index].showPP = false;
      }
    },
    onFileProgress(rootFile, file, chunk){
      let index = this.findFileById(file.uniqueIdentifier),
        p = Math.round(file.progress()*100);
      if(index > -1){
        if(p < 100){
          this.fileList[index].progress = p;
        }
        this.fileList[index].status = file.status;
      }
    },
    myMD5(file) {//這里主要是使用MD5對文件做一個上傳的查重
      let md5 = "";
      md5 = SparkMD5.hash(file.name);//業(yè)務(wù)需求以文件名作為加密
      let index = this.findFileById(md5);
      if(index==-1){
        file.uniqueIdentifier = md5;
        this.fileList.push({
          id: null,
          uid: file.uniqueIdentifier,
          filePath: '',
          coverPath: '',
          name: file.name,
          resName: '',
          progress: 0,
          state: 0,
          status: '',
          isPlayOrPause:true,
          showPP:true,
          elMD5:md5   //多余的參數(shù)可注釋
        });
        //繼續(xù)上傳文件
        file.resume();
      }else{
        EventBus.$emit('alert.show', {type: 'warning', msg: `該文件已上傳至列表说敏,請勿重復(fù)上傳`});
        file.ignored = true//文件過濾
      }
    },
    playFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=true
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)//兼容點擊上傳按鈕
      uploaderInstance.fileList[index].resume();
    },
    pauseFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=false
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
      uploaderInstance.fileList[index].pause();
    },
uoloader實例的一些方法和鉤子

1.@file-added="onFileAdded" file-added(file,filelist)相當(dāng)于before-upload鸥跟,即在上傳文件之前對文件進行的操作,一般用來做文件驗證盔沫。如果沒有配置autoStart=false點擊上傳文件按鈕之后文件會自動上傳

autoStart {Boolean}
默認(rèn) true, 是否選擇文件后自動開始上傳医咨。

我們要在onFileAdded()中通過pause()方法暫停文件上傳

   onFileAdded(file,fileList) {
      file.pause()//用來暫停文件上傳
      if(file.getType()!='zip'){//根據(jù)自己項目要求進行文件驗證
        EventBus.$emit('alert.show', {type: 'warning', msg: `只能上傳.zip格式的文件`});
        file.ignored = true//文件校驗,不符規(guī)則的文件過濾掉
        // file.cancel()//清除文件
        // return false;
      }
  }

這里要講的是file.cancel()file.ignored = true拟淮。

先講一下這個插件獲取uploader實例的方法
const uploaderInstance = this.$refs.uploader.uploader
uploaderInstance 就是uploader實例

在項目中點擊上傳文件按鈕(第一次上傳)谴忧,文件成功加入fileList之后,再次上傳此文件(第二次)沾谓,會進入onFileAdded()進行文件驗證委造,因為我進行文件查重,重復(fù)的就不在上傳了昏兆,所以報錯之后使用uploader實例提供的file.cancel()方法清除文件妇穴,此后再有點擊文件上傳此文件都不會跳入onFileAdded()方法爬虱,更不會進行驗證和報錯,這對于用戶來說無疑是一個巨大的BUG腾它。
通過檢查log發(fā)現(xiàn),每次點擊上傳文件之后插件會在uploader實例中的files數(shù)組中加入當(dāng)前文件的信息携狭,用插件實例方法cancel()是清除不掉當(dāng)前文件的逛腿。如果使用uploaderInstance.cancel()則會清除包括正在上傳,暫停上傳单默,已上傳成功的所有的文件信息。使用file.ignored = true使驗證失敗的文件不加入uploader實例中的files數(shù)組引颈,則下次點擊上傳文件按鈕可再次調(diào)起onFileAdded()進行驗證

uploader實例中的fileList數(shù)組中的文件才是實際正在上傳文件列表蝙场,對于uploader實例中files,file,fileList這幾個數(shù)組的具體作用有待研究

2.@file-success="onFileSuccess" 文件上傳成功的回調(diào)

    onFileSuccess(rootFile, file, response, chunk){
      //文件成功的時候觸發(fā)
    },

3.@file-progress="onFileProgress" 用來獲取文件的實時上傳進度

    onFileProgress(rootFile, file, chunk){
      let index = this.findFileById(file.uniqueIdentifier),//通過index來獲取對應(yīng)的文件progress
          p = Math.round(file.progress()*100);
      if(index > -1){
        if(p < 100){
          this.fileList[index].progress = p;
        }
        this.fileList[index].status = file.status;
      }
    },

4.@file-error="onFileError" 文件上傳失敗的回調(diào)

    onFileError(rootFile, file, response, chunk){
      //文件上傳失敗的回調(diào)
    },

5.@file-removed="fileRemoved" 刪除文件的回調(diào)
也可自定義刪除的按鈕不使用此鉤子

  delFile(e, f){
      e.stopPropagation();
      let txt = '是否刪除選中數(shù)據(jù)?';
      if(f.id){
        txt = '該資源還未提交信息售滤,確認(rèn)刪除選中數(shù)據(jù)?';
      }
      this.$confirm(txt, '刪除', {
        type: 'warning',
        showCancelButton: false
      }).then(() => {
        const uploaderInstance = this.$refs.uploader.uploader
        let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
        if(index>-1){
          uploaderInstance.fileList[index].cancel();   //這句代碼是刪除所選上傳文件的關(guān)鍵
        }
        this.delFileByFileuid(f.uid);
      }).catch(console.error);
    },

6.文件的暫停上傳和繼續(xù)上傳

    playFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=true
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)//兼容點擊上傳按鈕
      uploaderInstance.fileList[index].resume();   //
    },
    pauseFile(e,f){
      e.stopPropagation();
      f.isPlayOrPause=false
      const uploaderInstance = this.$refs.uploader.uploader
      let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
      uploaderInstance.fileList[index].pause();   //
    },

在第5和第6點中對刪除文件,暫停赐俗,繼續(xù)上傳都需要通過獲取uploader實例中fileList對應(yīng)的Index來進行操作
即:
const uploaderInstance = this.$refs.uploader.uploader
let index = uploaderInstance.fileList.findIndex(e => e.uniqueIdentifier === f.uid)
uploaderInstance.fileList[index].pause();// resume();cancel();

7.關(guān)于斷點續(xù)傳

官方文檔是這樣介紹的
checkChunkUploadedByResponse 可選的函數(shù)用于根據(jù) XHR 響應(yīng)內(nèi)容檢測每個塊是否上傳成功了阻逮,傳入的參數(shù)是:Uploader.Chunk 實例以及請求響應(yīng)信息秩彤。這樣就沒必要上傳(測試)所有的塊了

      checkChunkUploadedByResponse: function (chunk, res) {// 服務(wù)器分片校驗函數(shù),秒傳及斷點續(xù)傳基礎(chǔ)
        //需后臺給出對應(yīng)的查詢分片的接口進行分片文件驗證
          let objMessage = JSON.parse(res);//skipUpload币励、uploaded 需自己跟后臺商量好參數(shù)名稱
          if (objMessage.skipUpload) {
            return true;
          }
          return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
        },

checkChunkUploadedByResponse()options中的一個配置項珊拼,配置之后上傳文件之前會自動調(diào)起一個get請求獲取已上傳的分片信息澎现。返回的內(nèi)容自己跟后臺協(xié)商每辟,一般是返回已上傳的分片的index數(shù)組,然后前端通過對返回的分片index進行篩選跳過已上傳的分片妹蔽,只上傳未上傳的分片挠将。

  1. MD5文件驗證
    斷點續(xù)傳及秒傳的基礎(chǔ)是要計算文件的MD5,這是文件的唯一標(biāo)識乳丰,然后服務(wù)器根據(jù)MD5進行判斷,我這里主要是用來做文件查重判斷
    我這里使用的加密工具是spark-md5内贮,可以通過npm來安裝
npm install spark-md5 --save

在當(dāng)前vue文件引用即可

import SparkMD5 from 'spark-md5';

file有個屬性是uniqueIdentifier,代表文件唯一標(biāo)示粘勒,我們把計算出來的MD5賦值給這個屬性 file.uniqueIdentifier = md5

9.關(guān)于file.getType()的坑

getType()方法用于獲取文件類型屎即。
file.getType() ===>'zip' 等文件類型

如我在onFileAdd方法中做的文件格式驗證

    if(file.getType()!='zip'){
        EventBus.$emit('alert.show', {type: 'warning', msg: `只能上傳.zip格式的文件`});
        file.ignored = true
      }

但是項目提測之后發(fā)現(xiàn)驗證文件格式老是出問題,檢查log發(fā)現(xiàn)在我的電腦上file.getType()返回的是zip,在測試的電腦上返回的是x-zip-compressed埃撵。另外又試了其他格式的文件輸出其文件格式如下圖虽另,發(fā)現(xiàn)兩臺電腦的zip格式文件返回的不一樣捂刺。關(guān)鍵的問題就在于不同電腦上返回的文件格式不一樣(有待研究該api),所以對于getType()的使用得根據(jù)自己項目來考慮自己獲取還是使用該api
提供一個解決方案森缠,截取file.name的值獲取后綴

file.name.substring(file.name.lastIndexOf(".")+1) 

另外關(guān)于文件上傳的格式application/x-zip-compressed仪缸,application/zip,application/vnd.ms-excel……有興趣的小伙伴可以自行研究一下插件的底層代碼

電腦1

電腦2

功能完成

圖1

說明:兼容了el-upload的上傳按鈕,第4個文件是用el-upload按鈕進行上傳的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末跨晴,一起剝皮案震驚了整個濱河市端盆,隨后出現(xiàn)的幾起案子费封,更是在濱河造成了極大的恐慌,老刑警劉巖访敌,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衣盾,死亡現(xiàn)場離奇詭異,居然都是意外死亡阻塑,警方通過查閱死者的電腦和手機陈莽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來独柑,“玉大人私植,你說我怎么就攤上這事曲稼。” “怎么了贫悄?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵窄坦,是天一觀的道長。 經(jīng)常有香客問我拴袭,道長曙博,這世上最難降的妖魔是什么父泳? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任吴汪,我火速辦了婚禮漾橙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘霜运。我一直安慰自己,他們只是感情好藕各,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布激况。 她就那樣靜靜地躺著,像睡著了一般竭讳。 火紅的嫁衣襯著肌膚如雪浙踢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音奋岁,去河邊找鬼闻伶。 笑死,一個胖子當(dāng)著我的面吹牛蓝翰,可吹牛的內(nèi)容都是我干的畜份。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼停蕉,長吁一口氣:“原來是場噩夢啊……” “哼钙态!你這毒婦竟也來了册倒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤灿意,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甲捏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞭执,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡兄纺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年估脆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片付材。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叭爱,靈堂內(nèi)的尸體忽然破棺而出前鹅,到底是詐尸還是另有隱情,我是刑警寧澤页徐,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布变勇,位于F島的核電站贴唇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜授嘀,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一蹄皱、第九天 我趴在偏房一處隱蔽的房頂上張望芯肤。 院中可真熱鬧崖咨,春花似錦油吭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟹腾。三九已至,卻和暖如春娃殖,著一層夾襖步出監(jiān)牢的瞬間珊随,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工鲫凶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衩辟,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓昼钻,卻偏偏與公主長得像然评,于是被迫代替她去往敵國和親狈究。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容