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相关的代码是异步执行的,并不会阻塞,所以此方法很快就执行结束了,结果什么也没返回,就直接导致了上面的问题,readExcelResultundefined,此时就开始搜异步转同步,果不其然,结果都是使用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下。

欢迎点评~~~~~~