前端自定义导出PPT

2023-09-19 18:07:16
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 nodes
      switch (el.nodeName) {
        case 'STRONG':
          nodeAttributes.bold = true;
      }
      // font color
      if (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。欢迎大家一起沟通交流!!!

更多推荐

MVVM 模式、Vue 双向绑定原理

MVVM模式是什么传统的MVC指的是,用户操作会请求服务端路由,路由会调用对应的控制器来处理,控制器会获取数据。将结果返回给前端,页面重新渲染MVVM(Model-View-ViewModel)是一种软件架构模式,用于实现界面(UI)和业务逻辑的分离。他的设计目标是将界面的开发与后端的业务逻辑分离,使得代码易于理解、维

为什么要选择Spring cloud Sentinel

为什么要选择SpringcloudSentinel🍎对比Hystrix🍂雪崩问题及解决方案🍂雪崩问题🍂.超时处理🍂仓壁模式🍂断路器🍂限流🍂总结🍎对比Hystrix在SpringCloud当中支持多种服务保护技术:NetfixHystrixSentinelResilience4J早期比较流行的是Hyst

线性回归网络

李沐大神的《动手学深度学习》,是我入门机器学习的首课,因此在这里记录一下学习的过程。线性回归的从零开始实现线性回归是理解机器学习的基础,它经常用来表示输入和输出之间的关系。线性回归基于几个简单的假设:首先,假设自变量X和因变量y之间的关系是线性的,即y可以表示为X中元素的加权和,这里通常允许包含观测值的一些噪声。下面基

行业报告:视频直播美颜sdk对互联网直播产业的影响与前景

随着互联网直播产业的不断崛起,直播内容的质量和用户体验已成为成功的关键因素之一。本篇报告将深入研究视频直播美颜sdk对互联网直播产业的影响,并探讨其未来的前景。第一章:视频直播美颜sdk的基本概念1.1什么是视频直播美颜SDK?视频直播美颜sdk是一种软件工具包,旨在为互联网直播平台和应用程序提供实时的美颜和图像增强功

笔试面试相关记录(4)

(1)实现防火墙的主流技术有哪些?实施防火墙主要采用哪些技术-服务器-亿速云(yisu.com)(2)chararr[][2]={'a','b','c','d'};printf("%d",*(arr+1));输出的是谁的地址?字符c测试代码如下chararr[][2]={'a','b','c','d'};printf(

java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

Java版工程项目管理系统SpringCloud+SpringBoot+Mybatis+Vue+ElementUI+前后端分离功能清单如下:首页工作台:待办工作、消息通知、预警信息,点击可进入相应的列表项目进度图表:选择(总体或单个)项目显示1、项目进度图表2、项目信息施工地图:1、展示当前角色权限下能看到的施工地图(

pom的配置策略

dependencyManagement和dependencies区别和联系参考:https://blog.csdn.net/Sunshineoe/article/details/121083505<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://mav

剔除数据中的异常值(python实现)

目录一、3σ原则二、箱线图发现异常值三、boxcox数据变换一、3σ原则该准则仅局限于对正态或近似正态分布的样本数据处理,此外,当测量次数少的情形用准则剔除粗大误差是不够可靠的。异常值是指样本中的个别值,其数值明显偏离其余的观测值。异常值也称离群点,异常值的分析也称为离群点的分析。在进行机器学习过程中,需要对数据集进行

Python进阶学习----一闭三器

目录​编辑前言一.三器1.迭代器(Iterator)1.1什么是可迭代对象1.2什么是迭代器1.3案例演示:以下是一个简单的迭代器示例,遍历一个列表并打印每个元素:1.4迭代器总结2.生成器(Generator)3.装饰器(Decorator)二.一闭4.闭包(Closure)总结:前言Python是一种功能强大而灵活

C2基础设施威胁情报对抗策略

威胁情报是指在信息安全和安全防御领域,收集、分析和解释与潜在威胁相关的信息,以便预先发现并评估可能对组织资产造成损害的潜在威胁,是一种多维度、综合性的方法,其通过信息的收集、分析和研判,帮助组织了解可能对其安全构成威胁的因素。这种方法不仅仅着重于技术层面,还包括了社会、心理、政治等多个维度,以此更好地应对不断变化和复杂

软件项目管理【UML-类图】

前言UML图有很多种,但是并非必须掌握所有的UML图,才能完整系统分析和设计工作。一般说来,在UML图中,只要掌握类图、用例图、时序图的使用,就能完成大部分的工作。也就是说,掌握UML的20%,就能做80%的事情。对于程序员来说,最频繁使用的莫过于类图。目录前言一、什么是类图二、类图中类表示法三、类图中具体类、抽象、接

热文推荐