在前端实现文件下载(react)
copyright copyleft copywhatever
February 23, 2017
chng
— layout: post title: 在前端实现文件下载(React) date: 2017-02-23 categories: 前端 tag:
-
前端
一个故事
近期要在数据报表上加一个表格下载的功能,我想了以下几种方式:
- 由前端发请求,服务端查询数据库,将数据组装成excel或csv文件。由于公司的安全规范规定不允许将文件存储在本地服务器上,这个文件只能上传到云,再将下载url返回到前端。
- 前端希望下载的,是展示在用户眼前的一个table,因此数据已经存储在前端浏览器了。前端发送请求,将这些数据带回给服务端,由服务端打包成文件。
- 前面两种方式都需要BS之间交互,但既然数据已经在距离用户最近的地方了,完全可以在前端完成文件的下载,由前端将JSON转成csv,再写到本地。
由于页面基于React开发,于是我打开google和npm去搜寻可用的包:
- react-csv-downloader是一个下载组件,把data投给它,自动生成一个可以点击下载的按钮或文本。
- json2csv 可以将json转成csv,再配合文件api。
我用第一种方式,生成的csv文件存在中文乱码问题。网上看到的原因是MS Excel打开的csv文件需要以固定的BOM码开头(一个魔数)。于是决定采用第二种,自己解析自己写文件,于是掉入了另一个坑:npm上已经没有fs包了。即使诸如fs.io, level-fs, browserify-fs等包,也依赖fs。 巧合之下去node_modules目录下看react-csv-downloader源码,学会了一种处理文件的方式,Data Protocol。原来根本是无知的自己想太多了,根本不需要自己去实现写本地文件的过程,react-csv-downloader的代码就及其简单,并且实际上也兼顾了魔数的问题:
handleClick() {
const { suffix, prefix, bom } = this.props;
let bomCode = '';
let filename = this.props.filename;
if (filename.indexOf('.csv') === -1) {
filename += '.csv';
}
if (bom) {
bomCode = '%EF%BB%BF';
}
if (suffix) {
if (typeof suffix === 'string' || typeof suffix === 'number') {
filename = filename.replace('.csv', `_${suffix}.csv`);
}
else {
filename = filename.replace('.csv', `_${(new Date()).getTime()}.csv`);
}
}
if (prefix) {
if (typeof prefix === 'string' || typeof prefix === 'number') {
filename = `${prefix}_${filename}`;
}
else {
filename = `${(new Date()).getTime()}_${filename}`;
}
}
const a = document.createElement('a');
a.textContent = 'download';
a.download = filename;
a.href = `data:text/csv;charset=utf-8,${bomCode}${encodeURIComponent(this.state.csv)}`;
a.click();
}
重点在:
- 将魔数和符合csv文件格式的文本,按照data协议的格式拼接好,放在一个<a>标签里,然后触发<a> DOM的click事件。
a.href = `data:text/csv;charset=utf-8,${bomCode}${encodeURIComponent(this.state.csv)}`;
- 这个组件实际上把handleClick方法作为回调方法,让CsvDownloader组件的子组件的onClick进行代理,因此CsvDownloader里可以包含任何可以click的玩意。