用exceljs和file-saver插件实现纯前端表格导出Excel(支持样式配置,多级表头)

news2024/9/20 1:02:47

exceljs在Jquery(HTML)和vue项目中实现导出功能

  • 前言
  • Jquery(HTML)中实现导出
      • 第一步,先在项目本地中导入exceljs和file-saver包
      • 第二步,封装导出Excel方法(可直接复制粘贴使用)
      • 第三步,在项目中使用
  • vue中实现导出
      • 第一步,还是先安装,可以使用npm,yarn,pnpm,都可以
      • 第二步,在assets/index.js中暴露出一个exportDataToExcel方法
      • 第三步,项目中的使用

前言

最近,做公司一个老项目(jquery),有个需求是将表格中的json数据,导出为Excel文件,表格表头还是动态多层级,导出的excel样式和项目中表格一致;

老项目中之前用的都是xlsx插件,我查询了一下文章,也看到过用xlsx实现多层表头下载,但是感觉比较麻烦,看到推荐使用exceljs插件比较方便;就了解一下,用在项目中得以实现;

话不多说,上案例图:
在这里插入图片描述
导出excel效果:
在这里插入图片描述

这个案例我主要演示的是两层分组表头和样式配置的代码;如果要想仔细研究该插件,可以阅读 Exceljs官方文档;

怎么使用,下面记录非常详情,每一步代码还有注释解析,方便自己和大家理解

Jquery(HTML)中实现导出

第一步,先在项目本地中导入exceljs和file-saver包

在这里插入图片描述
在这里插入图片描述
以上插件包,也可以 npm install exceljs ,npm install file-saver获取,执行命令后项目中会多一个node_modules包,然后找到这两个文件复制出来放到项目中,然后删除无用的node_modules,和pakege.json文件;

第二步,封装导出Excel方法(可直接复制粘贴使用)

