最新要闻
- 昂利康:七氟烷原料药上市申请获批
- 新出行贺磊聊“李想疯狂输出的背后” 点名多位车企高管
- 北京工体梅西含量超标:今晚8点举行足球友谊赛-全球快看点
- 一加第四款《原神》联名机在路上了:这次主角是派蒙
- 双髻鲨头上的“锤子”有什么用? 世界热资讯
- 618先别买洗地机!分享几条你不知道的要点|即时焦点
- 万方查重多久出报告(万方查重)-全球速递
- 最新快讯!经常出汗是怎么回事呢_经常出汗是怎么回事
- 亚运会倒计时100天!vivo成手机独家供应商:交付vivo X90s、iQOO 11S 天天要闻
- 世界即时:越南人的国民神车! 越南版“宝骏悦也”发布:车长仅3114mm
- 天天快看点丨河北热成了炣北!多地气温突破40℃:石家庄成今年首个40℃省会级城市
- 环球要闻:谷歌推出“硬核”智能家居编辑器:支持用户手搓脚本
- 环球最资讯丨周鸿祎:做大模型这3个月 最惭愧的是自己不擅长吹牛
- 美晨生态:诸城经开投累计质押股数约为7843万股
- 会说话!爱德华兹谈生涯至今最爱的时刻:唐斯的60分之夜
- 游戏网站开除40%员工 “AI编辑”每周撰写数百篇问题文章引众怒|每日焦点
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
记录--前端如何优雅导出多表头xlsx
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
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已经下载下来了。
(相关资料图)
需求分析
- 新建一个表格
- 根据表头将表格进行合并
- 对合并后的表头进行内容填充
- 填入数据内容
效果如上图(时间原因就先不写xlsx的样式了)。
需求实现
- 合并单元格: 需要指定开始的行和列以及结束的行和列,如
{ "s": { "r": 0, "c": 0 }, "e": { "r": 3, "c": 0 } }
,计算好需要合并的单元格后统一赋值给!merges
属性。 - 合并单元格后填充内容:由多个合并后的单元格填入内容时,应该也按照多个单元格填入,只是第一个有内容,其他按空填入即可。
- 表头结束后我们可以指定在某一行继续填入内容,即可继续填入数据内容。
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" } ]
思路分析
- 找到当前节点的深度和广度
- 根据当前节点深度和广度,生成当前节点单元格开始与结束位置
- 根据当前节点深度和广度,生成表头数据结构
- 根据最大深度位置,生成表单列表数据
代码实现
tips: 如果你对树结构的遍历还不太熟悉,可以看看【前端不求人】树形结构和一维数组,一笑泯恩仇
获取当前节点最大广度和最大深度
- 递归发现当前已无子节点时,就返回0,然后每返回一层就递增1,每次返回时都获取当前节点的最大值,这样就能获得最深层数。
- 递归记录每层每个子节点的长度 - 1,这样就能获取当前列表的最大宽度。
- 我们使用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,高为整个根节点的最大深度
- 当前节点有子节点,单元格高为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] } }}
多表头值填充
- 我们声明一个headerValue的空数组来记录表头内容
- headerValue应该是一个二维数组,headerValue[i][j]代表第i行第j列的内容
- 当发现当前节点有children,直接获取当前节点的宽度,该宽度就是合并后空白单元格的个数。
- 当发现当前节点并没有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
- 继续递归mergedCells
- 收集无叶子节点的prop值
- 将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
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
关键词:
记录--前端如何优雅导出多表头xlsx
昂利康:七氟烷原料药上市申请获批
新西兰经济陷入技术性衰退
天天热头条丨商品日报(6月15日):焦煤领涨商品市场 贵金属承压下行
新华指数|钢“财”说:库存降幅收窄,宏观情绪回暖|全球最资讯
新出行贺磊聊“李想疯狂输出的背后” 点名多位车企高管
北京工体梅西含量超标:今晚8点举行足球友谊赛-全球快看点
一加第四款《原神》联名机在路上了:这次主角是派蒙
双髻鲨头上的“锤子”有什么用? 世界热资讯
618先别买洗地机!分享几条你不知道的要点|即时焦点
万方查重多久出报告(万方查重)-全球速递
扩展中国剩余定理(EXCRT)
天天即时看!三类重要Linux文件的用途与区别
沉浸式的使用 Windows/office激活工具|环球聚焦
delphi7使用rave5.0展现数据库数据报表
一文读懂物联网平台如何搞定80%以上的物联网项目
最新快讯!经常出汗是怎么回事呢_经常出汗是怎么回事
亚运会倒计时100天!vivo成手机独家供应商:交付vivo X90s、iQOO 11S 天天要闻
世界即时:越南人的国民神车! 越南版“宝骏悦也”发布:车长仅3114mm
天天快看点丨河北热成了炣北!多地气温突破40℃:石家庄成今年首个40℃省会级城市
环球要闻:谷歌推出“硬核”智能家居编辑器:支持用户手搓脚本
环球最资讯丨周鸿祎:做大模型这3个月 最惭愧的是自己不擅长吹牛
美晨生态:诸城经开投累计质押股数约为7843万股
世界视讯!如何优雅地使用Low Code提高开发效率
环球热点!ctfpwn-堆入门之uaf(新手向)
会说话!爱德华兹谈生涯至今最爱的时刻:唐斯的60分之夜
游戏网站开除40%员工 “AI编辑”每周撰写数百篇问题文章引众怒|每日焦点
让手机更智能!小米小爱建议宣布已覆盖40余款机型 看看有你的吗?|全球报道
3A大作游戏《星空》尚未发售 却已收到玩家差评:直接0分 焦点速看
夜景拍摄表现出彩:尼康尼克尔Z DX 24mm f/1.7镜头开售_全球观热点
天天最新:芭比娃娃真人电影《芭比》确认引进中国内地:画风对比《小美人鱼》如何?
vivox9什么时候上市的多少钱(vivox9什么时候上市的) 最新资讯
资讯:上传自己java项目到maven中央仓库pom
当前关注:《深入探索C++对象模型》- 第一章 - 关于对象 - 笔记
.NET 文件上传服务设计 天天快播报
变形积木:以装配式装修助力美团酒店布局“百品、千城、万店”-当前观点
国资委召开中央企业提高上市公司质量暨并购重组工作专题会-环球热资讯
科创板收盘播报:科创50指数跌0.14% 电气设备股表现强势|环球热议
马斯克邀请丰田加入特斯拉快充阵营:你们充电太慢了
要闻:新一轮油价调整将于6月28日开启 这次又有降价的可能
Steam客户端大规模更新!新增实用笔记功能 世界热议
AMD Yes!RX 6650 XT显卡杀到1799元:比N卡更香 天天播资讯
贾跃亭团队出身就是不一样!高合HiPhi Y内饰官图发布:平替FF 91
世界热文:手机微信出现状态是什么意思(微信出现状态是怎么回事)
云小课|RDS for MySQL参数模板一键导入导出,参数配置轻松搞定|焦点简讯
【环球时快讯】数据分析提效5倍,国有集团企业数字化历程 | 数字化标杆
ESMapV数字孪生三维可视化云平台-零代码可视化设计师助力者
flask自定义参数校验、序列化和反序列化_视讯
出庭当晚特朗普与“金主”举行晚宴,筹得200多万美元-世界微头条
微资讯!新华社权威快报|“一箭41星” 发射成功
欠2.4亿罚款又如何?贾跃亭国内“现身”:我不要烧太多钱也能快速成功 环球快讯
端午节放假3天自驾方便了!滴滴租车正式上线:跑车、房车全都有
全球即时:一机搞定全屋清洁!石头新品洗地机A10 Ultra今晚首销:到手价3399元
1799元咬死RTX 3060显卡 英特尔新驱动继续鸡血:性能猛增33%|当前快看
用上丰田氢燃料电池 海马7X-H来了:不到5分钟加注续航达800km-天天微速讯
汉字找茬王连歌曲不甘怎么过-世界热闻
全球热文:解析Spring内置作用域及其在实践中的应用
世界观点:pickle
RPG游戏开发日志: 世界观速讯
石家庄太和网点电话_石家庄太和网
3个ChatGPT插件自动写书爆火!更多躺着赚钱玩法可以问AI自己_全球看热讯
3.1级地震发生时上海居民躲床下避难 监控视频显示:剧烈摇晃、余震可能性不大|今头条
全球热点评!盘点C#最有价值的10个语法糖
Cannot Reference “XxxClass.xxx” Before Supertype Constructor Has Been Called
足球服批发_足球装备批发|热推荐
男子赶集买毛蛋到家全变鸭子 后续会饲养:网友点赞赚大了
直言理想ONE被问界M7打残 学习华为要看什么书:李想整理分享
丰田手把手教经销商诋毁纯电|每日动态
矛盾的马斯克!特斯拉4D雷达首拆:千元成本、探测距离300米
女学霸边读研边兼职3年赚17万:为了明确自己毕业后适合什么工作
开源即时通讯IM框架MobileIMSDK的H5端开发快速入门-世界关注
港股午评:恒指涨0.83% 恒生科技指数涨1.82% 天天看点
热点在线丨会定期删除 微软回应Edge私传图片问题:不含任何用户标识
天玑9200+加持 vivo X90s官宣:安卓最强悍的曲面屏旗舰
全球聚焦:李想最新发言引围观:我们谁都没兴趣搞、那点销量有啥可搞的
温州近视司机深夜高速上镜片突然脱落:两眼一抹黑
动态焦点:张雪峰称高考绝对是普通家庭改变命运的最好出路 回应建议报理科
2023年农业电商行业发展现状调查及行业未来趋势分析
用益-今日财经视点:美联储如期暂停加息但放鹰!
深度学习应用篇-元学习[15]:基于度量的元学习:SNAIL、RN、PN、MN
『题解』BZOJ2839 集合计数|天天热点评
热推荐:00-串口和SSH方式登录
戴德梁行亚洲REITs报告:C-REITs市场快速扩容 ESG将成为高质量发展新动力
因为读书值得
每日动态!六合一套装:八喜冰淇淋3.8元/杯抄底(商超8元)
巴黎治鼠患官宣失败:市民将与600万只老鼠共存
世界微速讯:全系降价3万后 蔚来ET5旅行版今日首发上市:这颜值30万你买吗?
每日热门:618最值得入手智能门锁非它莫属:华为智能门锁系列该怎么选?
业内第一!小鹏首宣开放北京城市智能辅助驾驶:不靠高精地图|天天热点
《长安三万里》曝角色海报 7月8日梦回诗意大唐 要闻
顶奢好文:3W字,穿透Spring事务原理、源码,至少读10遍|焦点速读
c++ mutex 每日消息
微头条丨月的暗面——戴冰选集_对于月的暗面——戴冰选集简单介绍
当前快报:大导演冯小刚多年的白癜风“消失”了引围观 这病真能治好?医生释疑
天天热资讯!高速错过出口 丰田亚洲龙缓行被大货车追尾致5死 官方:两司机均分心驾驶
全球聚焦:李想:理想MPV不会是埃尔法“私生子” 开上街比法拉利还瞩目
女子穿联名汉服进入迪士尼被拦 官方回应:网友吵翻 说好的穿衣自由呢
视讯!对象说冷怎么回
北斗GPS卫星授时服务器(NTP授时)应用于地铁自控系统
最新SMS-Activate短信验证码接收教程-观天下