背景介紹
某老人院資訊管理系統專案,甲方要求將財務模組的各種報表匯出為PDF文件,方便列印。
之前的解決方案,是將報表生成專門的列印 HTML 頁面,然後按 Ctrl+P 呼叫瀏覽器本身列印功能去列印。 這種方式存在的問題是不同解析度的顯示器,頁面效果不一,需要專門設定列印尺寸,使用起來不夠方便,功能上線後,一直遭到甲方吐槽...
輪子工具選擇
目標很明確明確,將 HTML 內容匯出為PDF。 時間有限,先找輪子,一通谷歌後選定了前端工具 jspdf。具體使用方式比較簡單,參考下列兩個連結:
解決方案解析
先上程式碼:
html2canvas(document.body, {
onrendered:function(canvas) {
// 要輸出的 PDF 每頁的寬高尺寸,單位是 pt
let pageWidth = 841.89
let pageHeight = 592.28
// 要列印內容,轉換成 canvas 圖片後的寬高尺寸
let contentWidth = canvas.width*3/4
let contentHeight = canvas.height*3/4
// 將要列印內容的圖片,等比例縮放至寬度等於輸出時 PDF 每頁的寬度,此時的圖片寬
let imgWidth = pageWidth
// 將要列印內容的圖片,等比例縮放至寬度等於輸出時 PDF 每頁的寬度,此時的圖片高
let imgHeight = pageWidth / contentWidth * contentHeight
// 起始內容擷取位置
let position = 0
// 剩餘未列印內容的高度
let leftHeight = imgHeight
// 獲取列印內容 canvas 圖片元素
let pageData = canvas.toDataURL('image/jpeg', 1.0)
// 初始化 pdf 容器,三個引數分別是:紙張方向(填'',則是橫向)、列印單位、紙張尺寸
let PDF = new JsPDF('landscape', 'pt', 'a4')
// 迴圈擷取列印內容並新增進容器
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 -= pageHeight
if (leftHeight > 0) {
PDF.addPage()
}
}
}
// 將容器中的內容輸出為 PDF 文件
pdf.save('content.pdf');
}
})
複製程式碼
匯出 pdf 的函式我參考了這條github連結,做了部分修改。函式邏輯比較簡單,不做過多解釋。主要提兩點:
- 修復了一個小bug,原函式忽略了單位轉換問題(px 要轉 pt),存在匯出的 PDF 最後會有空白頁。
- 原函式中 leftHeight 用的是 contentHeight,也就是 canvas 圖片的未縮放換算前的高。這就導致 pageHeight 需要再換算才能得到,這增加了函式邏輯複雜度。其實 leftHeight 可以設為 imgHeight,即縮放換算後的高,而pageHeight 就設為 PDF 單頁的高,這樣程式碼邏輯更清晰。
這函式核心邏輯就三步:
- 獲取要列印內容區域的寬高,並等比縮放直至其寬度等於輸出 PDF 的頁面的寬度,以此獲得縮放後的列印內容圖片寬高(imgWidth, imageHeight)
- 按單頁 PDF 的寬高 (pageWidth, pageHeight),迴圈擷取縮放後的列印內容圖片,並將每次擷取的內容新增至 PDF 物件容器。(每擷取一次,就是一頁 pdf)
- 將 PDF 物件容器中的內容,輸出為 PDF 文件。
問題與補救思路
實踐中遇到的問題是豎直方向上圖片被隨機截斷,如下圖示:
針對這個專案的業務場景,我採取的補救方案是“設定列印內容高度”。 具體思路如下:
- 確定輸出單頁紙張的尺寸比例
例如: A4紙寬高比 = 841.89 / 592.28 (橫向);- 保持比例不變,通過簡單換算確定單個列印的頁面寬高
例如:頁面寬 1920px ,高 1360px;- 通過 CSS,精確控制列印頁面中各元素的高度,使得超出單頁高度的內容,合理過度。
例如:我想每頁列印表格不超過 34 行,那麼單行高度就應該設定為 1360/34 = 40px;
第一頁由於有標題、表頭等元素,所以只打 30 行,標題和表頭合計高度為 160px (這個可根據實際需求,只需保證標題、表頭的高度都是單行高的整數倍即可)
最終成功解決豎直方向不規則截斷問題。
總結
做的好的三個點:
- 快速尋找輪子,思路正確
- 知其所以然的態度,促使深入思考實現原理,因此才有可能優化解決方案,使得最終交付的結果更優質
- 實在想不到...
不足的兩點:
- 整個任務完成花費了一個工作日,效率太低。在專案本地部署環節浪費很多時間(主要因為專案本身技術棧選型不好,也沒有相應的部署說明文件)。另外,一開始未看懂函式就開始瞎改,浪費了不少時間。
- 功能沒有合理封裝,最終手動複製黏貼到所有頁面,浪費大量時間。
進一步研究和思考:
- 研究 jspdf 原始碼,更進一步瞭解匯出 PDF 的實現原理
- 研究通用列印頁面方案,看能否將匯出 PDF 文件功能封裝為 vue 元件(先針對列印內容為表格的)