在项目中创建assets文件,新建一个exportDataToExcel.js文件

   /**
     * 导出数据到Excel方法
     * @param {Array[Object]} config.data 表格数据
     * @param {Array[String]} config.fields 字段列表
     * @param {Array[String]} config.headers excel表头列表[[]],可以是多级表头[['A1','B1'],['A2','B2']]
     * @param {Array[Object]} config.merges 需要合并的单元格,需要考虑表头的行数[{row:1, col:1, rowspan: 1, colspan: 2}]
     * @param {Array[Object]} config.attrs 单元格样式配置
     * @param {Array[Object]} config.views 工作表视图配置
     * @param {Array[Number]} columnsWidth 每个字段列对应的宽度
     * @param {Object} config.protect 工作表保护【此配置会保护全表,一般推荐只针对单元格进行保护配置】
     * @param {String} sheetName 工作表名称,默认从sheet1开始
     * @param {String} fileName excel文件名称
     */
 function exportDataToExcel(config, fileName) {
       
    if (!config) return;
    const options = {
        fileName: fileName || `导出excel文件【${Date.now()}】.xlsx`,
        worksheets: []
    }
    if(!Array.isArray(config)) {
        config = [config]
    }
    config.forEach((item) => {
        // 深拷贝data【JSON.stringify有缺陷,可自行换成_.cloneDeep】
        const data = JSON.parse(JSON.stringify(item.data));
       
        const results = data.map(obj => {
           return item.fields.map(key => {
                return obj[key]
            })
        })
        // 生成完整excel数据
        let excelData = [];
        excelData = excelData.concat(item.headers).concat(results);
        // 单元格合并处理【excel数据的第一行/列是从1开始】
        let excelMerges = [];
        excelMerges = item.merges.map(m => {
            return [m.row + 1, m.col + 1, m.row + m.rowspan, m.col + m.colspan]
        })
        // 单元格配置处理 excel数据的第一行/列是从1开始】
        let excelAttrs = [];
        excelAttrs = item.attrs.map(attr => {
            attr.rowStart += 1;
            attr.rowEnd += 1;
            attr.colStart += 1;
            attr.colEnd += 1;
            return attr
        })
        options.worksheets.push({
            data: excelData,
            merges: excelMerges,
            attrs: excelAttrs,
            views: item.views,
            columnsWidth: item.columnsWidth,
            protect: item.protect,
            sheetName: item.sheetName
        })
    })
    createExcel(options)
  }

   // 创建Excel文件方法
   async  function createExcel(options) {
       if (!options.worksheets.length) return;
        // 创建工作簿
        const workbook = new ExcelJS.Workbook();
        for (let i = 0; i < options.worksheets.length; i++) {
            const sheetOption = options.worksheets[i];
            // 创建工作表
            const sheet = workbook.addWorksheet(sheetOption.sheetName || 'sheet' + (i + 1));
            // 添加数据行
            sheet.addRows(sheetOption.data);
            // 配置视图
            sheet.views = sheetOption.views;
            // 单元格合并处理【开始行,开始列,结束行,结束列】
            if (sheetOption.merges){
            sheetOption.merges.forEach((item) => {
                sheet.mergeCells(item) 
            });
            }
            // 工作表保 
            if (sheetOption.protect) {
            const res = await sheet.protect(sheetOption.protect.password, sheetOption.protect.options);
            }
            // 单元格样式处理
            if (sheetOption.attrs.length) {
            sheetOption.attrs.forEach((item) => {
                const attr = item.attr || {};
                // 获取开始行-结束行; 开始列-结束列
                const rowStart = item.rowStart;
                const rowEnd = item.rowEnd;
                const colStart = item.colStart;
                const colEnd = item.colEnd;
            if (rowStart) { // 设置行
            for (let r = rowStart; r <= rowEnd; r++) {
                    // 获取当前行
                    const row = sheet.getRow(r);
                    if (colStart) { // 列设置
                    for (let c = colStart; c <= colEnd; c++) {
                        // 获取当前单元格
                        const cell = row.getCell(c);
                        Object.keys(attr).forEach((key) => {
                        // 给当前单元格设置定义的样式
                        cell[key] = attr[key];
                        });
                    }
                    } else {
                    // 未设置列,整行设置【大纲级别】
                    Object.keys(attr).forEach((key) => {
                        row[key] = attr[key];
                    });
                    }
                }
                } else if (colStart) { // 未设置行,只设置了列
                for (let c = colStart; c <= colEnd; c++) {
                    // 获取当前列,整列设置【大纲级别】
                    const column = sheet.getColumn(c);
                    Object.keys(attr).forEach((key) => {
                    column[key] = attr[key];
                    });
                }
                } else {
                // 没有设置具体的行列,则为整表设置
                Object.keys(attr).forEach((key) => {
                    sheet[key] = attr[key];
                });
                }
            })
        }
        // 列宽设置
        if (sheetOption.columnsWidth) {
            for (let i = 0; i < sheet.columns.length; i++) {
                sheet.columns[i].width = sheetOption.columnsWidth[i]
            }
        }
    }
    
    // 生成excel文件
    workbook.xlsx.writeBuffer().then(buffer => {
        // application/octet-stream 二进制数据
        saveAs(new Blob([buffer], { type: 'application/octet-stream' }), options.fileName)
    })
} 

第三步,在项目中使用

在HTML中使用,不能使用模块导入,只能通过< script >标签引入;

<!DOCTYPE html>
<html lang="en">
<head>
    //引入我们放入项目的文件的的压缩js文件
    <script src="/static/exceljs/dist/exceljs.min.js"></script>
    <script src="/static/file-saver/dist/FileSaver.min.js"></script>
    //引入封装导出Excel方法
    <script src="/static/assets/exportDataToExcel.js"></script>
