前端自定义导出PPT

1、背景

        前端导出PPT,刚接触这个需求,还是比较懵逼,然后就在网上查找资料,最终确认是可行的;这个需求也是合理的,我们做了一个可视化数据报表,报表导出成PPT,将在线报表转成文档类型留存;

2、技术方案

        实现这种复杂的功能,都得依赖前辈匠人,还好有一个比较完善好用的库:pptxgenjs

只有英文文档:Quick Start Guide | PptxGenJS,还可以搭配大家比较熟悉的库:html2canvas更好的实现完善的PPT

3、PptxGenJS运用

引入,生成一个简单的PPT文档

import pptxgen from "pptxgenjs";let pptx = new pptxgen();let slide = pptx.addSlide();slide.addText("React Demo!", { x: 1, y: 1, w: 10, fontSize: 36, fill: { color: "F1F1F1" }, align: "center" });pptx.writeFile({ fileName: "react-demo.pptx" }

pptx全局属性:

pptx.author = 'Brent Ely';
pptx.company = 'S.T.A.R. Laboratories';
pptx.revision = '15';
pptx.subject = 'Annual Report';
pptx.title = 'PptxGenJS Sample Presentation';
pptx.layout = 'LAYOUT_WIDE'; //13.5 x 7.5

其中最重要的属性,layout,顾名思义,就是设置PPT的slide的大小,默认就下面几种:

还可以自定义。这个x*y,是后面PPT页计算布局必不可少的;

slide master,自定义PPT页模板:

    ppt.defineSlideMaster({title: 'DEFAULT_SLIDE',objects: [{ image: { x: 0.25, y: 0.3, w: 0.6, h: 0.6, path: path} },{ image: { x: 10.8, y: 0.61, w: 1.485, h: 0.166, path: path} },{ image: { x: 12.3, y: 0.52, w: 0.72, h: 0.36, path: path} },{ line: { x: 0.25, y: 1, w: 12.8, h: 0, line: { color: '3874c5', width: 2 } } },{ image: { path: path, x: 0, y: 7.2, w: '100%', h: 0.3, size: { type: 'cover' } } },],});

我们自己创建PPT时,也会引用模板,这个就是自定义模板,就避免每页都设置;

slide,PPT页属性对象:

创建一页PPT,addSilde({masterName});masterName就是上面自定义的模板,就是上面的DEFAULT_SLIDE

4、添加表格

表格是一个常用功能,PPT的表格也比较完善,addTable();

表头和合计,这个pptx没有特殊的处理,只能作为正常row处理;

行高度问题row Height,表格设置高度h,如果不设置行高,rowH,这样表格会填充h,设置了rowH,最小高度会按rowH设置值显示;

表格一般有比较多的数据,PPT页就那么高,肯定会有超出PPT页的情况,pptx支持自定义分页,autoPage:true,然后结合autoPageCharWeight,autoPageLineWeight调试分页,在实际使用过程发现自动分页也是根据你设置的rowH和H来计算的,对表格单元格多行,还是会超出,然后导出的ppt文档,会报错,要修复啥的,所以我选择手动给它分页;

列宽问题,默认是等分的,colW,实际开发是最好根据列的宽度,然后计算colW的,记住设置了colW了,表格会严格按照设置的值展示,不会自适应,所以还要程序根据w在计算;

  setTable(data, option = {}) {let row = [];let options = { fontFace: 'Microsoft YaHei', fontSize: 12, margin: 0.05, valign: 'middle', align: 'left' };options['border'] = [{ pt: 1, color: 'ffffff', type: 'dash' }, { type: 'none' }, { pt: 1, color: 'ffffff', type: 'dash' }, { type: 'none' }];let head = [];let colW = [];data.head.forEach(item => {head.push({ text: item.label, options: { ...options, fill: '1E4265', color: 'ffffff' } });if (item.width < 85) {colW.push(0.1);} else if (item.width < 101) {colW.push(0.2);} else if (item.width < 121) {colW.push(0.3);} else {colW.push(0.4);}});// autoPage: true, newSlideStartY: 1.1, autoPageRepeatHeader: true,暂时自动分页不太行let page = { ...this.page, rowH: 0.5, valign: 'middle' };// 表格超出,ppt分页展示,最多展示三页数据let tableData = data.table || [];tableData = tableData.slice(0, 30);tableData.forEach((item, index) => {let temp = [];let fill = index % 2 == 1 ? 'ffffff' : 'f2f2f2';data.head.forEach(h => {temp.push({ text: item[h.prop] === null ? '' : item[h.prop], options: { ...options, fill } });});row.push(temp);});let sumW = colW.reduce((per, cur) => per + cur, 0);let fNum = option.h < 6 ? 5 : 10;let fRow = row.slice(0, fNum);let fOption = { ...page, ...option };fOption['colW'] = colW.map(item => Number((fOption.w / sumW) * item).toFixed(1));this.slide.addTable([head, ...fRow], fOption);let eNum = parseInt(fNum + 10);let tRow = row.slice(fNum, eNum);if (tRow.length) {// 第二页let slide = this.ppt.addSlide({ masterName: 'DEFAULT_SLIDE' });page['colW'] = colW.map(item => Number((page.w / sumW) * item).toFixed(1));slide.addTable([head, ...tRow], { ...page });tRow = row.slice(eNum, parseInt(eNum + 10));if (tRow.length) {// 第三页let slide = this.ppt.addSlide({ masterName: 'DEFAULT_SLIDE' });slide.addTable([head, ...tRow], { ...page });}}}
5、添加Image

addImage(),支持两种格式:data,base64数据;path,图片地址;注意,根据img的大小,然后换算实际的w和h;

  setChart(data, option = {}) {const { chartW, chartH, chartData } = data;let options = { ...this.page, ...option, sizing: { type: 'contain' } };let temp = (options.w / chartW) * chartH;if (temp > options.h) {temp = (options.h / chartH) * chartW;options.x = options.x + (options.w - temp) / 2;options.w = temp;} else {options.y = options.y + (options.h - temp) / 2;options.h = temp;}this.slide.addImage({ data: chartData, ...options });}

 复杂的html,可以通过html2canvas,把dom转成图片添加PPT

  async setDom2Image(dom, option = {}) {let result = {};const res = await html2Canvas(dom, { scale: 2 });result['chartData'] = res.toDataURL('image/jpeg', 1);result['chartH'] = res.height;result['chartW'] = res.width;this.setChart(result, option);}
6、添加文本

添加文本,这个介绍把一段html,添加成PPT文本

  setContent(content, option = {}) {let obj = [];content &&content.forEach(item => {let len = item.children.length;--len;item.children.forEach((cItem, index) => {obj.push({ text: cItem.text, options: { bold: cItem.bold ? true : false, color: cItem.color ? this.RGBToHex(cItem.color) : '333333', breakLine: index === len } });});});let options = { ...this.page, ...option, align: 'left' };if (options.h == 6) {options['fontSize'] = options.h == 6 ? 16 : 12;}this.setTitle(obj, { ...options });}setTitle(text, option = {}) {const options = {fontSize: 12, //字号fontFace: 'Microsoft YaHei',bold: false,color: '333333', //颜色 与背景颜色一样,一样不要 #,填满6位valign: 'middle', // 垂直居中 top middle bottom};this.slide.addText(text, { ...options, ...option });}

 由于PPT只支持16进制的颜色值,所以需要把rgb转成6位颜色值

  RGBToHex(rgb) {let regexp = /\d+/g;let res = rgb.match(regexp);return ((res[0] << 16) | (res[1] << 8) | res[2]).toString(16);}

把html转成slate的JSON

let document = new DOMParser().parseFromString(this.content, 'text/html');result['json'] = this.deserialize(document.body);deserialize(el, markAttributes = {}) {if (el.nodeType === Node.TEXT_NODE) {return jsx('text', markAttributes, el.textContent);} else if (el.nodeType !== Node.ELEMENT_NODE) {return null;}const nodeAttributes = { ...markAttributes };// define attributes for text nodesswitch (el.nodeName) {case 'STRONG':nodeAttributes.bold = true;}// font colorif (el.style.color) {nodeAttributes.color = el.style.color;}const children = Array.from(el.childNodes).map(node => this.deserialize(node, nodeAttributes)).flat();if (children.length === 0) {children.push(jsx('text', nodeAttributes, ''));}switch (el.nodeName) {case 'BR':return '\n';case 'P':return jsx('element', { type: 'paragraph' }, children);default:return children;}},

7、绘制图形Shapes

shapes,绘制图形,文档有详细介绍,这里我就不累述

 8、总结

整个功能实现下来还是比较耗时,主要文档都是英文,有些字段描述也不是很清晰,有的需要慢慢调试,上面一些介绍的功能,都是实际开发在使用的;总体来说,还是比较完美实现自定义导出PPT。欢迎大家一起沟通交流!!!

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

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

相关文章

【编码魔法师系列_构建型1.2 】工厂方法模式(Factory Method)

学会设计模式&#xff0c;你就可以像拥有魔法一样&#xff0c;在开发过程中解决一些复杂的问题。设计模式是由经验丰富的开发者们&#xff08;GoF&#xff09;凝聚出来的最佳实践&#xff0c;可以提高代码的可读性、可维护性和可重用性&#xff0c;从而让我们的开发效率更高。通…

设置github的默认分支

设置github的默认分支 更换默认分支默认分支的作用 更换默认分支 之前默认的分支想main, 现在想更换默认的分支 点击main, 可以看到有两个分支: main和gpuVersion, 可以看到这里默认main分支为default 如果想设置gpuVersion作为default,可以点击View all branches, 进入下一个…

测试域: 流量回放-工具篇jvm-sandbox,jvm-sandbox-repeater,gs-rest-service

JVM-Sandbox Jvm-Sandbox-Repeater架构_小小平不平凡的博客-CSDN博客 https://www.cnblogs.com/hong-fithing/p/16222644.html 流量回放框架jvm-sandbox-repeater的实践_做人&#xff0c;最重要的就是开心嘛的博客-CSDN博客 [jvm-sandbox-repeater 学习笔记][入门使用篇] 2…

数据结构 | 树和二叉树

树 树是n&#xff08;n>0&#xff09;个结点的有限集。当n 0时&#xff0c;称为空树。在任意一棵非空树中应满足&#xff1a; 有且仅有一个特定的称为根的结点。当n>1时&#xff0c;其余节点可分为m&#xff08;m>0&#xff09;个互不相交的有限集T1,T2,…,Tm&#…

uni-app 之 去掉顶部导航

uni-app 之 去掉顶部导航 uniapp怎么样去掉顶部导航 uniapp去掉顶部导航的方法&#xff1a; 1、去掉所有导航栏&#xff1b; 2、单一页面去掉顶部导航栏。 image.png uniapp去掉顶部导航的方法&#xff1a; 1、去掉所有导航栏 "globalStyle": {"navigationBar…

Perceptual Compression与Semantic Compression的含义

这是我在读LDMS的学到的 Perceptual Compression 保留人类能够感知的重要信息&#xff0c;例如纹理&#xff0c;局部边缘等 Semantic Compression 保留数据的实际意义&#xff0c;例如图片包含了人物、建筑&#xff0c;人物之间的关系等

活动预告 | 中国数据库联盟(ACDU)中国行第三站定档成都,邀您探讨数据库前沿技术

数据库技术一直是信息时代中不可或缺的核心组成部分&#xff0c;随着信息量的爆炸式增长和数据的多样化&#xff0c;其重要性愈发凸显。作为中国数据库联盟&#xff08;ACDU&#xff09;的品牌活动之一&#xff0c;【ACDU 中国行】在线下汇集数据库领域的行业知名人士&#xff…

uniapp小程序点击按钮直接退出小程序效果demo(整理)

点击按钮直接退出小程序 <navigator target"miniProgram" open-type"exit">退出小程序</navigator>

支撑位和阻力位在Renko和烛台图如何使用?FPmarkets澳福3秒回答

很多投资者都知道&#xff0c;Renko图表和普通日本烛台都会采用相同的交易信号&#xff0c;即支撑位和阻力位。那么支撑位和阻力位在Renko和烛台图如何使用?FPmarkets澳福3秒回答。 这些信号在任何时间框架上都会出现&#xff0c;且在蜡烛图交易中颇受欢迎。对于Renko图表而言…

《DATASET CONDENSATION WITH GRADIENT MATCHING》

本文提出了一种用于数据效率学习的训练集合成技术&#xff0c;称为“数据集凝聚”(Dataset)&#xff0c;它学习将大数据集压缩成一个小的信息合成样本集&#xff0c;用于从头开始训练深度神经网络。我们将这个目标表述为在原始数据和合成数据上训练的深度神经网络权值的梯度之间…

[Linux] 2.Linux开发环境的搭建(Ubuntu)

虚拟机&#xff1a;VMare安装、Ubuntu、VitualBox 真机&#xff1a;公司的研发服务器 Linux虚拟机安装所需文件&#xff1a; 网盘资源&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1WN-tizjHpOgNF0tjbvcZsA?pwd2itd 提取码&#xff1a;2itd 文件解压&#xff…

十四、流式编程(4)

本章概要 终端操作 数组循环集合组合匹配查找信息数字流信息 终端操作 以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说&#xff0c;终端操作&#xff08;Terminal Operations&#xff09;总是我们在流管道中所做的最后一件事。 数组 toArray()&…

火山引擎DataLeap推出两款大模型应用: 对话式检索与开发 打破代码语言屏障

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 自上世50年代&#xff0c;以“计算机”作为代表性象征的信息革命开始&#xff0c;社会对于先进生产力的认知便开始逐步更迭——从信息化&#xff08;通常认为是把企…

Coupang真的好做吗?韩国Coupang入驻流程——站斧浏览器

coupang真的好做吗&#xff1f; Coupang自开放全球注册以来&#xff0c;一直备受跨境电商各平台卖家的关注&#xff0c;那么作为一颗跨境电商的新星&#xff0c;真的值得做吗&#xff1f; 不到一年的关注度遭到如此众多的跨境卖家追捧的平台&#xff0c;火是有他的原因的&…

【办公类-16-06】20230901大班运动场地分配表-斜线排列、5天循环、不跳节日,手动修改节日”(python 排班表系列)

背景需求&#xff1a; 大班组长发来一个“运动排班”的需求表&#xff1a;“就是和去年一样的每个班的运动排班&#xff0c;就因为今年大班变成7个班&#xff0c;删掉一个场地&#xff0c;就要重新做一份&#xff0c;不然我就用去年的那份了&#xff08;8个大班排班&#xff0…

【内网穿透】在Ubuntu搭建Web小游戏网站,并将其发布到公网访问

目录 前言 1. 本地环境服务搭建 2. 局域网测试访问 3. 内网穿透 3.1 ubuntu本地安装cpolar 3.2 创建隧道 3.3 测试公网访问 4. 配置固定二级子域名 4.1 保留一个二级子域名 4.2 配置二级子域名 4.3 测试访问公网固定二级子域名 前言 网&#xff1a;我们通常说的是互…

notepad++配合正则表达式分组模式处理文本转化为sql语句

一、正则分组知识点补充 正则分组和捕获 ()&#xff1a;用于分组和捕获子表达式。 大白话就是()匹配到的数据&#xff0c;通过美元符号加下标可以获取该数据&#xff0c;例如$1、$2, 下标从1开始。 下面的案例就采用该模式处理文本数据 二、使用正则的需求背景 有一份报表…

小米云原生文件存储平台化实践:支撑 AI 训练、大模型、容器平台多项业务

小米作为全球知名的科技巨头公司&#xff0c;已经在数百款产品中广泛应用了 AI 技术&#xff0c;这些产品包括手机、电视、智能音箱、儿童手表和翻译机等。这些 AI 应用主要都是通过小米的深度学习训练平台完成的。 在训练平台的存储方案中&#xff0c;小米曾尝试了多种不同的…

网络安全CTF比赛有哪些事?——《CTF那些事儿》告诉你

目录 前言 一、内容简介 二、读者对象 三、专家推荐 四、全书目录 前言 CTF比赛是快速提升网络安全实战技能的重要途径&#xff0c;已成为各个行业选拔网络安全人才的通用方法。但是&#xff0c;本书作者在从事CTF培训的过程中&#xff0c;发现存在几个突出的问题&#xff1…

ndoe.js、npm相关笔记

1、npm 全局安装 npm config get prefix 获取 npm 全局安装路径如果全局插件不能正常使用&#xff0c;看环境变量是否已经配置。没有配置则把全局安装路径配置到环境变量的path中