需求:使用superset搭建的BI项目,需要在dashboard模块中,可以编辑所选的图表,并且将html下载为pdf格式的文件。BI页面展示大体如下:
功能分析
1.首先,我们需要一个预览功能,那就点击右上角三个点的按钮,弹出弹窗展示范围内的图表即可,这个实现的话主要就是把图表拿过来的过程,这个具体看代码如何实现;
2.然后我们需要在弹窗对html进行编辑操作,我们看到可操作性的不知图表还有text、table以及input输入框等内容,我们需要一个专业的插件的开完成,于是我们找到了jodit-react插件(因为代码使用了react框架,所以我们选择这个);
3.将修改后的页面保存并且下载为pdf格式,网上百度了一圈,我只找到两个插件,但实现的原理都是一样,我们选择大家基本都在用的插件,html2canvas和jspdf来实现。
实现过程以及遇到的问题
1.实现预览
我们需要先拿到这个需要展示的部分,看到页面上有一个下载图表为image的功能,直接操作了dom元素去拿页面,并且考虑到,使用jodit的时候需要传入的是string格式的html,所以,我们也直接去操作它的dom吧(不推荐,但是如过获取组件数据太麻烦。。);
那么我们就遇到了第一个问题,右边menu菜单,再打开弹窗的时候出现的延迟,预览的画面展示出了menu,这时候我们用了cloneNode,并删掉了对应的节点,所以现在我们展示的就是克隆后的节点html:
然后我们又遇到了canvas实现的图表无法正常显示的问题,我想到的方法就是toDataURL方法拿到url,并替换canvas为img,将url赋值给src属性,下面是具体的方法,我是用递归,因为可能下载的是一整页,就有很多canvas标签的情况,如下:
2.下面我们对预览的内容进行修改
之后也得到了html,这个是克隆并处理过的数据,我们用这个数据直接去进行下载pdf,网上的代码基本如下所示(这个是我拿来测试的):
<!DOCTYPE HTML><html> <head> <title>HTML转PDF测试</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <style type="text/css"> /* Basic styling for root. */ #root { width: 500px; height: 700px; background-color: yellow; } .dragdroppable { position: relative; } .dragdroppable-row { width: 100%; } </style> </head> <body> <button onclick="test()">生成PDF</button> <div id="app" style="height: 500px;border: 1px solid;">app</div> <div class="dragdroppable dragdroppable-row"> <div id="root"> <svg xmlns="http://www.w3.org/2000/svg" version="1.1"> <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" style="fill:none;stroke:black;stroke-width:3" /> </svg> </div> </div> <script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.0.272/jspdf.debug.js"></script> <script> function test() { // 获取DIV元素 var element1 = document.getElementById('root'); var element = element1.cloneNode(true); var w = element.offsetWidth; // 获得该容器的宽 var h = element.offsetHeight; // 获得该容器的高 var offsetTop = element.offsetTop; // 获得该容器到文档顶部的距离 var offsetLeft = element.offsetLeft; // 获得该容器到文档最左的距离 var canvas = document.createElement("canvas"); var abs = 0; var win_i = document.body.clientWidth; // 获得当前可视窗口的宽度(不包含滚动条) var win_o = window.innerWidth; // 获得当前窗口的宽度(包含滚动条) if (win_o > win_i) { abs = (win_o - win_i) / 2; // 获得滚动条长度的一半 } canvas.width = w * 2; // 将画布宽&&高放大两倍 canvas.height = h * 2; var context = canvas.getContext("2d"); context.scale(2, 2); context.translate(-offsetLeft - abs, -offsetTop); // 这里默认横向没有滚动条的情况,因为offset.left(),有无滚动条的时候存在差值,因此 // translate的时候,要把这个差值去掉 document.body.appendChild(element); html2canvas(element, { allowTaint: true, scale: 2, // 提升画面质量,但是会增加文件大小 }).then(function (canvas) { document.body.removeChild(element); var contentWidth = canvas.width; var contentHeight = canvas.height; //一页pdf显示html页面生成的canvas高度; var pageHeight = contentWidth / 592.28 * 841.89; //未生成pdf的html页面高度 var leftHeight = contentHeight; //页面偏移 var position = 0; //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高 var imgWidth = 595.28; var imgHeight = 592.28 / contentWidth * contentHeight; var pageData = canvas.toDataURL('image/jpeg', 1.0); var pdf = new jsPDF('', 'pt', 'a4'); //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89) //当内容未超过pdf一页显示的范围,无需分页 if (leftHeight < pageHeight) { pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight); } else { // 分页 while (leftHeight > 0) { pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight) leftHeight -= pageHeight; position -= 841.89; //避免添加空白页 if (leftHeight > 0) { pdf.addPage(); } } } pdf.save(`${name}.pdf`); }); } </script> </body></html>
结果是可以正常展示的,下面我们运用起来。
可以正常下载,但是如果按照上面的配置,那么下载出的pdf内容,就可能无法显示,原因是因为它继承了图表在页面上的位置,pdf显示为空白,由于已经提前测试了插件,可以排除插件的问题,并且我去掉了预览功能直接下载进行测试,结果也不正常,这就证明,superset的图表和插件有冲突。
3.尝试直接操作canvas图表的样式
1)尝试着给它添加绝对定位的样式;
2)尝试在html2canvas上添加高度;
3)最后又想到了,替换canvas变成替换为img,实现的代码如下所示:
let element = document.getElementById('copyDom'); domToImage .toJpeg(element, { quality: 0.95, bgcolor: '#fff', // Mapbox controls are loaded from different origin, causing CORS error // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#exceptions filter: (node) => node.className !== 'mapboxgl-control-container', ...{}, }) .then(dataUrl => { var imgWidth = 595.28; var imgHeight = 592.28 / element.offsetWidth * element.offsetHeight; var pageHeight = element.offsetWidth / 592.28 * 841.89; var leftHeight = element.offsetHeight; var pdf = new jsPDF('', 'pt', _type ? [imgWidth, imgHeight] : 'a4'); var p = 0; pdf.addImage(dataUrl, 'JPEG', 0, 0, imgWidth, imgHeight); pdf.save(`${name}.pdf`); document.body.removeChild(element); })
实现的原理都是一样的,方式变了而已,这个方法可以实现下载pdf并且样式正常,我没有分页,分页的代码会出现内容被分割,因为这些图表没有规律,也不是自己写的,没法具体到每一个观察高度,中间还出现了克隆的html没法使用的问题,我想到它可能不在整个页面中,于是使用appendChild将html加载body的后面,然后再下载完成之后,remove掉,成功解决问题。。
总结问题及解决方法
1.canvas画出的图表无法显示的问题
这个可以使用方法将canvas转为img标签;
2.superset的dashboard图表无法使用html2canvas正常显示
插件没有问题,但是却和superset产生了冲突,换一种方式,转为图片再下载;
3.克隆html时
要注意如果无法显示或者操作,就将html写在body上,然后记得remove,就可以正常显示和操作属性了;