前端文件导出下载

最近接触到了一个业务,需要将接口数据以CSV文件格式。以此文记录一下。

1.Blob对象

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

要从其他非blob对象和数据构造一个Blob,请使用Blob()构造函数。

Blob构造函数

Blob()构造函数返回一个新的Blob对象。blob的内容由参数数组中给出的值的串联组成。

语法:

1
var aBlob = new Blob( array, options );

其中参数:

  • array:是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。
  • options:是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性:
    • type:默认值为 “”,它代表了将会被放入到blob中的数组内容的MIME类型。
    • endings,默认值为”transparent”,用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个:
      • “native”,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者
      • “transparent”,代表会保持blob中保存的结束符不变

如:

1
2
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含DOMString的数组
var oMyBlob = new Blob(aFileParts, {type : 'text/html'}); // 得到 blob

Blob属性

Blob.size

只读。Blob 对象中所包含数据的大小(字节)。

Blob.type

只读。一个字符串,表明该 Blob 对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。

方法

Blob.slice([start[, end[, contentType]]])

返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。

Blob.close()

关闭 Blob 对象,以便能释放底层资源。

兼容情况

IE需IE10及以上,移动端基本全兼容。

保险起见也可以做下兼容处理,兼容处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 获取blob对象的兼容性写法
* @param buffer
* @param format
* @returns {*}
*/
function getBlob(buffer, format) {
try {
return new Blob(buffer, {type: format});
} catch (e) {
var bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder);
buffer.forEach(function(buf) {
bb.append(buf);
});
return bb.getBlob(format);
}
}

*分片上传

其实也就是将文件分为多个blob文件,再一个个上传。如:

1
2
3
4
5
6
7
8
9
const BYTES_PER_CHUNK = 1024 * 1024; // 每个文件切片大小定为1MB .
let blob = document.getElementById('file').files[0], // 有个input,id叫file
slices = Math.ceil(blob.size / BYTES_PER_CHUNK);
let blobs = [];
slices.forEach(function(item, index) {
blobs.push(blob.slice(index,index + 1));
});

// ajax上传...

2.常用文件类型(MIME Type)

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准,也就是文件的媒体类型。浏览器可以根据它来区分文件,然后决定什么内容用什么形式来显示。

后缀 MIME Type
.html text/html
.txt text/plain
.csv text/plain
.rtf application/rtf
.gif image/gif
.ipeg,.jpg image/jpeg
.au audio/basic
.mid,.midi audio/midi,audio/x-midi
.ra,.ram audio/x-pn-realaudio
.avi video/x-msvideo
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.xls application/vnd.ms-excel
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.ppt application/vnd.ms-powerpoint
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.gz application/x-gzip
.tar application/x-tar

更多类型可见http://tool.oschina.net/commons/https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types

3.实现一个前端CSV文件下载

*CSV文件

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。通常都是纯文本文件。建议使用WORDPAD或是记事本来开启,再则先另存新档后用EXCEL开启,也是方法之一。

简单来说,就是一个数据集文件格式。

arrayToCSV

都知道无论接口返回还是本地数据,js数据格式多为JSON格式,因此JSON转CSV字符串的方法尤为重要。

直接上函数:

1
2
3
4
5
6
7
8
/**
* @function arrayToCSV
* @param {array} arr
* @param {string} delimiter
*/
export function arrayToCSV(arr, delimiter = ',') {
return arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
}

如现在有这样一个数据:

1
2
3
4
5
6
7
8
let data = [
['tit', 'val', 'date'],
['csv', '10', '2019-10-18'],
['jpg', '8', '2019-10-17'],
['docx', '1', '2019-10-16'],
];

arrayToCSV(data); // '"tit","val","date"\n"csv","10","2019-10-18"\n"jpg","8","2019-10-17"\n"docx","1","2019-10-16"'

利用Blob生成文件

1
2
3
4
5
let dataString = arrayToCSV(data);
let blobContent = new Blob(
[dataString],
{type: `text/plain`} // csv的MIME TYPE
);

文件生成后需要出发浏览器的下载事件,此时需要下面URL接口的帮助。

中文的编码

涉及到数据内容中有中文的时候,需要注意中文编码。此时的方案就是设置charset以及添加标识前缀。如

1
2
3
4
let blobContent = new Blob(
['ufeff' + dataString], // 添加前缀
{type: 'text/plain,charset=utf-8'} // utf-8
);

