vue+element-ui+xlsx實現校驗前端上傳的Excel文件

  • 2019 年 10 月 5 日
  • 筆記

運行程式碼請看:https://github.com/GloryXu/vue

背景

項目中需要校驗上傳的Excel模板是否符合規範。一開始的想法是在後端進行校驗,但是後來想到一個跑批的文件最大是2M,如果放置在後端校驗,對於不規範的文件,這2M的傳輸也就白費了,同時,對於用戶的體驗也很不好,就想把校驗放置在客戶端,不合乎規範的文件直接拒掉,節省頻寬同時客戶體驗也有所改善。

程式碼歷程

項目是使用vue+element-ui構建的web項目,那麼要實現前端Excel解析,就需要添加xlsx.js插件。 安裝插件npm i xlsx --save,當然也可以這樣cnpm i xlsx --save。

失敗的程式碼

剛開始的使用element-ui的程式碼如下,實現的上傳文件功能。本例中以下Excel格式則會校驗通過。

<el-upload       style="display: inline; margin-left: 10px;margin-right: 10px;"       action="#"       ref="fileupload"       :show-file-list="false"       :http-request="upLoadChange"       :before-upload="beforeUpload">       <el-button size="small" type="primary">上傳文件<i class="el-icon-upload el-icon--right"></i></el-button>     </el-upload>

將校驗的程式碼放置在beforeUpload方法中,程式碼如下:

  beforeUpload(file) {     // 讀取Excel文件並校驗返回Boolean值     let readExcelResult = this.readExcel(file);     console.log(readExcelResult);     if (readExcelResult) {       const isLt2M = file.size / 1024 / 1024 < 2;       if (!isLt2M) {         this.$message.error('文件大小不能超過2MB!');         return false;       }       this.$message.success('校驗成功!');       return true;     } else {       this.$message.error('校驗文件失敗');       return false;     }   }

readExcel方法程式碼如下:

readExcel(file) {// 解析Excel         let _this = this;         const reader = new FileReader();         reader.onload = (e) => {           try {             // 以二進位流方式讀取得到整份excel表格對象             var data = e.target.result, workbook = XLSX.read(data, {type: 'binary'});           } catch (e) {             this.$message.error(e.message);             return false;           }         // 表格的表格範圍,可用於判斷表頭是否數量是否正確           var fromTo = '';           // 遍歷每張表讀取           for (var sheet in workbook.Sheets) {             let sheetInfos = workbook.Sheets[sheet];             let locations = [];// A1,B1,C1...             if (workbook.Sheets.hasOwnProperty(sheet)) {               fromTo = sheetInfos['!ref'];// A1:B5               // 該方法獲取每列第一行的值,如:輸入A1:B5,返回['A1','B1'],該方法詳情可查看文章末尾的github具體程式碼               locations = _this.getLocationsKeys(fromTo);             }           for (let i = 0;i < locations.length; i++) {               let value = sheetInfos[locations[i]].v;             if (value != i) {                 this.$message.error(locations[i] + ''s parameter isn't ' + i);                 return false;               }             }             return true;           }         };         reader.readAsBinaryString(file);       }

發現問題

程式碼看上去沒問題,但是一直執行不通過,後來F12跟蹤了下,發現readExcelResult一直是undefined,所以就導致校驗異常。

優化的程式碼

  1. 相信絕大部分人都發現了問題,在readExcel方法中load相關的程式碼是非同步執行的,並不會阻塞,所以此方法很快就執行結束了,結果什麼也沒返回,就直接導致了上面的問題,readExcelResult為undefined,此時就開始搜非同步轉同步,果不其然,結果都是使用promise來實現。
  2. 再看到element-ui的官方文檔看到了這麼一句話: 看來只能是它了,來實現我所需要的功能。

微調後的程式碼

以下為beforeUpload改變後的程式碼:

  beforeUpload(file) {     let _this = this;     // 使返回的值變成Promise對象,如果校驗不通過,則reject,校驗通過,則resolve     return new Promise(function(resolve, reject){       // readExcel方法也使用了Promise非同步轉同步,此處使用then對返回值進行處理       _this.readExcel(file).then(result => {// 此時標識校驗成功,為resolve返回         const isLt2M = file.size / 1024 / 1024 < 2;         if (!isLt2M) {           _this.$message.error('文件大小不能超過2MB!');         }         if (isLt2M && result){           resolve('校驗成功!');         } else {           reject(false);         }       }, error => {// 此時為校驗失敗,為reject返回         _this.$message.error(error);         reject(false);       });     });   }

以下為readExcel改動後的程式碼

  readExcel(file) {// 解析Excel     let _this = this;     return new Promise(function(resolve, reject){// 返回Promise對象       const reader = new FileReader();       reader.onload = (e) => {// 非同步執行         try {           // 以二進位流方式讀取得到整份excel表格對象           var data = e.target.result, workbook = XLSX.read(data, {type: 'binary'});         } catch (e) {           reject(e.message);         }       // 表格的表格範圍,可用於判斷表頭是否數量是否正確         var fromTo = '';         // 遍歷每張表讀取         for (var sheet in workbook.Sheets) {           let sheetInfos = workbook.Sheets[sheet];           let locations = [];// A1,B1,C1...           if (workbook.Sheets.hasOwnProperty(sheet)) {             fromTo = sheetInfos['!ref'];// A1:B5             locations = _this.getLocationsKeys(fromTo);           }         for (let i = 0;i < locations.length; i++) {             let value = sheetInfos[locations[i]].v;             if (value != i) {// 自定的校驗規則,自由實現即可               // 校驗失敗reject               reject(locations[i] + ''s parameter isn't ' + i);             }           }           // 校驗成功resolve           resolve(true);         }       };       reader.readAsBinaryString(file);     });   }

執行成功

以上程式碼完美解決了之前遇到的問題,能正確校驗並返回了

總結

  1. 此時整個文件上傳到文件校驗整個過程就讓人很舒服。不用等很久再響應,如果還是一個校驗失敗的結果,用戶體驗就下去了。
  2. 使用的Promise是關鍵,非同步轉同步,整個的lambda語法也讓程式碼很容易理解,第一次使用,感覺尤為強大。
  3. 作為一個常年搞後端的人,偶爾解決一個自認為有點棘手的前端問題,感覺很有成就感,Mark下。

歡迎點評~~~~~~

Tags: