likes
comments
collection
share

前端实现下载CSV文件

作者站长头像
站长
· 阅读数 12

背景

前端导出文件大部分还是通过服务器端的方式生成文件,然后传递到客户端。但很多情况下当我们导出CSV时并不需要后端参与,甚至没有后端。

csv文件介绍

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

CSV文件的格式不存在通用标准,也没有指定所使用的字符编码,但是在RFC 4180中有基础性的描述, 而7-bit ASCII是最基本的通用编码。

csv文件构成

每个CSV文件都由以下组成部分:

  • 头部:包含文件的元数据,例如文件名、创建日期等。
  • 行:每行代表一个数据记录。
  • 列:每行中的数据记录被分成多个列,由逗号分隔。
  • 分隔符:常用逗号作为数据字段的分隔符,但也可以使用其他分隔符,例如分号或制表符。
  • 处理引号:为了防止分隔符与数据字段中的逗号冲突,可以用引号将数据字段括起来。

csv文件特点

  • 用逗号作为表格列分隔符,当然也可以用其他分隔符,不过为了通用性,推荐用逗号
  • 纯文本格式,读取、写入都很简单
  • 每一行文本就是表格的一行数据
  • 几乎能被所有的表格应用支持,excel 和 wps 更是老早就支持导入 csv 文件

如何定义csv文件

虽然 CSV 格式有各种规范和实现,但目前还没有正式的规范,允许对 CSV 文件进行多种解释。本节记录了大多数实现似乎都遵循的格式:

  1. 每条记录位于单独一行,以换行符(CRLF)分隔。例如:
aaa,bbb,ccc CRLF
zzz,yyy,xxx CRLF
  1. 文件中的最后一条记录可能有换行符,也可能没有。例如:
aaa,bbb,ccc CRLF
zzz,yyy,xxx
  1. 文件的第一行可能会出现一个可选的标题行,格式与正常记录行相同。标题行将包含与文件字段相对应的名称,字段数量应与文件其余部分的记录数量相同(标题行的有无应通过该 MIME 类型的可选 "标题 "参数来表示)。例如:
field_name,field_name,field_name CRLF
aaa,bbb,ccc CRLF
zzz,yyy,xxx CRLF
  1. 在文件头和每条记录中,可以有一个或多个字段,字段之间用逗号隔开。在整个文件中,每行应包含相同数量的字段。空格被视为字段的一部分,不应忽略。记录中的最后一个字段后面不能有逗号。例如:
aaa,bbb,ccc
  1. 每个字段可以用双引号括起来,也可以不用(但有些程序,如 Microsoft Excel,根本不使用双引号)。如果字段没有用双引号括起来,则字段内可能不会出现双引号。例如:
"aaa","bbb","ccc" CRLF
zzz,yyy,xxx
  1. 包含换行符 (CRLF)、双引号和逗号的字段应用双引号括起来。例如:
"aaa","b CRLF
bb","ccc" CRLF
zzz,yyy,xxx
  1. 如果使用双引号来括弧字段,那么出现在字段内的双引号必须用另一个双引号来转义。例如:
"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

  1. 如果字段中含有希伯来文、法语、德语等文字('éà; ç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;',
});
  1. 字段值中含有特殊符号影响csv文件的正确解读,如:“,”,"\n"。

解决方法:将含有特殊符号的字段用双引号包装起来,如:a,b => "a,b"

const textField = '"';
if (value && /[,\r\n]/g.test(value)) {
    value = textField + value + textField;
}
  1. 如果字段值中含有‘ " ’这个符号,经过上方代码处理反而会出现问题: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);  
},