运行代码请看: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
,所以就导致校验异常。
readExcel
方法中load相关的代码是异步执行的,并不会阻塞,所以此方法很快就执行结束了,结果什么也没返回,就直接导致了上面的问题,readExcelResult
为undefined
,此时就开始搜异步转同步,果不其然,结果都是使用promise
来实现。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);
});
}
以上代码完美解决了之前遇到的问题,能正确校验并返回了
Promise
是关键,异步转同步,整个的lambda
语法也让代码很容易理解,第一次使用,感觉尤为强大。欢迎点评~~~~~~