</head>
<body>
<script>
//表格数据
 var exportTableData = [
    {name:'张三',sexy:'男',age:22,hobby:'篮球',provices:'河南省',city:'郑州',status:'未婚'},
    {name:'李四',sexy:'女',age:23,hobby:'排球',provices:'北京市',city:'北京市',status:'未婚'},
    {name:'王二',sexy:'男',age:28,hobby:'足球',provices:'山东省',city:'青岛',status:'已婚'},
  ]
  
  //点击导出按钮事件
 function onExport() {
      let config = exportConfig();
      exportDataToExcel(config, "人员信息表.xlsx");
  }
  
  //导出表格配置
 function  exportConfig(){
      //配置表头header1为一级表头,header2为二级表头,被合并的单元格为空写占位符"":
      //这是实现导出多级表头的关键两点的中的第一点,分几层表头写几个header;
      const header1 = ["姓名", "个人信息","","","居住城市","","婚姻状况"];
      const header2 = ["","性别","年龄","爱好","省份","城市",""];
      //表格展示字段,顺序要正确
      const fields =  ["name", "sexy","age","hobby","provices","city","status"]
      //这是实现导出多级表头的关键两点的中的第二点,合并表头单元格;
      //row:代表行,col:代表列,rowspan:代表合并行数,colspan:代表合并列数,
      //也就是表头有几个要合并单元格的属性,merges数组属性就有几个;
      const merges = [
            //导出表格的第一行第一列,行合并2个单元格,列就用自己的一个;
            {row: 0, col: 0, rowspan: 2, colspan: 1},
            //导出表格的第一行第二列,行合并1个单元格,列合并3个单元格;
            {row: 0, col: 1, rowspan: 1, colspan: 3},
            {row: 0, col: 4, rowspan: 1, colspan: 2},
            {row: 0, col: 6, rowspan: 2, colspan: 1},
         ]
        // 如果导出前要处理数据,需要深克隆一份表格数据,然后进行处理
        exportTableData = JSON.parse(JSON.stringify(exportTableData));
        const config = {
                data: exportTableData,//exportTableData为表格数据,为空的话,导出表格只显示表头
                fields:fields,
                headers: [header1, header2],
                merges: merges,
                attrs: [],
                view: [],
                columnsWidth: [20, 20, 20, 20, 20,20, 20,],//每行列的宽
                // protect: {},
                sheetName: "个人信息"
         };
         // 设置全表单元格边框,居中布局
         config.attrs.push({
                rowStart: 0,
                rowEnd: config.data.length + 1,//表格表头多几层,就加几个
                colStart: 0,
                colEnd: config.fields.length - 1,
                attr: {
                    alignment: { vertical: "middle", horizontal: "center" },
                    border: {
                        top: { style: "thin" },
                        left: { style: "thin" },
                        bottom: { style: "thin" },
                        right: { style: "thin" }
                    }
                }
           });
           // 设置表头填充颜色,字体加粗
            config.attrs.push({
                rowStart: 0,
                rowEnd: 1,//表格表头多几层,就写几
                colStart: 0,
                colEnd: config.fields.length - 1,
                attr: {
                    fill: {
                        type: "pattern",
                        pattern: "solid",
                        fgColor: { argb: "c5c8ce" }
                    },
                    font: {
                        bold: true
                    }
                }
            });
           return config;
 }
</script>
</body
</html>

vue中实现导出

第一步,还是先安装,可以使用npm,yarn,pnpm,都可以

npm install exceljs
npm install file-saver

第二步,在assets/index.js中暴露出一个exportDataToExcel方法

这个方法跟上面的方法是基本一样的,有两个点不同:
1.引入方式不同,vue项目可以使用模块引入,
2.还有方法中一个函数的使用,最后的FileSaver.saveAs(),在HTML代码中该方法可以直接用saveAs();主要原因还是引入的方式不同而已;

下面,一样的代码我就省略了,主要写不一样的代码展示:

// 封装exceljs
const ExcelJS = require('exceljs');
const FileSaver = require('file-saver');

export function exportDataToExcel(config, fileName){
...
}

async function createExcel(options){
...
  // 生成excel文件
  workbook.xlsx.writeBuffer().then(buffer => {
    // application/octet-stream 二进制数据
    FileSaver.saveAs(new Blob([buffer], { type: 'application/octet-stream' }), options.fileName)
  })
}

第三步,项目中的使用

把第二步写的方法在项目中导入;

