压缩包怎么下载

本篇文章主要介绍使用exceljs、file-saver、jszip实现下载包含多层级文件夹、多个excel、每个excel支持多个sheet的zip压缩包。上一篇文章:前端复杂表格导出excel,一

本篇文章主要介绍使用 exceljs、file-saver、jszip实现下载包含多层级文件夹、多个 excel、每个 excel 支持多个 sheet 的 zip 压缩包。上一篇文章:前端复杂表格导出excel,一键导出 Antd Table 看这篇就够了(附源码)[1]详细介绍了如何实现解析 Antd Table、组装数据和调整表格的样式,感兴趣的可以先看看。本篇将接着上一篇,重点讲方法的更高级抽象,和下载多层级文件夹的 zip 压缩包。源码地址:github.com/cachecats/e…[2]

实现效果

最终下载的是 压缩包.zip,解压之后包含多个文件夹,每个文件夹下又可以无限嵌套子文件夹,excel 文件可以自由选择放到根目录下,或者子文件夹下。实现效果如图:

使用方法

使用方式也很简单,经过高度封装后,只需按照方法参数的规则传入参数即可:

downloadFiles2ZipWithFolder({      zipName: &39;,      folders: [        {          folderName: &39;,          files: [            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },          ]        },        {          folderName: &39;,          files: [            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },          ]        },        {          folderName: &39;,          files: [            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },          ]        },        {          folderName: &39;,          files: [            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },          ]        },        {          folderName: &39;,          files: [            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              },                {                  sheetName: &39;,                  columns: columns,                  dataSource: list                }              ]            },            {              filename: &39;,              sheets: [{                sheetName: &39;,                columns: columns,                dataSource: list              }]            },          ]        }      ]    })复制代码

这里会封装三个方法,分别满足不同场景下的导出需求:

downloadExcel:导出普通的单文件 excel,预设样式,可包含多个 sheet。

downloadFiles2Zip:将多个 excel 文件导出到一个 zip 压缩包内,没有嵌套文件夹。

一、封装普通的下载导出 excel 方法

我们来封装一个常用的,预定义好样式,直接能开箱即用的导出方法,使用者不用关心具体细节,只管简单的调用:

function onExportExcel() {  downloadExcel({    filename: &39;,    sheets: [{      sheetName: &39;,      columns: columns,      dataSource: list    }]  })}复制代码

如上,直接调用 downloadExcel方法,它传入一个对象作为参数,分别有 filename和 sheets两个属性。

filename:文件名。不用带 .xlsx后缀,会自动加后缀名。

sheets:sheet 数组。传入几个 sheet 对象就会创建几个 sheet 页。

Sheet对象的定义:

