记录--前端如何优雅导出多表头xlsx

news2024/10/7 10:23:46

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

xlsx导出是比较前后端开发过程中都比较常见的一个功能。但传统的二维表格可能很难能满足我们对业务的需求,因为当数据的维度和层次比较多时,二维表格很难以清晰和压缩的方式展现所有的信息,所以我们也就经常能碰到多级表头开发了。

demo

每当我们新使用一个插件的时候,我们都可以看着官方文档去新建立一个demo,然后去尝试一下效果,这有助于我们分析错误。

npm i xlsx -S
function exportFile() {
  const ws = utils.json_to_sheet([])
  const wb = utils.book_new()
  utils.sheet_add_aoa(ws, [
    [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
  ], { origin: 'A1' })
  utils.book_append_sheet(wb, ws, 'Data')
  writeFileXLSX(wb, 'SheetJSVueAoO.xlsx')
}
exportFile()

demo已经成功了,xlsx已经下载下来了。

需求分析

  1. 新建一个表格
  2. 根据表头将表格进行合并
  3. 对合并后的表头进行内容填充
  4. 填入数据内容

效果如上图(时间原因就先不写xlsx的样式了)。

需求实现

  1. 合并单元格: 需要指定开始的行和列以及结束的行和列,如{ 's': { 'r': 0, 'c': 0 }, 'e': { 'r': 3, 'c': 0 } },计算好需要合并的单元格后统一赋值给!merges属性。
  2. 合并单元格后填充内容:由多个合并后的单元格填入内容时,应该也按照多个单元格填入,只是第一个有内容,其他按空填入即可。
  3. 表头结束后我们可以指定在某一行继续填入内容,即可继续填入数据内容。
function exportFile() {
  const ws = utils.json_to_sheet([])
  ws['!merges'] = [
    { 's': { 'r': 0, 'c': 0 }, 'e': { 'r': 3, 'c': 0 } },
    { 's': { 'r': 0, 'c': 1 }, 'e': { 'r': 3, 'c': 1 } },
    { 's': { 'r': 0, 'c': 2 }, 'e': { 'r': 3, 'c': 2 } },
    { 's': { 'r': 0, 'c': 3 }, 'e': { 'r': 0, 'c': 8 } },
    { 's': { 'r': 1, 'c': 3 }, 'e': { 'r': 3, 'c': 3 } },
    { 's': { 'r': 1, 'c': 4 }, 'e': { 'r': 1, 'c': 7 } },
    { 's': { 'r': 2, 'c': 4 }, 'e': { 'r': 3, 'c': 4 } },
    { 's': { 'r': 2, 'c': 5 }, 'e': { 'r': 3, 'c': 5 } },
    { 's': { 'r': 2, 'c': 6 }, 'e': { 'r': 2, 'c': 7 } },
    { 's': { 'r': 1, 'c': 8 }, 'e': { 'r': 3, 'c': 8 } },
    { 's': { 'r': 0, 'c': 9 }, 'e': { 'r': 3, 'c': 9 } }
  ] // 合并单元格内容
  const wb = utils.book_new()
  utils.book_append_sheet(wb, ws, 'Data')
  utils.sheet_add_aoa(ws, [
    ['序号', '姓名', '性别', '公司概况', '', '', '', '', '', '备注'],
    ['', '', '', '职位', '项目', '', '', '', '公司名称'],
    ['', '', '', '', '项目时长', '项目描述', '金额', ''],
    ['', '', '', '', '', '', '总金额', '利润']
  ], { origin: 'A1' }) // 表头内容
  utils.sheet_add_aoa(ws, [
    [0, '张三', '男', '区域经理', '3天', '暂无描述', 998, 9.98, '阿里巴巴', '暂无'],
    [1, '李四', '女', 'CEO', '30天', '稳了', 998, 9.98, '中石油', '暂无']
  ], { origin: 'A5' }) // 数据内容
  writeFileXLSX(wb, `${+new Date()}.xlsx`)
}
好的,大功告成,今天就先到这里?

这东西也太丑了吧,我是一个开发,我不是来这里数格子的。看看上面的代码,我都不好意思说是我自己写的。要不到同事电脑上提交一下吧?

数据分析

[
    { 's': { 'r': 0, 'c': 0 }, 'e': { 'r': 3, 'c': 0 } },
    { 's': { 'r': 0, 'c': 1 }, 'e': { 'r': 3, 'c': 1 } },
    { 's': { 'r': 0, 'c': 2 }, 'e': { 'r': 3, 'c': 2 } },
    { 's': { 'r': 0, 'c': 3 }, 'e': { 'r': 0, 'c': 8 } },
    { 's': { 'r': 1, 'c': 3 }, 'e': { 'r': 3, 'c': 3 } },
    { 's': { 'r': 1, 'c': 4 }, 'e': { 'r': 1, 'c': 7 } },
    { 's': { 'r': 2, 'c': 4 }, 'e': { 'r': 3, 'c': 4 } },
    { 's': { 'r': 2, 'c': 5 }, 'e': { 'r': 3, 'c': 5 } },
    { 's': { 'r': 2, 'c': 6 }, 'e': { 'r': 2, 'c': 7 } },
    { 's': { 'r': 1, 'c': 8 }, 'e': { 'r': 3, 'c': 8 } },
    { 's': { 'r': 0, 'c': 9 }, 'e': { 'r': 3, 'c': 9 } }
]

我想要转成上面的数据结构,r从0开始,最大值就是它的深度,c从0开始,最大值就是它的广度。因为这是一个多级表头,每一级都会出现比上一级相等或更多子级的情况,我好像已经把答案说到嘴边了。对,就是用树形结构将其转换处理。

我们结合上面已转换好的列表结构和下面准备转换的树形结构,比如现在要合并第一个单元格序号,我们应该先找到起始位置,也就是0,0,这个很好确定;我们单单从当前节点并不能判断真正的结束位置,我们应该找到同级节点的最大深度,也就是公司概况->项目->金额->总金额,深度为3。所以它的结束位置应该为3,0

当我们要合并横向单元格的时候,比如公司概况,它下边有三个子节点分别是职位,项目,公司名称,而子节点下方仍有不同的子节点,此时我们就应该去获取它们的每个子节点的每层子节点的总长度 - 1,为什么要 - 1,因为当前节点和第一个子节点占用的是同一个col,因此可以需要减一。也就是说,如果公司概况的起始点为0,3,那么它的终止位置由此可推:职位+项目+公司名称-1+项目时长+项目描述+金额-1+总金额+利润-1 = 5。所以终点位置为0,3+5 => 0,8

const mergedCells = [ 
    { name: '序号', prop: 'id' },
    { name: '姓名', prop: 'name' },
    { name: '性别', prop: 'sex' },
    { 
        name: '公司概况', 
        children: [ 
            { name: '职位', prop: 'jobTitle' },
            { 
                name: '项目', children: [
                    { name: '项目时长', prop: 'projectTime' },
                    { name: '项目描述', prop: 'projectDesc' },
                    { 
                        name: '金额', 
                        children: [
                            { name: '总金额', prop: 'total' },
                            { name: '利润', prop: 'profit' }
                        ] 
                    } 
                ] 
            }, 
            { name: '公司名称', prop: 'companyName' }
        ] 
    },
    { name: '备注', prop: 'remark' } 
]

思路分析

  1. 找到当前节点的深度和广度
  2. 根据当前节点深度和广度,生成当前节点单元格开始与结束位置
  3. 根据当前节点深度和广度,生成表头数据结构
  4. 根据最大深度位置,生成表单列表数据

代码实现

tips: 如果你对树结构的遍历还不太熟悉,可以看看【前端不求人】树形结构和一维数组,一笑泯恩仇

获取当前节点最大广度和最大深度

  1. 递归发现当前已无子节点时,就返回0,然后每返回一层就递增1,每次返回时都获取当前节点的最大值,这样就能获得最深层数。
  2. 递归记录每层每个子节点的长度 - 1,这样就能获取当前列表的最大宽度。
  3. 我们使用map做记录,下次获取就不需要重新计算了。
const map = new Map()
const getCellsSize = list => {
    if (map.has(list)) { return map.get(list) }
    if (list?.length) {
        let rows = -1, cols = list.length - 1
        list.forEach(item => {
            if (item.children) {
                const size = getCellsSize(item.children)
                rows = Math.max(size[0], rows)
                cols += size[1]
            }
        })
        map.set(list, [rows + 1, cols])
        return [rows + 1, cols]
    }
}

合并单元格开始和结束位置

  1. 获取当前节点的开始和结束位置
  2. 当前节点无子节点,单元格宽为1,高为整个根节点的最大深度
  3. 当前节点有子节点,单元格高为1,宽为当前节点的宽,即最大广度
const size = getCellsSize(headers)
const headerMerge = []
const mergeHeadersCell = (headers, row, col) => {
    for (let i = 0, len = headers.length;i < len;i++) {
        const cell = headers[i]
        if (!cell.children?.length) {
            if (row === size[0]) { continue }
            headerMerge.push({ s: { r: row, c: col + i }, e: { r: size[0], c: col + i } })
        } else {
            const size = map.get(cell.children)
            headerMerge.push({ s: { r: row, c: col + i }, e: { r: row, c: col + size[1] + i }})
            mergeHeadersCell(cell.children, row + 1, col + i)
            col += size[1]
        }
    }
}

多表头值填充

  1. 我们声明一个headerValue的空数组来记录表头内容
  2. headerValue应该是一个二维数组,headerValue[i][j]代表第i行第j列的内容
  3. 当发现当前节点有children,直接获取当前节点的宽度,该宽度就是合并后空白单元格的个数。
  4. 当发现当前节点并没有headerValue,表示前面的节点被纵向合并了,因此应该直接加上这些空白单元格的节点
  const headerValue = []
  const getHeadersValue = (headers, row, col) => {
    if (!headerValue[row]) {
      headerValue[row] = new Array(col).fill('')
    }
    for (let i = 0, len = headers.length; i < len; i++) {
      const cell = headers[i]
      headerValue[row].push(cell.name)
      if (cell.children?.length) {
        const len = getCellsSize(cell.children)[1]
        const emptyNameList = new Array(len).fill('')
        headerValue[row].push(...emptyNameList)
        getHeadersValue(cell.children, row + 1, col + i)
      }
    }
  }

获取列表prop

  1. 继续递归mergedCells
  2. 收集无叶子节点的prop值
  3. 将prop值依次放进一个数组中以备后续使用
const bodyMapList = []
const getBodyMapList = list => {
    if (list?.length) {
        list.forEach(item => {
            !item.children ? bodyMapList.push(item.prop) : getBodyMapList(item.children)
        })
    }
}

list.map(item => bodyMapList.map(key => item[key]))

以上就是核心代码展示啦,如果想看完整代码,可以到github观看,欢迎star。

总结

我们通过计算当前树节点的大小,就可以获取该节点的广度和深度,通过广度和深度又可以让我们进一步去演算当前节点是否需要去合并其他单元格,是否需要生成空白单元格的数据内容。生成表格内容则只需要将最子层节点的prop收集,然后对应取值即可。

本文转载于:

https://juejin.cn/post/7243435843145678907

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

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

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

相关文章

macOS Ventura 13.5beta3(22G5048d)发布

系统介绍 黑果魏叔 6 月 16 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.5 开发者预览版 Beta 3 更新&#xff08;内部版本号&#xff1a;22G5048d&#xff09;&#xff0c;本次更新距离上次发布隔了 15 天。 macOS Ventura 带来了台前调度、连续互通相机、Fa…

【Axure 教程】中继器(进阶篇)

一、修改、删除指定行 首先我们还是在 Axure 页面中拖入一个【中继器】&#xff0c;并双击打开&#xff0c;在默认的【矩形】后面加上【修改】和【删除】按钮&#xff1a; 然后我们给修改按钮添加【中继器事件】&#xff0c;选择【更新行】&#xff1a; 可以看到&#xff0c;由…

Axure RP 9 基础教程 元件基础3

11、组合元件 Axure中可以将多个元件组合起来&#xff0c;组合可以被命名&#xff0c;也可以被当成一个元件来进行交互&#xff0c;调整位置和大小等。选中多个元件&#xff0c;在顶部菜单中点击组合图标即可。选中一个组合&#xff0c;点击取消组合&#xff0c;可以就地解散。…

多传感器融合分类及对比

1.多传感器融合的体系结构 在多传感器融合中&#xff0c;按照对原始数据处理方法的不同&#xff0c;多传感器融合系统的体系结构可以分为三种&#xff1a;集中式&#xff0c;分布式和混合式(混合式又分为有反馈结构和无反馈结构)。 集中式融合&#xff1a;将各传感器获得的原始…

软件设计的核心方法及实例解析

李连杰电影版《倚天屠龙记》里有个经典的名场面&#xff0c;祖师爷爷张三丰花了三分钟教张无忌太极拳&#xff0c;张无忌学成打败了对手。三丰爷爷的教学思路是这样的&#xff1a;爷爷演示太极拳让张无忌跟着练&#xff0c;边练边问张无忌记住了多少&#xff0c;等张无忌把所有…

网络系统安全——MS15_034漏洞利用与安全加固

Kali 192.168.124.162 Windows server 2008 192.168.124.169 检查2008服务器的IIS网站是否正常&#xff0c;进入2008服务器&#xff0c;使用ie浏览器访问本机地址 切换到kali&#xff0c;使用命令ping来测试他们的连通性 然后使用使用命令curl测试&#xff0c;测试&#x…

FTP协议,带你了解FTP协议

目录 一、FTP的概述 1.FTP的理念 2.FTP数据连接模式 3.连接模式分类 4.主动和被动模式的工作原理 二、配置FTP服务 1、配置匿名用户FTP服务 1. 1安装FTP服务器软件 1.2 配置FTP服务器 1.3 重启FTP服务器 1.4 测试FTP服务器 2.关闭防火墙安装vsftpd软件包 3.开启FTP…

单片机中移植lua解释器

一、基本开发环境 开发环境基于野火STM32开发板。 前测试的 Lua 解释器版本为 5.4.2。 官网下载lua资源包&#xff0c;下载地址如下&#xff1a; https://www.lua.org/ https://github.com/rjpcomputing/luaforwindows/releases lua: Lua 国内镜像 (gitee.com)‍ 二、移植Lua解…

AIGC数据库工具-阿里开源Chat2DB

前言 今天无意间发现了一个AIGC数据库工具&#xff0c;chat2DB&#xff0c;重点&#xff01;&#xff01;&#xff01;阿里开源&#xff0c;其设计产品的思想给了我很多灵感&#xff0c;故记录一下&#xff0c;并分享给大家。 概述&#xff1a; Chat2DB 是一款有开源免费的多…

驱动开发:内核RIP劫持实现DLL注入

本章将探索内核级DLL模块注入实现原理&#xff0c;DLL模块注入在应用层中通常会使用CreateRemoteThread直接开启远程线程执行即可&#xff0c;驱动级别的注入有多种实现原理&#xff0c;而其中最简单的一种实现方式则是通过劫持EIP的方式实现&#xff0c;其实现原理可总结为&am…

【C++】入门基础知识详解(二)

目录 一、内联函数 1、概念 2、特性 3、内联函数与宏的优缺点 二、auto关键字(C11) 1、auto 简介 2、auto的使用细则 2.1 auto与指针和引用结合起来使用 2.2 在同一行定义多个变量 3、auto不能推导的场景 3.1 auto 不能作为函数的参数 3.2 auto 不能直接用来声明数组 三、…

英语中如何描述五颜六色

前言 如何用英语描述五颜六色&#xff0c;看完这篇文章&#xff0c;你就学会了 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是沐风晓月&#xff0c;阿里云社区博客专家 &#x1f609;&#x1f609; &#x1f495; …

Axure RP 9 基础教程 元件基础2

第一章&#xff1a;Axure RP 9的元件(2) 6、改变元件的位置 要改变元件的位置&#xff0c;只需要拖动对应的元件即可。另外也可以在顶部快捷样式菜单中设置坐标值&#xff0c;然后按回车键&#xff0c;让元件移动到指定位置。 X轴是横轴&#xff0c;改变可以调整左右的位置。 Y…

技术科普与解读:ChatGPT 大模型硬核解读!(一)家族历史从GPT-1到ChatGPT

多模态&#xff0c;指的是融合文本、图像、视频或音频等多种模态作为输入或输出。 GPT-4是严格意义上的多模态模型&#xff0c;可以支持图像和文字两类信息的同时输入&#xff0c;输出为文本。从学术界的分析来看&#xff0c;无论是知识/能力获取还是与现实物理世界的交互&…

【应用安全架构】什么是联合身份管理?

介绍 联合身份管理是一种可以在两个或多个信任域之间进行的安排&#xff0c;以允许这些域的用户使用相同的数字身份访问应用程序和服务。这称为联合身份&#xff0c;使用这种解决方案模式称为身份联合。 联合身份管理建立在两个或多个域之间的信任基础之上。例如&#xff0c;信…

如此有艺术感的AI生成式二维码,你肯定没有见过

这是一张很常见的图片&#xff0c;要说有特殊的话可能是由 AI 来生成的&#xff0c;其它并无特别之处。但给它加上三个定位点后&#xff0c;这张图就变成一个可以扫描识别的二维码&#xff1a; 真的假的&#xff1f;不信你长按图片识别一下&#xff01;我一次看到时&#xff0c…

基于Hexo和Butterfly创建个人技术博客,(8) 博客网站butterfly主题UI框架美化

Butterfly官方网站&#xff0c;请 点击进入 说明&#xff1a; 此文中的设置并不影响网站的整体&#xff0c;只是一些视觉上的调整&#xff0c;可以按需调整。 本章目标&#xff1a; 掌握butterfly主题的配置&#xff0c;优化UI样式 一、特效 1、过场动画 在每个页面打开前会有…

干货!具有三维感知的换脸算法

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 作者介绍 李逸轩 上海交通大学2022级硕士研究生&#xff0c;研究兴趣主要是三维人脸生成 报告题目 具有三维感知的换脸算法 内容简介 AI换脸旨在将一张给定目标图片中的人脸五官替换成源图片中的另一个人&#…

操作系统中的进程调度与优先级算法:理论与实践探索

前言 在计算机科学领域中&#xff0c;进程调度是操作系统中一个重要的组成部分&#xff0c;它负责决定哪个进程能够获得 CPU 的执行权&#xff0c;以及如何合理地分配 CPU 时间。通过合理的进程调度算法&#xff0c;可以提高系统的性能和响应能力。在本篇博客中&#xff0c;我…

ubuntu开发环境

boost介绍 Boost是一个广受欢迎的、开源的C程序库集合&#xff0c;提供了许多高质量和可重用的组件&#xff0c;涵盖了广泛的领域&#xff0c;如容器、算法、函数对象、日期与时间、正则表达式、文件系统、线程等。Boost旨在通过提供开发人员友好的C工具和组件来增强C的功能。…