<script>
 import { exportDataToExcel } from "../assets/index.js";

 data() {
      return {
         exportTableData:[
		    {name:'张三',sexy:'男',age:22,hobby:'篮球',provices:'河南省',city:'郑州',status:'未婚'},
		    {name:'李四',sexy:'女',age:23,hobby:'排球',provices:'北京市',city:'北京市',status:'未婚'},
		    {name:'王二',sexy:'男',age:28,hobby:'足球',provices:'山东省',city:'青岛',status:'已婚'},
		  ]
      };
  },
 methods:{
   onExport(){
     const config = this.exportConfig();
     exportDataToExcel(config, "人员信息表.xlsx");
   },
   //该方法和上面在HTML中的一样
   exportConfig(){
   ...
   }
 }

</script>

记录是为了自己日后学习和方便使用,也希望能够帮助到你;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2035243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JJ音乐,听歌自由!

林俊杰&#xff0c;这位才华横溢的音乐才子&#xff0c;用他的音符编织了一个又一个令人陶醉的梦幻世界。作为他的音乐爱好者&#xff0c;每一次倾听都是一次心灵的旅程。 他的歌声仿佛有一种魔力&#xff0c;能够穿透灵魂。从《江南》的诗意浪漫&#xff0c;到《不为谁而作的歌…

探索树莓派Pico 2:新一代RP2350芯片引领的微型开发革命

Raspberry Pi Pico 2 是由树莓派基金会推出的微处理器开发板&#xff0c;作为Pico系列的最新成员&#xff0c;它在原有的基础上进行了多项改进和扩展。这款开发板搭载了全新的RP2350芯片&#xff0c;具有更强大的处理能力和更多的功能特性。 1. Raspberry Pi Pico 2的特性和规格…

使用CUbeMX配置STM32F103C8T6 CRC校验

一、CubeMX配置 1.配置RCC 2.配置SYS 3.启用CRC校验 二、Keil添加程序 1.main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program body*******…

LVGL——(4)标签控件

文章目录 一、介绍二、用法1、创建2、显示文本2.1 直接设置要显示的文本2.2 格式化给定要显示的文本2.3 在 label 中进行换行 3、改变字体大小4、长模式5、文本选择6、文本对齐方式7、非常长的文本8、显示内置图标字体9、事件处理 三、拓展1、修改文本颜色1.1 Palette&#xff…

研0 冲刺算法竞赛 day30 P1102 A-B 数对

P1102 A-B 数对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; ①map&#xff0c;键值对计数&#xff0c;将A-B->A-C ②先排序&#xff0c;找对应差值为C的第一个和最后一个计数 代码&#xff1a; #include<iostream> #include <map> #i…

Typora绿色版

1、下载安装 Typora 官网地址&#xff1a;https://typora.io/ 中文站地址&#xff1a;Typora 2、击活 Typora 鼠标右击文件所在位置查询 resources\page-dist\static\js\LicenseIndex.180dd4c7.4da8909c.chunk.chunk.js e.hasActivated"true"e.hasActivated, 替…

使用nvm切换Node.js版本

一、安装nvm nvm&#xff08;Node Version Manager&#xff09;是一个用于管理Node.js版本的工具&#xff0c;它允许你在同一台机器上安装和切换多个Node.js版本。 1.安装nvm https://github.com/coreybutler/nvm-windows 访问以上链接到github去下载 点击releases 下载下图…

优化if-else的几种方式

优化if-else的几种方式 策略模式1、创建支付策略接口2、书写不同的支付方式逻辑代码微信支付QQ支付 3、service层的实现类使用4、controller层的调用说明 枚举与策略模式结合1、创建枚举2、service层书写处理方法3、controller层调用4、说明 Lambda表达式与函数接口说明 策略模…

用于理解视频的基础视觉编码器VideoPrism

人工智能咨询培训老师叶梓 转载标明出处 如何让机器有效地理解和处理视频内容&#xff0c;一直是计算机视觉领域的一个挑战。最近&#xff0c;Google Research的研究人员提出了一种名为VideoPrism的新型视频编码器&#xff0c;旨在通过单一的冻结模型处理多样化的视频理解任务。…

风云崛起之拉氏变换和拉式逆变换