export interface ISheet {  // sheet 的名字  sheetName: string;  // 这个 sheet 中表格的 column,类型同 antd 的 column  columns: ColumnType<any>[];  // 表格的数据  dataSource: any[];}复制代码

核心代码

downloadExcel方法关键源码:

压缩包怎么下载

export interface IDownloadExcel {  filename: string;  sheets: ISheet[];}export interface ISheet {  // sheet 的名字  sheetName: string;  // 这个 sheet 中表格的 column,类型同 antd 的 column  columns: ColumnType<any>[];  // 表格的数据  dataSource: any[];}/** * 下载导出简单的表格 * @param params */export function downloadExcel(params: IDownloadExcel) {  // 创建工作簿  const workbook = new ExcelJs.Workbook();  params?.sheets?.forEach((sheet) => handleEachSheet(workbook, sheet));  saveWorkbook(workbook, `${params.filename}.xlsx`);}function handleEachSheet(workbook: Workbook, sheet: ISheet) {  // 添加sheet  const worksheet = workbook.addWorksheet(sheet.sheetName);  // 设置 sheet 的默认行高。设置默认行高跟自动撑开单元格冲突  // worksheet.properties.defaultRowHeight = 20;  // 设置列  worksheet.columns = generateHeaders(sheet.columns);  handleHeader(worksheet);  handleData(worksheet, sheet);}export function saveWorkbook(workbook: Workbook, fileName: string) {  // 导出文件  workbook.xlsx.writeBuffer().then((data: any) => {    const blob = new Blob([data], {type: &39;});    saveAs(blob, fileName);  });}复制代码

generateHeaders方法是设置表格的列。handleHeader方法负责处理表头,设置表头的高度、背景色、字体等样式。handleData方法处理每一行具体的数据。这三个方法的实现在上篇文章都有介绍,如需了解更多请查看源码:github.com/cachecats/e…[3]

导出的 excel 效果如下图,列宽会根据传入的 width 动态计算,单元格高度会根据内容自动撑开。

二、导出包含多个 excel 的 zip 压缩包

如果没有多级目录的需求,只想把多个 excel 文件打包到一个压缩包里,可以用 downloadFiles2Zip这个方法,得到的目录结构如下图:

参数结构如下,支持导出多个 excel 文件,每个 excel 文件又可以包含多个 sheet。

使用示例

function onExportZip() {  downloadFiles2Zip({    zipName: &39;,    files: [      {        filename: &39;,        sheets: [          {            sheetName: &39;,            columns: columns,            dataSource: list          },          {            sheetName: &39;,            columns: columns,            dataSource: list          }        ]      },      {        filename: &39;,        sheets: [{          sheetName: &39;,          columns: columns,          dataSource: list        }]      },      {        filename: &39;,        sheets: [{          sheetName: &39;,          columns: columns,          dataSource: list        }]      }    ]  })}复制代码

核心代码

通过 handleEachFile()方法处理每个 fille 对象,每个 file 其实就是一个 excel 文件,即一个 workbook。给每个 excel 创建 workbook并将数据写入,然后通过 JsZip库写入到压缩文件内,最终用 file-saver库提供的 saveAs方法导出压缩文件。注意 12、13行,handleEachFile()方法返回的是一个 Promise,需要等所有异步方法都执行完之后再执行下面的生成 zip 方法,否则可能会遗漏文件。

import {saveAs} from &39;;import * as ExcelJs from &39;;import {Workbook, Worksheet, Row} from &39;;import JsZip from &39;/** * 导出多个文件为zip压缩包 */export async function downloadFiles2Zip(params: IDownloadFiles2Zip) {  const zip = new JsZip();  // 待每个文件都写入完之后再生成 zip 文件  const promises = params?.files?.map(async param => await handleEachFile(param, zip, &39;))  await Promise.all(promises);  zip.generateAsync({type: &34;}).then(blob => {    saveAs(blob, `${params.zipName}.zip`)  })}async function handleEachFile(param: IDownloadExcel, zip: JsZip, folderName: string) {  // 创建工作簿  const workbook = new ExcelJs.Workbook();  param?.sheets?.forEach((sheet) => handleEachSheet(workbook, sheet));  // 生成 blob  const data = await workbook.xlsx.writeBuffer();  const blob = new Blob([data], {type: &39;});  if (folderName) {    zip.folder(folderName)?.file(`${param.filename}.xlsx`, blob)  } else {    // 写入 zip 中一个文件    zip.file(`${param.filename}.xlsx`, blob);  }}function handleEachSheet(workbook: Workbook, sheet: ISheet) {  // 添加sheet  const worksheet = workbook.addWorksheet(sheet.sheetName);  // 设置 sheet 的默认行高。设置默认行高跟自动撑开单元格冲突  // worksheet.properties.defaultRowHeight = 20;  // 设置列  worksheet.columns = generateHeaders(sheet.columns);  handleHeader(worksheet);  handleDataWithRender(worksheet, sheet);}复制代码

render 渲染的单元格处理

数据处理还有一点需要注意,因为有的单元格是通过 render 函数渲染的,render 函数里可能进行了一系列复杂的计算,所以如果 column 中有 render 的话不能直接以 dataIndex 为 key 进行取值,要拿到 render 函数执行后的值才是正确的。比如 Table 的 columns 如下:

const columns: ColumnsType<any> = [    {      width: 50,      dataIndex: &39;,      key: &39;,      title: &39;,      render: (text, row) => <div><p>{row.id + 20}</p></div>,    },    {      width: 100,      dataIndex: &39;,      key: &39;,      title: &39;,    },    {      width: 50,      dataIndex: &39;,      key: &39;,      title: &39;,    },    {      width: 80,      dataIndex: &39;,      key: &39;,      title: &39;,    },  ];复制代码

第一列传入了 render 函数 render: (text,row) => <div><p>{row.id + 20}</p></div>,经过计算后,ID 列显示的值应该是原来的 id + 20。构造的数据原来的 id 是 0-4,页面上显示的应该是 20-24,如下图:

这时导出的 excel 应该跟页面上显示的一模一样,这样才是正确的。点击【导出zip】按钮,解压后打开下载的其中一个 excel,验证显示的内容跟在线表格完全一致。

那么是如何做到的呢?主要看 handleDataWithRender()方法:

** * 如果 column 有 render 函数,则以 render 渲染的结果显示 * @param worksheet * @param sheet */function handleDataWithRender(worksheet: Worksheet, sheet: ISheet) {  const {dataSource, columns} = sheet;  const rowsData = dataSource?.map(data => {    return columns?.map(column => {      // @ts-ignore      const renderResult = column?.render?.(data[column.dataIndex], data);      if (renderResult) {        // 如果不是 object 说明没包裹标签,是基本类型直接返回        if (typeof renderResult !== &34;) {          return renderResult;        }        // 如果是 object 说明包裹了标签,逐级取出值        return getValueFromRender(renderResult);      }      // @ts-ignore      return data[column.dataIndex];    })  })  // 添加行  const rows = worksheet.addRows(rowsData);  // 设置每行的样式  addStyleToData(rows);}// 递归取出 render 里的值// @ts-ignorefunction getValueFromRender(renderResult: any) {  if (renderResult?.type) {    let children = renderResult?.props?.children;    if (children?.type) {      return getValueFromRender(children);    } else {      return children;    }  }  return &39;}复制代码

worksheet.addRows()可以添加数据对象,也可以添加由每行的每列组成的二维数组。由于我们要自己控制每个单元格显示的内容,所以采用第二种方式,传入一个二维数组来构造 row。结构如下图所示:

循环 dataSource和 columns,就得到了每个单元格要显示的内容,通过执行 render 函数,得到 render 执行后的结果:const renderResult = column?.render?.(data[column.dataIndex],data);注意 render 需要传入两个参数,一个是 text,一个是这行的数据对象,我们都能确定参数的值,所以直接传入。然后判断 renderResult的类型,如果是 object 类型,说明是个由 html 标签包裹的 ReactNode,需要递归取出最终渲染的值。如果是非 object 类型,说明是 boolean 或者 string 这样的基本类型,即没有被标签包裹,可以直接展示。由于我们采用了递归来取最后渲染的值,所以无论嵌套了多少层标签,都可以正确的取到值。

三、导出包含多个子文件夹、多个excel文件的 zip 压缩包

如果文件、文件夹嵌套比较深,可以使用 downloadFiles2ZipWithFolder()方法。文件结构如下图:

核心代码

跟上一个方法 downloadFiles2Zip相比,参数的数据结构多了层 folders,其他的逻辑基本没变。所以 downloadFiles2ZipWithFolder方法能实现downloadFiles2Zip方法的所有功能。

使用示例

如文章开头的使用示例,为了方便看清结构,将每个对象的 files 值删除,精简之后得到如下结构:

不管嵌套几层文件夹,folders永远是一个一维数组,每一项里面也不会嵌套 folders。多级目录是通过文件名 folderName实现的。

folderName为空字符串,则将它的 files放入压缩包的顶级目录中,不在任何子文件内。

folderName为普通字符串,如:文件夹1,则以 folderName为文件名新建一个文件夹,并将它的 files放入此文件夹下。

folderName为带斜杠的字符串,如:文件夹2/文件夹2-1/文件夹2-1-1,则按照顺序依次新建 n 个文件夹并保持嵌套关系,最终将它的files放入最后一个文件夹下。

如需查看 demo 完整代码,源码地址:github.com/cachecats/e…[4]

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:cloud.tencent.com/developer/s…[5]

关于本文

上一篇 2023年05月21 04:47
下一篇 2023年03月30 12:37

相关推荐

  • 微信怎么发群消息,微信全发怎么发给所有人

    微信全发怎么发给所有人,微信大家都在用吧!现在这个社会,微信已经成为人与人之间沟通的“桥梁”,可谓是社交软件中的“大明星”,微信群发消息怎么发?大家都知道,微信发消息给别人非常容易,但是如果想要一条消

    2023年05月31 278
  • 华硕飞行堡垒怎么样,飞行堡垒7真的那么不堪吗

    阿橙的第6期推送槽序最近广东一直持续梅雨天气,很潮湿,衣服/袜子都很难干,大家有没有什么好的除湿办法呀?槽品1、屏幕素质相对较弱,不太适合专业设计和后期用户选择。2、显卡性能较弱,使用的是核显,只能支

    2023年05月23 276
  • 怎样设置分屏,vivo手机怎么分屏所有应用

    电脑分屏是一直是用户比较关注的实用方法,比如有时我们一边看视频、一边查找资料,又或者一边上网课一边记笔记等等;电脑分屏不仅十分方便,vivo手机怎么分屏所有应用,而且可以避免频繁切换窗口的麻烦。说到这

    2022年12月31 262
  • 微信红包最多可以发多少,微信一次发500元红包怎么发

    没两天就要过年了,微信一次发500元红包怎么发,过年的时候,大家也会用微信来收发红包,活跃气氛,图个喜庆,但是,用微信红包和微信转账,还是有区别的,学会之后,叮嘱家人不要再用错了。首先,我们来了解一下

    2023年04月04 204
  • 电量显示怎么设置,桌面电池电量如何显示

    如果单纯的显示电量,有点过于简单了。华为的EMUI9.0在电量显示方面,确实有用心,它不仅仅提供了多种电量显示的方法,桌面电池电量如何显示,而且还对于电量百分比进行了设置。今天,我们一起来看一看,华为

    2023年05月24 213
  • 怎样打开qq邮箱,怎样打开qq邮箱里的邮件

    QQ邮箱2021最新版本是一起腾讯旗下的非常好用的世界邮箱软件,相信有很多小伙伴都有用过这个邮箱。那么它有什么样的功能,怎样打开qq邮箱里的邮件,让大家都会去使用呢?下面随小编一起看看吧。该软件是一款

    2022年12月27 255
  • qq怎么接收文件夹,为什么qq不能接收文件夹

    最近很多的小伙伴都在问小编,QQ通过离线传输的文件找不到了,是不是也会过期?是不是会接收不到?首先它是肯定会过期的,为什么qq不能接收文件夹,那怎么找到离线接收的文件?在小伙伴接收时可自行选择保存的路

    2023年05月24 214
  • oppo客服电话多少,oppo手机客服电话24小时

    oppo手机客服电话24小时,本人女,结婚后一直在家带孩子,今年孩子9岁了,上三年级。因为是独生子女,孩子有时候会比较无聊,就拿我手机玩一会儿!起初也没有太在意,一直限定一个时间段。OPPO客服告诉我

    2023年04月09 230
  • 为什么微信不能绑定qq号,该qq号暂时无法绑定微信

    我们都知道,腾讯除了游戏业务,旗下两大社交产品,都几乎会出现在每个人的手机上,那就是QQ跟微信。QQ一开始就是基于PC的即时聊天软件,该qq号暂时无法绑定微信,而微信完完全全是基于移动平台而生的。QQ

    2023年04月24 216
  • 水滴筹怎么捐款,水滴筹怎么给指定人捐款

    无论是否决定要发起筹款,多数患者都应该提前了解一下平台的筹款信息,需要筹款的患者能够更加放心发起,胸有成竹才能在后续的筹款及取款中一路绿灯,而对于暂时没有筹款需求的普通家庭来说,也可以先了解一下,明天

    2023年05月13 282
  • mac怎么大写,mac

    苹果电脑在一些文字编辑软件中输入英文的时候,Mac会自动识别进行首字母大写,但是如果是写一些脚本之类的话就会非常麻烦,所以要禁用这个功能。那我们该如何禁用首字母自动大写呢?需要的朋友快和小编一起来看看

    2023年05月19 248
  • 苹果平板怎样截屏,苹果平板截屏的三种方法

    “买前生产力,买后爱奇艺”,这是购买iPad用户对其的总结。而其实,iPad是被苹果当成生产力工具来打造的,其中有许多提高效率的操作技巧,就如常用到的截屏,iPad就有高达7种方法!而其实,iPad是

    2022年12月28 286
  • 为什么打电话呼叫失败,电话拨不出去

    手机有信号打不出去电话,这种奇葩的问题,你有遇到过吗?苹果手机的信号一直被网友吐槽,信号弱,电话拨不出去,无信号,但这次是有信号还不能打电话,是什么情况?你有没有考虑过造成这种故障的原因有哪些?下面我

    2023年04月14 237
关注微信