SheetJS 教程

SheetJS 数据导出

🎉摘要:本教程详细讲解使用SheetJS库将JSON数据导出为Excel文件的全过程。通过美国总统姓名和生日数据示例,涵盖数据获取、筛选、排序、工作簿创建、格式优化及文件导出等步骤,提供完整可运行的JavaScript代码,帮助开发者快速实现前端数据导出功能。

许多现代数据源都提供了应用程序接口(API),用于下载 JSON 格式的数据。但许多用户更喜欢使用电子表格软件。SheetJS 库通过将便于程序员使用的 JSON 转换为便于用户使用的工作簿,帮助弥合了这一差距。

本示例的目标是生成一个包含美国总统姓名和出生日期的XLSX工作簿。我们将使用标准的JavaScript函数下载并处理一个JSON数据集。一旦我们得到一个简单的姓名和出生日期列表,我们将使用SheetJS API函数来构建工作簿对象并导出为XLSX格式。

以下序列图展示了该流程:

SheetJS 数据导出

获取数据

原始数据以JSON格式提供,下载地址:https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_executive.json

原始数据

使用 fetch 获取数据很简单,代码如下:

fetch("https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_executive.json").then(resp => {
    // 检查请求是否成功(状态码 200-299)
    if (!resp.ok) {
        throw new Error(`请求失败:状态码 ${resp.status}`);
    }
    // 解析响应体为完整的 JSON 数据
    return resp.json();
}).then(data => {
    // data 是获取的数据,后续业务在这里进行
}).catch(err => {
    // 拉取数据失败
    console.log(err);
});

注意,原始数据是一个对象数组。下面是“John Adams”的相关数据,格式如下:

[
  {
    "name": {
      "first": "John",          // <-- 名字(名)
      "last": "Adams"           // <-- 姓氏
    },
    "bio": {
      "birthday": "1735-10-19", // <-- 生日
    },
    "terms": [                  // <-- 总统任期数组
      { "type": "viceprez", "start": "1789-04-21", }, // viceprez 副总统
      { "type": "viceprez", "start": "1793-03-04", },
      { "type": "prez",     "start": "1797-03-04", } // <-- 总统任期,prez 总统
    ]
  },...
]

筛选总统

该数据集包含副总统,从未担任过总统的数据,我们需要筛选出担任过总统的数据。

每个对象的 terms 字段是一个任期数组。如果 type 属性为 "prez",则该任期为总统任期。我们关注的是至少担任过一个任期的总统。下面这行代码创建了一个总统数组:

// 筛选总统:从原始数据中过滤出有总统任期的条目
const prez = data.filter(row => {
    // 检查当前人物的所有任期(terms)中,是否存在总统类型的任期
    // some()方法:只要有一个任期满足条件,就返回true,当前人物会被保留
    return row.terms.some(term => {
        // 任期类型(type)为 "prez" 表示该任期是总统任期
        return term.type === "prez";
    });
});

按首个任期排序

该数据集按首次担任总统或副总统的任期以时间顺序排序。在特定任期内,副总统和总统按字母顺序排序。

巴拉克·奥巴马于 2009 年当选总统,约瑟夫·拜登当选副总统。由于按字母顺序“拜登”排在“奥巴马”之前,所以拜登的数据会先出现。

目标是按照总统首次任期的顺序对他们进行排序。

第一步是将第一个总统任期的开始日期添加到数据集中。下面的代码会查看每位总统,并创建一个 start 属性,该属性代表第一个总统任期的开始。

prez.forEach(row => {
  // find()方法:返回第一个满足条件的任期对象(总统通常只有连续的总统任期,取第一个即可)
  row.start = row.terms.find(term => term.type === "prez").start
});

此时,prez 数组中的每一行都有一个 start 属性。由于这些 start 属性都是字符串,因此下面这行代码对数组进行排序:

prez.sort((l,r) => l.start.localeCompare(r.start));

重塑数组

在这个示例中,姓名将是名和姓的组合(row.name.first + " " + row.name.last),而生日可以在row.bio.birthday 处获取。使用 Array#map,可以通过一次调用对数据集进行处理:

const rows = prez.map(row => ({
  name: row.name.first + " " + row.name.last,
  birthday: row.bio.birthday
}));

结果是一个由“简单”对象组成的数组,这些对象没有嵌套:

[
  { name: "George Washington", birthday: "1732-02-22" },
  { name: "John Adams", birthday: "1735-10-19" },
  // ... one row per President
]

创建一个工作簿

json_to_sheet() 方法可以从清理后的数据集生成一个 SheetJS 工作表:

const worksheet = XLSX.utils.json_to_sheet(rows);

XLSX.utils.book_new() 方法会创建一个新的工作簿,而 XLSX.utils.book_append_sheet() 方法会将一个工作表附加到该工作簿中。这个新工作表将被命名为 “Dates”:

// 创建一个新的工作簿
const workbook = XLSX.utils.book_new();
// 将工作表添加到工作簿,名称为 “Dates”
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

清理工作簿

数据已经在工作簿中,并且可以导出。如下图:

SheetJS 数据导出

上述表格有多个改进项,如可以重命名标题,也可以调整列宽。

更改标题名称

默认情况下,json_to_sheet() 会创建一个带有标题行的工作表。在这种情况下,标题来自 JS 对象的键:“name” 和 “birthday”,标题会被写入第一行。