图像的分割写出来了&#xff0c;但是写的不好&#xff0c;暂时先不发了。这两天小y想在把拉式变换的内容写出来&#xff0c;小y最近再看信号和电路&#xff0c;需要复习数学&#xff0c;所以把这点写出来。 首先要推出分布积分的公式&#xff0c;我们知道积分和微分为逆运算&am…

纯css实现多行文本右下角最后一行展示全部按钮

未展开全部&#xff1a; 展开全部&#xff1a; 综上演示按钮始终保持在最下方 css代码如下&#xff1a; <div class"info-content"><div class"info-text" :class"!showAll ? mle-hidden : "><span class"show-all"…

STM32-定时器-定时器中断-PWM调光

1、TIM 定时器 定时器是一种电子设备或软件组件&#xff0c;用于在预定时间后触发一个事件或操作。它可以基于时钟信号或其他周期性信号来工作&#xff0c;并且可以用来测量时间间隔、生成延时、触发中断等。 时钟信号 时钟信号是一种周期性的电信号&#xff0c;用于同步电路中…

如何检查端口占用:netstat和lsof指令

在网络故障排查和系统管理中&#xff0c;检查端口占用情况是一项常见且重要的任务。本文将详细介绍如何使用 netstat 和 lsof 这两个强大的工具来检查端口占用和相关服务。 1. 使用 netstat 查看端口占用 netstat (network statistics) 是一个用于显示网络连接、路由表、接口…

Flutter 学习 一部分注意点记录

使用AndroidStudio进行开发 假设你已经配置好Flutter和dart的SDK. 创建一个可执行dart文件 如果需要直接新建一个dart文件来运行&#xff0c;可以点击 File->New->New Flutter Project &#xff0c;下面是接下来弹出的新建项目弹窗&#xff0c;选中左边的Dart&#xff…

SpringBoot 整合 RabbitMQ 实现延迟消息

一、业务场景说明 用于解决用户下单以后&#xff0c;订单超时如何取消订单的问题。 用户进行下单操作&#xff08;会有锁定商品库存、使用优惠券、积分一系列的操作&#xff09;&#xff1b;生成订单&#xff0c;获取订单的id&#xff1b;获取到设置的订单超时时间&#xff0…

python语言day5 MD5 json

md5&#xff1a; python提供了内置的md5加密功能&#xff0c;使用md5模拟一个小项目&#xff1a; 注册&#xff1a; 启动py程序&#xff0c;在控制台界面提示用户输入用户名及密码&#xff1b; 使用md5加密 密码&#xff1b; 创建txt文件记录输入的用户名 和密文。 登录&…

访问网站显示不安全打不开怎么办如何处理

当访问网站时浏览器提示“不安全”&#xff0c;这通常是由于多种原因造成的。下面是一些常见的原因及其解决办法&#xff1a; 未启用HTTPS协议 如果网站仅使用HTTP协议&#xff0c;数据传输没有加密&#xff0c;会被浏览器标记为“不安全”。解决办法是启用HTTPS协议&#xff…

vue3-02-vue3中的组件通信

目录 组件通信一、vue3组件通信和vue2的区别二、父子通信2.1 props通信1&#xff09;父→子传递数据&#xff08;父组件向子组件传递数据&#xff09;2&#xff09;子→父传递数据 2.2 v-model1&#xff09;v-model的本质2&#xff09;给modelValue起别名3&#xff09;$event 2…

【资源】wordpress 子比主题

简介 子比主题是一款功能强大的WordPress主题模板&#xff0c;支持社区论坛、商城、支付、古腾堡编辑器等多种功能。很多资源类网站都是基于此搭建的。搭建后的效果基本上和官网一致&#xff0c;可查看官网的演示效果。 官方网站&#xff1a;https://www.zibll.com/ 如要获取…

精酿啤酒的酿造过程 你喝的不仅仅是酒

大家下午好呀&#xff01;今天我要带大家一起探索精酿啤酒的神秘世界&#xff01;&#x1f31f;&#x1f31f; 第一步&#xff1a;原料准备 精酿啤酒的四大原料&#xff1a;麦芽、啤酒花、水和酵母。 别小看这些原料&#xff0c;它们的品质直接决定了啤酒的风味。&#x1f33e;…