前端实现下载CSV文件
背景
前端导出文件大部分还是通过服务器端的方式生成文件,然后传递到客户端。但很多情况下当我们导出CSV时并不需要后端参与,甚至没有后端。
csv文件介绍
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。
CSV文件的格式不存在通用标准,也没有指定所使用的字符编码,但是在RFC 4180中有基础性的描述, 而7-bit ASCII是最基本的通用编码。
csv文件构成
每个CSV文件都由以下组成部分:
- 头部:包含文件的元数据,例如文件名、创建日期等。
- 行:每行代表一个数据记录。
- 列:每行中的数据记录被分成多个列,由逗号分隔。
- 分隔符:常用逗号作为数据字段的分隔符,但也可以使用其他分隔符,例如分号或制表符。
- 处理引号:为了防止分隔符与数据字段中的逗号冲突,可以用引号将数据字段括起来。
csv文件特点
- 用逗号作为表格列分隔符,当然也可以用其他分隔符,不过为了通用性,推荐用逗号
- 纯文本格式,读取、写入都很简单
- 每一行文本就是表格的一行数据
- 几乎能被所有的表格应用支持,excel 和 wps 更是老早就支持导入 csv 文件
如何定义csv文件
虽然 CSV 格式有各种规范和实现,但目前还没有正式的规范,允许对 CSV 文件进行多种解释。本节记录了大多数实现似乎都遵循的格式:
- 每条记录位于单独一行,以换行符(CRLF)分隔。例如:
aaa,bbb,ccc CRLF
zzz,yyy,xxx CRLF
- 文件中的最后一条记录可能有换行符,也可能没有。例如:
aaa,bbb,ccc CRLF
zzz,yyy,xxx
- 文件的第一行可能会出现一个可选的标题行,格式与正常记录行相同。标题行将包含与文件字段相对应的名称,字段数量应与文件其余部分的记录数量相同(标题行的有无应通过该 MIME 类型的可选 "标题 "参数来表示)。例如:
field_name,field_name,field_name CRLF
aaa,bbb,ccc CRLF
zzz,yyy,xxx CRLF
- 在文件头和每条记录中,可以有一个或多个字段,字段之间用逗号隔开。在整个文件中,每行应包含相同数量的字段。空格被视为字段的一部分,不应忽略。记录中的最后一个字段后面不能有逗号。例如:
aaa,bbb,ccc
- 每个字段可以用双引号括起来,也可以不用(但有些程序,如 Microsoft Excel,根本不使用双引号)。如果字段没有用双引号括起来,则字段内可能不会出现双引号。例如:
"aaa","bbb","ccc" CRLF
zzz,yyy,xxx
- 包含换行符 (CRLF)、双引号和逗号的字段应用双引号括起来。例如:
"aaa","b CRLF
bb","ccc" CRLF
zzz,yyy,xxx
- 如果使用双引号来括弧字段,那么出现在字段内的双引号必须用另一个双引号来转义。例如:
"aaa","b""bb","ccc"
javascript写入csv文件
const csvData = `
aaa,bbb,ccc \r\n
zzz,yyy,xxx \r\n
`
const blobData = new Blob([csvData], {
type: 'text/csv;charset=utf-8;',
});
边缘case
- 如果字段中含有希伯来文、法语、德语等文字(
'éà; ça; 12\nà@€; çï; 13'
),导出的csv文件在Excel中打开后,这些文字呈现出乱码。
解决方法:严格来说这并不是csv文件的问题,而是Excel处理文件编码方式问题,Excel默认并不是以UTF-8来打开文件,所以在csv开头加入BOM,告诉Excel文件使用utf-8的编码方式。
const BOM = "\uFEFF";
const csvData = `
aaa,bbb,ccc \r\n
zzz,yyy,xxx \r\n
`
const blobData = new Blob([BOM + csvData], {
type: 'text/csv;charset=utf-8;',
});
- 字段值中含有特殊符号影响csv文件的正确解读,如:“,”,"\n"。
解决方法:将含有特殊符号的字段用双引号包装起来,如:a,b => "a,b"
const textField = '"';
if (value && /[,\r\n]/g.test(value)) {
value = textField + value + textField;
}
- 如果字段值中含有‘ " ’这个符号,经过上方代码处理反而会出现问题:a"b => "a"b"。显然是语法错误。
解决方法:是将"换成"",a"b => "a""b"
const textField = '"';
if (value && /[",\r\n]/g.test(value)) {
value = textField + value.replace(/(")/g, '""') + textField;
}
下载csv文件
const handleDownload = () => {
const currentChartData = downloadData[scope];
const csvData = handleFormatCSV(currentChartData);
if (!csvData) {
Message.error('保存文件失败');
return;
}
const blobData = new Blob([csvData], {
type: 'text/csv;charset=utf-8;',
});
const url = URL.createObjectURL(blobData);
const link = document.createElement('a');
link.setAttribute('href', url);
const startTime = userChooseTime?.[0] ?? dayjs();
const endTime = userChooseTime?.[0] ?? dayjs();
link.setAttribute(
'download',
`${filename}${formatDate(startTime)}-${formatDate(endTime)}`,
);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
},
转载自:https://juejin.cn/post/7259647015604011045