URL接口对象

URL 接口是一个包含若干静态方法的对象,用来创建 URLs。(当使用一个没有实现该构造器的用户代理时,可以通过 Window.URL 属性来访问该对象(基于 Webkit 和 Blink 内核的浏览器均可用 Window.webkitURL 代替)。)

URL.createObjectURL()

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。

语法:

1
objectURL = window.URL.createObjectURL(object);

URL.revokeObjectURL()

URL.revokeObjectURL() 静态方法用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象。当你结束使用某个 URL 对象之后,应该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。

你可以在 sourceopen 被处理之后的任何时候调用 revokeObjectURL()。这是因为 createObjectURL() 仅仅意味着将一个媒体元素的 src 属性关联到一个 MediaSource 对象上去。调用revokeObjectURL() 使这个潜在的对象回到原来的地方,允许平台在合适的时机进行垃圾收集。

语法:

1
window.URL.revokeObjectURL(objectURL);

使用URL接口对象和a标签实现下载

1
2
3
4
5
6
7
8
9
10
11
12
let blobUrl = window.URL.createObjectURL(blobContent);	// 之前生成的字符串
let eleLink = document.createElement('a');

if (!('download' in eleLink)) return false; // 是否支持a标签下载

eleLink.download = `test.csv`; // 文件名
eleLink.style.display = 'none';
eleLink.href = blobUrl;

document.body.appendChild(eleLink);
eleLink.click(); // 模拟点击事件
document.body.removeChild(eleLink)

4.实现一个图片压缩并下载

图片压缩

借助于canvas。方案有两种:一种是缩小图片尺寸,一种是弱化图片色值精准度。直接上函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* @function compressImg
* @param {image object} img
* @param {number} rate
* @return {string} image base64
*/
export function compressImg(img, rate = 0.9) {
let canvas = document.createElement("canvas"),
ctx = canvas.getContext('2d');

let tCanvas = document.createElement("canvas"),
tctx = tCanvas.getContext("2d");

let width = img.width;
let height = img.height;

// 如果图片过大,缩小
let ratio;
if ((width > 700 || height > 700) && (ratio = width * height / 500000) > 1) {
ratio = Math.sqrt(ratio);
width /= ratio;
height /= ratio;
} else {
ratio = 1;
}

let count;

canvas.width = width;
canvas.height = height;

ctx.fillStyle = "#fff"; // 铺底色
ctx.fillRect(0, 0, width, height);
if ((count = width * height / 600000) > 1) {
count = ~~(Math.sqrt(count) + 1); // 分成多少块瓦片
// 每块瓦片的宽和高
let nw = ~~(width / count);
let nh = ~~(height / count);
tCanvas.width = nw;
tCanvas.height = nh;
for (var i = 0; i < count; i++) {
for (var j = 0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
ctx.drawImage(tCanvas, i * nw, j * nh);
}
}
} else {
ctx.drawImage(img, 0, 0, width, height);
}

let ndata = canvas.toDataURL('image/jpeg', rate);

tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
return ndata;
}

比如这么一个图片链接:http://blog.michealwayne.cn/images/icons/js.png,调用如下

1
2
3
4
5
let _img = new Image();
_img.onload = () => {
let base64 = compressImg(_img, 0.8); // 压缩后的图片base64
}
_img.src = 'http://blog.michealwayne.cn/images/icons/js.png'

如果是input上传的图片文件,需要先用URL.createObjectURL()转为url,再转为base64。

base64转为图片blob

需要使用window.atob和Uint8Array()函数,可见《浏览器中的base64编码解码》

1
2
3
4
5
6
7
8
let text = window.atob(base64.split(',')[1]);
let buffer = new Uint8Array(text.length);

for (let i = 0; i < text.length; i++) {
buffer[i] = text.charCodeAt(i);
}

let blobContent = new Blob([buffer], 'image/jpeg');

实现下载

1
2
3
4
5
6
7
8
9
10
11
12
let blobUrl = window.URL.createObjectURL(blobContent);	// 之前生成的字符串
let eleLink = document.createElement('a');

if (!('download' in eleLink)) return false;

eleLink.download = `test.jpg`; // 文件名
eleLink.style.display = 'none';
eleLink.href = blobUrl;

document.body.appendChild(eleLink);
eleLink.click(); // 模拟点击事件
document.body.removeChild(eleLink)

搞定收工。


相关链接