uniapp app端导出excel的探索和实现(二)
彻底终结uniapp App环境导出Excel的需求
一、前言
之前在app中使用html模板导出的.xlsx的兼容性不好,只能在wps中直接打开,在MIcrosoft的excel中直接甩给我一个警告:
于是,又一次进入“轻松愉快”的探索之中,这一次将彻底终结!
二、xlsx-js-style & renderjs 强强联手
在上一篇文章中提到,uniapp的App环境有限制,缺少dom和bom等相关api及对象支持,就算xlsx-js-style能导出修改完样式的excel的二进制流,也不能通过Blob对象的相关操作写入excel中。但是,还有个东西我一直忽略了,那就是renderjs!
在renderjs中可以使用Blob对象和一系列被限制的方法,虽然有其他限制,但是足以支持导出excel的需求。
renderjs使用
1.基础
在uniapp中,renderjs类似WXS,在.vue中额外声明一个新的script标签,并设置module和lang属性,称为视图层,之前已有的script称为逻辑层。
<script module="trans" lang="renderjs">
</script>
引入第三方库:
import XLSX from "static/xlsx.bundle.js";
这里要注意路径,APP 端视图层的页面引用资源的路径相对于根目录计算,例如:static/test.js。可以把第三方js都放在static中。
2.通信
在App中,视图层和逻辑层是分开的,视图层不能直接调用逻辑层中变量和方法。
类似WXS,调用视图层中的方法很简单,即module名.方法名
,同时可以通过一个:prop
来传递一个逻辑层变量给视图层。
<view @tap="trans.onClick" :prop="testData">Yes</view>
视图层中定义的onClick方法,可以调用逻辑层方法
onClick(event, ownerInstance) {
// 调用逻辑层的方法,并传参
ownerInstance.callMethod('onViewClick', {
test: 'test'
})
}
使用:change来监听逻辑层的数据更新
<view @tap="trans.onClick" :prop="testData" :change:prop="trans.watchData">Yes</view>
变量更新后会调用视图层的方法
watchData(newValue, oldValue, ownerInstance, instance) {
console.log(newValue, 'newValue');
// 更新视图层相关数据
this.acceptData = newValue;
}
xlsx-js-style获取
一切从简,直接从 github: xlsx-js-style 中获取需要的两个js文件放入static文件夹,并在renderjs中引用
三、实现
数据格式如下(xlsx-js-style提供了从复杂结构中提取相关属性,并重组成可用数据的相关api,感兴趣可以自己研究)
excelData: [
{
no: "1",
tag: "weqwrqwrqwrqwrq",
remark: "22",
},
{
no: "2",
tag: "342343434343",
remark: "33",
},
{
no: "3",
tag: "25125152512",
remark: "44",
},
]
注意: 以下均在renderjs中 !!!
组合拳,将上面的数据保存为excel,并处理成为binary字符串:
let sheet = XLSX.utils.json_to_sheet(excelData);
// 修改表头 默认表头为对象中的键名
XLSX.utils.sheet_add_aoa(sheet, [["Name", "Birthday", "Age"]], {
origin: "A1", // 从A1单元格开始,A1,B1,C1,...
});
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, sheet, "Sheet1"); // sheet1 是自定义的sheet名
// H5 走这一步直接下载文件了
// XLSX.writeFile(workbook, 'example.xlsx');
// XLSX.write 会根据type返回值,为什么选择binary类型,因为binary好歹是个string,H5+的io写入api只支持写入string类型的
this.binaryData = XLSX.write(workbook, {
bookType: 'xlsx',
bookSST: false,
type: "binary",
});
到这一步,还是不能写入,因为plus.io里面虽然支持写入string到某个文件,但是直接通过write写入到binary string后会发现文件损坏。
那我们自然考虑到使用Blob,转换Blob后直接通过URL.createObjectURL(blob),然后一个a链接模拟点击来下载文件就行了。
但是!没有那么简单,创建一个blob链接,直接下载后,会发现统统下载失败,这里笔者没太弄清楚,有人说是因为js在客户端系统会被限制,不能直接下载文件。
那我们只能走系统的写入了,先创建.xlsx文件,最后写入string类型的数据。
binary string 不能直接转换为Blob,先转为ArrayBuffer,再转换为Blob:
// 字符串转 ArrayBuffer
s2ab(str) {
const buf = new ArrayBuffer(str.length)
const view = new Uint8Array(buf)
for (let i = 0; i !== str.length; ++i) view[i] = str.charCodeAt(i) & 0xff
return buf
},
this.blobData = new Blob([this.s2ab(this.binaryData)], {
type: 'application/octer-stream',
});
这里又有一个坑,我们不能直接写入Blob类型的数据,因为Blob本质上还是对象,别做傻事,JSON.stringify转换出来会是空对象。
就在一筹莫展的时候,有线人发来消息,称APP环境已支持存储 base64 为文件,并提供了reference
所以我们需要将Blob转换为base64:
var reader = new FileReader();
reader.readAsDataURL(this.blobData)
reader.onload = (evt) => {
this.base64Data = evt.target.result
};
通过这种方式获取的base64也不能直接写入,需要去除开头的一段base64标识:
this.base64Data = this.base64Data.replace(/^data:\S+\/\S+;base64,/, '');
现在我们终于完成了数据转换,只需要通过io写入文件就行了!
// #ifdef APP-PLUS
let that = this
// 将 ownerInstance 挂载到that上,否则写入完成后,无法通过ownerInstance调用逻辑层的方法 !!
that.ownerInstance = ownerInstance
// H5+ 文档 https://www.html5plus.org/doc/zh_cn/io.html
// renderjs中可以直接调用plus api
plus.io.requestFileSystem(plus.io.PUBLIC_DOCUMENTS, function(fs) {
let root = fs.root;
// 直接在 documents 中创建.xlsx文件
root.getFile(
fileName, {
create: true,
},
(fileEntry) => {
fileEntry.createWriter(
(writer) => {
// 写入文件成功完成的回调函数
writer.onwrite = (e) => {
// 逻辑层通过writeOver来处理导出后续逻辑...
that.ownerInstance.callMethod('writeOver', {
test: 'test'
})
};
writer.writeAsBinary(that.base64Data )
},
(err) => {
console.log(err, "创建文件写入器错误");
}
);
},
(err) => {
console.log(err);
}
);
});
// #endif
writer.writeAsBinary
可以写入base64,看到这个writeAsBinary
的名字,我在想之前xlsx-js-style导出的时候设置的type就是binary,是不是不用通过这么多转换了,试过后发现只是巧合,直接导出excel里全是乱码。
四、总结
- App环境中不能使用浏览器相关api的时候,可以考虑使用renderjs
- App环境,renderjs不能调用uni相关接口
- H5+ Api已经支持写入base64格式的文件,暂不支持直接写入Blob(截止2023/02/17,writeAsBinary未更新到官网)
- App环境,直接通过Blob链接下载会失败(Andriod,ios未测试)
五、参考文章&相关文档
以上!
更多推荐
所有评论(0)