V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
GrapeCityChina
V2EX  ›  推广

在纯 JaveScript 中实现报表导出:从“PDF”到“JPG”

  •  
  •   GrapeCityChina · 2021-11-03 11:20:39 +08:00 · 591 次点击
    这是一个创建于 1120 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们在前端报表中完成了各种工作数据的输入或内容处理之后,需要做什么?

    数据的导出!

    这些数据的常用导出格式有:PDF 、Excel 、HTML 和图片几大类型。

    但总有一些实际应用场景,需要的不仅仅是将现有内容导出,还需要我们对一些内容的格式进行转化。

    就在前几天,葡萄刚上班,就看到客户发来下图,发生了以下对话

    -葡萄,这一页可以导出吗?

    -当然可以,PDF 、Excel 、HTML 都可以。

    -可是我想把这一页导出图片。

    这时候问题就出现了,在我们的前端电子报表中并没有默认图片保存的格式,那这时候我们如何用已有功能进一步扩展,来实现这个功能呢?

    一、确定实现思路

    巧妇难为无米之炊,首先我们先整理一下手中素材。

    通过阅读文档了解我们可以自定义添加按钮:

    同时我们还可以在 action 属性中,给按钮定义点击后触发的事件:

    顺着这个思路,我们可以在工具栏添加一个导出按钮,将按钮的动作设置为"点击这个按钮时实现导出图片的功能"。ARJS 本身支持导出 PDF ,并且也提供了直接调用导出 PDF 的接口:export,所以我们可以先通过接口导出 PDF ,然后再将 PDF 转换为图片,最终实现导出图片的功能。

    这下子,我们的最终问题就变成了是如何 ** PDF **转换为图片并导出

    PDF.js 是一款使用 HTML5 Canvas 安全地渲染 PDF 文件以及遵从网页标准的网页浏览器渲染 PDF 文件的 JavaScript 库。我们可以通过 PDF.js 库将导出的 PDF 通过 Canvas 在网页上渲染出来,然后通过 Canvas 的 toDataURL 方法返回一个包含图片展示的 data URL 。拿到这个 URL 就好办了,可以利用 a 标签的 download 属性直接对其进行下载,最终实现在 ARJS 中导出图片的功能。

    总结,整体实现思路如下:

    • 添加导出图片按钮
    • 实现导出 PDF
    • 将 PDF 通过 PDF.js 库渲染成 <canvas>
    • 通过 a 标签的 download 属性将<canvas>保存为图片

    二、代码实战 简单起见,本示例不使用任何框架集成 ARJS ,选择在纯 JaveScript 中集成报表,大家可以阅读相关文档:在纯JavaScript 项目中集成报表 Viewer。另外,为了在 document 中插入 canvas 元素,事先可以建立一个 div 元素,以便之后在该节点下插入 canvas 元素;同时为了界面中只有报表查看器,可以隐藏该 div 。最终的页面结构如下:

    	<body>
    	        <div id="viewer-host"></div>
    	        <div id="imgDiv" style="display: none"></div>
    	</body>
    
    

    添加导出图片按钮

    1.	    let exportImageButton = {
    2.	        key: '$exportImage',
    3.	        icon:{
    4.	            type: 'svg',
    5.	            content:'<svg role="img" xmlns="http://www.w3.org/2000/svg" width="21px" height="21px" viewBox="0 0 24 24" aria-labelledby="imageIconTitle" stroke="#205F78" stroke-width="2.2857142857142856" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#205F78"> <title id="imageIconTitle">Image</title> <rect width="18" height="18" x="3" y="3"/> <path stroke-linecap="round" d="M3 14l4-4 11 11"/> <circle cx="13.5" cy="7.5" r="2.5"/> <path stroke-linecap="round" d="M13.5 16.5L21 9"/> </svg>',
    6.	            size: 'small'
    7.	        },
    8.	        enabled: true,
    9.	        title:'导出图片',
    10.	        action: function() {
    11.	            //定义导出图片按钮点击事件
    12.	        }
    13.	    };
    14.	    viewer.toolbar.addItem(exportImageButton);
    
    

    接口文档:addItem。 (提示:以上在 icon 的 content 的属性中,使用了一个 svg ,这个示例代码中的 svg 来自网站:ikonate 。如果大家有需要可自行下载,如果作为商用需要注意版权 )

    以上代码添加之后,我们就可以在报表预览界面的工具栏看到这样一个按钮:

    实现导出 PDF

    在 exportImageButton 的 action 中定义一个 exportImage 方法,在这个方法中首先实现导出 PDF ,导出的结果包含一个 PDF 文件的 blob 对象,大家可自行打印出来看一下导出结果:

    	    function exportImage() {
    	        const settings = { title: 'Active Reports JS' };
    	        viewer.export('PDF', settings).then((result) =>{
    	                //这个 result 包含一个所导出 PDF 的 blob 对象
    	                console.log(result);
    	        });
    	    }
    

    将 PDF 通过 PDF.js 库渲染成 canvas

    首先我们需要去PDF.js 官网下载相关文件引入到项目中,我这里的示例通过 cdn 的方式引入:

    1.	  <script src="https://cdnjs.cloudflare.com/ajax/libs/PDF.js/2.10.377/PDF.min.js"></script>
    

    引入之后,我们就可以对上一步得到的 blob 对象进行操作,将 PDF 渲染成&lt;canvas&gt;:

    	    function pageToCanvasObj(page) {
    	        const viewport = page.getViewport({scale: 1});
    	        const canvas = document.createElement('canvas');
    	        const context = canvas.getContext('2d');
    	        canvas.height = viewport.height;
    	        canvas.width = viewport.width;
    	        canvas.style.width = "100%";
    	        canvas.style.height = "100%";
    	        imgDiv.append(canvas);
    	        return {
    	            canvas,
    	            renderContext: {
    	                canvasContext: context,
    	                viewport
    	            }
    	        }
    	    }
    	
    	    function exportImage() {
    	        const settings = { title: 'Active Reports JS' };
    	        viewer.export('PDF', settings).then((result) =>{
    	            //核心代码
    	           //通过 FileReader 的接口将 blob 转换为 ArrayBuffer
    	            const fileReader = new FileReader();
    	            fileReader.readAsArrayBuffer(result.data);
    	            fileReader.onload = function() {
    	                //为了读写 ArrayBuffer 对象,建立 typedArray 视图
    	                const typedArrayResult = new Uint8Array(fileReader.result);
    	                //PDF.js 读取文档后渲染 canvas
    	                PDFjsLib.getDocument(typedArrayResult).promise.then(function(PDF) {
    	                    if (PDF) {
    	                        const pageNum = PDF.numPages;
    	                        for (let i = 1; i <= pageNum; i++) {
    	                            PDF.getPage(i).then((page) => {
    	                                //创建 canvas ,并且返回相关数据
    	                                const canvasObj = pageToCanvasObj(page);
    	                                //<canvas>渲染
    	                                page.render(canvasObj.renderContext).promise.then(() => {
    	                                   //通过 canvas 对象的 toDataURL 得到图片链接
    	                                    const imgUrl = canvasObj.canvas.toDataURL();
    	                                })
    	                            })
    	                        }
    	                    }
    	                },(error) => {
    	                    alert(error);
    	                });
    	            };
    	        });
    	    }
    
    

    通过 a 标签的 download 属性将 canvas 保存为图片 将上一步得到的 imgURL 通过 a 标签下载:

       function saveImage(index, url) {
    	        const link = document.createElement("a");
    	        link.href = url;
    	        link.download = `image${index}`;
    	        link.click();
    	        link.remove();
    	    }
    
    

    这下就实现了在在前端报表中完整将报表内容作为图片导出。在此附上示例完整 demo 代码文件:

    https://gcdn.grapecity.com.cn/forum.php?mod=attachment&aid=MTY0Njg4fGNlMzM5MTkwfDE2MzM2NjU4MzB8NjI2NzZ8MTMyNDM3 导出效果:

    到这里,已经完全解决了本次提到的问题~

    后续也会为大家带来更多有趣或严肃的内容。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1019 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 20:18 · PVG 04:18 · LAX 12:18 · JFK 15:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.