XLSX.utils.sheet_add_aoa() 方法可以覆盖工作表中的数据,下面这行代码会将 A1设置为 “Name”,并将 B1设置为 “Birthday”:

XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });

上述代码中,“origin”用来配置写入位置,指定数据写入的起始单元格。其中,"A1" 表示从 A1 单元格开始写入(也可传数字索引,如 0 代表 A1,{r:0, c:0} 也等价于 A1)。

更改列宽

有些名称比默认列宽长,列宽存储在 "!cols" 工作表属性中。

以下代码行将 A 列的宽度设置为大约 10 个字符:

worksheet["!cols"] = [ { wch: 10 } ]; // 将A列宽度设置为10个字符

对 rows 执行一次 Array#reduce 调用可以计算出最大宽度:

// 计算姓名列的最大宽度:基于所有总统姓名的字符长度,取最大值(最小宽度保底为10)
// rows:包含总统数据的数组;r:遍历的每个总统对象;r.name:总统姓名
const max_width = rows.reduce((w, r) => {
    // 比较当前累计的最大宽度(w)和当前总统姓名的长度,保留较大值
    return Math.max(w, r.name.length);
}, 10); // 初始值10:确保列宽至少为10,避免姓名过短时列宽过小

// 设置列宽为姓名列的最大宽度
worksheet["!cols"] = [ { wch: max_width } ];

清理后,生成的工作簿如下方截图所示:

SheetJS 数据导出

导出文件

XLSX.writeFile() 方法会创建一个电子表格文件并尝试将其写入系统。在浏览器中,它会尝试提示用户下载该文件。在 NodeJS 中,它会写入本地目录:

XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });

其中,compression 表示是否开启压缩,默认不开启:

  • true:开启 ZIP 压缩(.xlsx 本质是压缩的 XML 包),生成的文件体积更小;

  • false:关闭压缩,文件体积更大,但写入 / 读取速度略快。

XLSX、XLSB、NUMBERS 和 ODS 文件使用 ZIP 容器。ZIP 容器格式支持不同级别的压缩。电子表格软件在导出文件时通常会使用压缩功能。

默认情况下,SheetJS 写入器会优先考虑速度。导出速度更快,但生成的文件更大。

如果设置了compression选项,写入器会优先考虑文件大小。导出的文件更小,但会花费更多时间。

完整示例

示例:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SheetJS 教程</title>
    <!-- 引入 SheetJS 库 -->
    <script src="https://www.hxstrive.com/cdn/libs/SheetJS/0.20.3/xlsx.full.min.js"></script>
</head>
<body>

<p>
    <button type="button" onclick="exportData()">导出数据</button>
</p>

<script type="text/javascript">
    // 示例数据的格式:
    // [
    //     {
    //         "id": {
    //             "bioguide": "W000178",
    //             "govtrack": 411351,
    //             "icpsr_prez": 99869
    //         },
    //         "name": {
    //             "first": "George",
    //             "last": "Washington"
    //         },
    //         "bio": {
    //             "birthday": "1732-02-22",
    //             "gender": "M"
    //         },
    //         "terms": [
    //             {
    //                 "type": "prez",
    //                 "start": "1789-04-30",
    //                 "end": "1793-03-04",
    //                 "party": "no party",
    //                 "how": "election"
    //             },
    //             {
    //                 "type": "prez",
    //                 "start": "1793-03-04",
    //                 "end": "1797-03-04",
    //                 "party": "no party",
    //                 "how": "election"
    //             }
    //         ]
    //     },...
    // ]
    function exportData() {
        // 获取 JSON 数据并解析
        fetch("https://www.hxstrive.com/cdn/resources/sheetjs/sheetjs_executive.json").then(resp => {
            // 检查请求是否成功(状态码 200-299)
            if (!resp.ok) {
                throw new Error(`请求失败:状态码 ${resp.status}`);
            }
            // 解析响应体为完整的 JSON 数据
            return resp.json();
        }).then(data => {
            // 筛选总统
            const prez = data.filter(row => {
                // 检查是否有总统任期
                return row.terms.some(term => {
                    return term.type === "prez";
                });
            });

            // 按首次总统任期排序
            prez.forEach(row => {
                row.start = row.terms.find(term => term.type === "prez").start
            });
            prez.sort((l,r) => l.start.localeCompare(r.start));

            // 扁平化对象
            const rows = prez.map(row => ({
                name: row.name.first + " " + row.name.last,
                birthday: row.bio.birthday
            }));

            // 生成工作表和工作簿
            const worksheet = XLSX.utils.json_to_sheet(rows);
            const workbook = XLSX.utils.book_new();
            XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");

            // 修复标题
            XLSX.utils.sheet_add_aoa(worksheet, [["姓名", "生日"]], { origin: "A1" });

            // 计算列宽
            const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
            worksheet["!cols"] = [ { wch: max_width } ];

            // 创建一个XLSX文件并尝试保存为Presidents.xlsx
            XLSX.writeFile(workbook, "Presidents.xlsx", { compression: true });
        }).catch(err => {
            // 拉取数据失败
            console.log(err);
        });
    }

</script>
</body>
</html>

看看效果👉自己动手试一试 »

点击“导出数据”按钮,导出表格如下:

SheetJS 数据导出

参考资料:https://docs.sheetjs.com/docs/getting-started/examples/export/ 

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号