一、前言

之前在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未测试)

五、参考文章&相关文档

以上!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