在純JaveScript中實現報表匯出:從“PDF”到“JPG”

葡萄城技術團隊發表於2021-11-03

我們在前端報表中完成了各種工作資料的輸入或內容處理之後,需要做什麼?

資料的匯出!

這些資料的常用匯出格式有: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 庫渲染成
  • 通過a標籤的download屬性將儲存為圖片

二、程式碼實戰
簡單起見,本示例不使用任何框架整合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
匯出效果:

到這裡,已經完全解決了本次提到的問題~

後續也會為大家帶來更多有趣或嚴肅的內容。

相關文章