摘要:筆者最近做了一個(gè)在構(gòu)建打印模板的需求,從中學(xué)習(xí)到一些有價(jià)值的東西,特地記錄一篇文章分享。此時(shí),瀏覽器會(huì)彈出打印預(yù)覽的窗口,通過(guò)頁(yè)面生成了用于打印預(yù)覽。最后,聯(lián)想到也可用于實(shí)現(xiàn)打印模板需求,筆者以和模板為例進(jìn)行了一個(gè)簡(jiǎn)單的實(shí)踐。
筆者最近做了一個(gè)在 Web 構(gòu)建打印模板的需求,從中學(xué)習(xí)到一些有價(jià)值的東西,特地記錄一篇文章分享。概述
本文首先描述筆者所處的項(xiàng)目組的 Web 打印項(xiàng)目的需求背景,然后描述筆者在實(shí)踐 Web 打印項(xiàng)目的過(guò)程中遇到了諸多問(wèn)題,闡述 Web 打印的問(wèn)題解決思路,最后給出了另外一種 Web 打印的需求解決方案,即使用Headless browser生成圖片并打印的方案。預(yù)計(jì)閱讀時(shí)間5 ~ 10分鐘。
本文主要分下面幾個(gè)方面:
Web 構(gòu)建打印模板需求
基本概念
打印設(shè)備接口
頁(yè)面模型
引入打印樣式
處理 Web打印 分頁(yè)問(wèn)題
去除瀏覽器默認(rèn)的頁(yè)眉頁(yè)底
構(gòu)建自定義的頁(yè)眉頁(yè)底
使用 Headless browser 生成圖片的解決方案
Web 構(gòu)建打印模板需求產(chǎn)品經(jīng)理小姐姐近期給筆者寫了這樣一個(gè)需求:
實(shí)現(xiàn)一個(gè)打印報(bào)告的模板頁(yè)面,瀏覽器或客戶端調(diào)用打印設(shè)備的接口打印出對(duì)應(yīng)的報(bào)告。
對(duì)應(yīng)報(bào)告支持報(bào)告模板配置,模板分幾種,例如免費(fèi)玩家用極簡(jiǎn)版、低保玩家用基本版、充值玩家用高級(jí)版、土豪玩家用頂配版。沒(méi)錯(cuò),充值才能變得更強(qiáng)。
需要實(shí)現(xiàn)分頁(yè)功能,支持把對(duì)應(yīng)的內(nèi)容展示到對(duì)應(yīng)的頁(yè)。例如:內(nèi)容A為基本信息,需要展示到第一頁(yè),低保玩家享受內(nèi)容B,展示到第二頁(yè)...土豪玩家享受所有的功能,展示到第n頁(yè)。
展示產(chǎn)品配置的對(duì)應(yīng)的頁(yè)眉、頁(yè)底。
于是,為了解決上述需求,筆者大概寫了這樣的一個(gè)模板,如下所示:
基本概念 打印設(shè)備接口我是第一頁(yè)1我是第一頁(yè)2我是第一頁(yè)3我是第二頁(yè)1我是第二頁(yè)2我是第二頁(yè)3我是第三頁(yè)1我是第三頁(yè)2我是第三頁(yè)3我是第四頁(yè)1我是第四頁(yè)2我是第四頁(yè)3
瀏覽器打印是一個(gè)很成熟的應(yīng)用,最簡(jiǎn)單的打印直接調(diào)用window.print()或者是調(diào)用document.execCommand("print")。此時(shí),瀏覽器會(huì)彈出打印預(yù)覽的窗口,通過(guò)頁(yè)面生成了pdf用于打印預(yù)覽。如圖所示,展示了谷歌首頁(yè)的打印預(yù)覽:
頁(yè)面模型和 CSS 盒子模型一樣,頁(yè)面盒子模型由外邊距 (margin)、邊框 (border)、內(nèi)邊距 (padding) 和 內(nèi)容區(qū)域 (content area) 構(gòu)成:
有以下兩點(diǎn)可以注意:
打印頁(yè)面時(shí),只打印出頁(yè)面的內(nèi)容區(qū)域
頁(yè)面默認(rèn)有頁(yè)眉頁(yè)腳信息,展現(xiàn)到頁(yè)面外邊距范圍
默認(rèn)情況下,頁(yè)面是從左到右、從上到下展示,如果需要更改打印設(shè)備的方向,可以通過(guò)設(shè)置根元素的 direction 和 writing-mode 屬性來(lái)改變頁(yè)面方向。
引入打印樣式可以通過(guò)三種方式引入打印樣式:
使用 @media print:
@media print { body { background-color:#FFFFFF; margin: 0mm; /* this affects the margin on the content before sending to printer */ } // ... }
內(nèi)聯(lián)樣式使用media屬性:
在 CSS 中使用 @import:
@import url("print.css") print;
HTML 中使用的link標(biāo)簽添加media屬性:
處理 Web打印 分頁(yè)問(wèn)題項(xiàng)目需求中首先遇到的問(wèn)題是需要處理 Web打印 分頁(yè)問(wèn)題。即使該部分未占滿一頁(yè)紙的高度,也需要進(jìn)行手動(dòng)的分頁(yè)。起初,我通過(guò)計(jì)算頁(yè)面每個(gè)部分的高度,在對(duì)應(yīng)頁(yè)面部分的節(jié)點(diǎn)的高度下方預(yù)留一部分的外邊距來(lái)實(shí)現(xiàn),如下代碼所示,通過(guò)查資料得知 A4紙的寬高比為 297 : 210 ,除去頁(yè)面外邊距(左右各 20mm )來(lái)算得每一部分需要預(yù)留的高度:
const A4_HEIGHT_WIDTH_RATE = 297 / (210 - 2 * 13); // 打印區(qū)域長(zhǎng)寬比:(A4紙高)比(A4紙寬減去左右側(cè)20mm的邊距) const PAGE_WIDTH = 680; // 頁(yè)面寬度(像素值) const PAGE_HEIGHT = PAGE_WIDTH * A4_HEIGHT_WIDTH_RATE; // 頁(yè)面高度 const $page1El = document.querySelector(".page1"); const page1Height = parseInt($page1El.clientHeight); // page1的高度是多少像素 const pageNum = Math.ceil(page1Height / PAGE_HEIGHT); // page1需要占多少頁(yè),超過(guò)1頁(yè)的高度,就需要占2頁(yè),因此向上取整 const marginBottom = pageNum * PAGE_HEIGHT - page1Height; // 需要預(yù)留多少外邊距 $page1El.style.marginBottom = `${marginBottom}px`;
但是,其實(shí) CSS 早就支持了打印設(shè)備里的分頁(yè)問(wèn)題了,可以通過(guò)設(shè)置break-after: page; 或 page-break-after: always;實(shí)現(xiàn)在打印設(shè)備的分頁(yè):
.page1 { break-after: page; page-break-after: always; } // ...去除瀏覽器默認(rèn)的頁(yè)眉頁(yè)底
實(shí)現(xiàn)分頁(yè)的效果后,發(fā)現(xiàn)頁(yè)面打印會(huì)在頁(yè)底出現(xiàn)當(dāng)前頁(yè)面的 url :
頁(yè)面默認(rèn)有頁(yè)眉頁(yè)腳信息,展現(xiàn)到頁(yè)面外邊距范圍,通過(guò)去除 頁(yè)面模型 的外邊距,使得內(nèi)容不會(huì)延伸到頁(yè)面的邊緣,再通過(guò)設(shè)置 body 元素的 margin 來(lái)保證 A4 紙打印出來(lái)的頁(yè)面帶有外邊距:
@media print { @page { margin: 0; } body { margin: 2cm; } }
現(xiàn)在打印出來(lái)的頁(yè)面不再具有默認(rèn)的頁(yè)底:
構(gòu)建自定義的頁(yè)眉頁(yè)底通過(guò)將對(duì)應(yīng)的頁(yè)眉、頁(yè)底元素的 position 設(shè)置為 fixed 可以固定對(duì)應(yīng)節(jié)點(diǎn)到頁(yè)面的任意一部分,它們也將在每個(gè)打印頁(yè)面上重復(fù)。
.header { position: fixed; top: 0; } .footer { position: fixed; bottom: 0; }使用 Headless browser 生成圖片的解決方案
上面說(shuō)了那么多,都是在前端實(shí)現(xiàn)的 Web 打印的解決方案,但實(shí)際上,如果可以在后臺(tái)直接通過(guò) Web 頁(yè)面,預(yù)先保存好的頁(yè)面模板,通過(guò)拉取后臺(tái)數(shù)據(jù),并運(yùn)行Headless browser生成一張截圖,通過(guò)打印截圖就可以解決這樣的問(wèn)題了,下面以 phantomjs 配合 pug為例,展示筆者使用Headless browser生成圖片的簡(jiǎn)單解決方案:
// 針對(duì)鏈接的截圖服務(wù) // 返回phantom實(shí)例的promise對(duì)象,為了獲取對(duì)應(yīng)的base64編碼 function captureByUrl(url, data) { let instance; let page; const destroyInstance = () => { // 關(guān)閉頁(yè)面 page.close(); // 退出實(shí)例 instance.exit(); }; return phantom.create() // 首先,創(chuàng)建phantom實(shí)例 .then((_instance) => { instance = _instance; return instance.createPage(); }) .then((_page) => { page = _page; if (data.width && data.height) { // 設(shè)置phantom截圖頁(yè)面的寬高值 page.property("viewportSize", { width: data.width, height: data.height }); } page.setting("userAgent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36"); return page.open(url); }) .then(() => { return page.renderBase64("PNG"); // 渲染對(duì)應(yīng)圖片,拿到base64字符串 }) .then((image) => { destroyInstance(); // 銷毀phantom實(shí)例 return image; }, (error) => { destroyInstance(); // 銷毀phantom實(shí)例 throw error; }); }
如上代碼所示,使用 Headless browser 打開(kāi)一個(gè)鏈接,通過(guò)renderBase64將對(duì)應(yīng)頁(yè)面的預(yù)覽圖截圖生成base64字符串。
對(duì)應(yīng)的,在服務(wù)端,可以通過(guò)讀取預(yù)先寫好的pug模板,傳入對(duì)應(yīng)數(shù)據(jù)生成對(duì)應(yīng)頁(yè)面預(yù)覽圖,再通過(guò) Headless browser 生成截圖保存到本地,即可實(shí)現(xiàn) Web 打印在服務(wù)端的解決方案,如下代碼所示,為服務(wù)端讀取模板,并保存圖片的部分代碼:
// 針對(duì)模板和數(shù)據(jù)的截圖服務(wù) function captureByTemplate(template, data) { const content = pug.compile(template)(Object.assign({ URL_PREFIX, }, data)); const contentInBase64 = new Buffer(content).toString("base64"); const url = `data:text/html;charset=utf8;base64,${contentInBase64}`; return captureByUrl(url, data); } captureByTemplate(fs.readFileSync("./print.pug", "utf-8"), data) .then(base64Data => { fs.writeFile("out.png", base64Data, "base64", function(err) { console.error(err); }); }) .catch(err => { console.error(err); });小結(jié)
本文為筆者在實(shí)踐 Web 打印相關(guān)項(xiàng)目的項(xiàng)目總結(jié),首先描述了 Web 打印項(xiàng)目一般需求,然后在打印設(shè)備下,頁(yè)面模型的展現(xiàn)形式;然后描述了筆者在實(shí)踐過(guò)程中遇到的一些常見(jiàn)問(wèn)題,給出一些通用性的解決方案。最后,聯(lián)想到 Headless browser 也可用于實(shí)現(xiàn)打印模板需求,筆者以 phantomjs 和 pug 模板為例進(jìn)行了一個(gè)簡(jiǎn)單的實(shí)踐。
最后,筆者近期建立了一個(gè)技術(shù)交流群,歡迎大家在群里討論技術(shù),另外,幫團(tuán)隊(duì)打個(gè)廣告,醫(yī)療健康事業(yè)部還在招聘前端、后臺(tái)、數(shù)據(jù)工程師,有想加入騰訊醫(yī)療健康的朋友可以加群或者直接發(fā)送簡(jiǎn)歷到 counterxing@tencent.com 。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/102699.html
摘要:對(duì)于通常的特別是那些具備并行計(jì)算多線程背景知識(shí)的來(lái)講,的異步處理著實(shí)稱得上詭異。而這個(gè)詭異從結(jié)果上講,是由的單線程這個(gè)特性所導(dǎo)致的。的特性之一是單線程,也即是從頭到尾,都在同一根線程下運(yùn)行。而這兩者的不同,便在于單線程和多線程上。 對(duì)于通常的developer(特別是那些具備并行計(jì)算/多線程背景知識(shí)的developer)來(lái)講,js的異步處理著實(shí)稱得上詭異。而這個(gè)詭異從結(jié)果上講,是由js...
摘要:通過(guò)方法提交一個(gè)任務(wù),并且通過(guò)對(duì)象來(lái)獲得結(jié)果。對(duì)象可以取消運(yùn)行任務(wù),設(shè)置等待時(shí)間,獲取任務(wù)狀態(tài),最終獲得任務(wù)結(jié)果。類似于,但是并不會(huì)有返回結(jié)果和異常信息。由兩個(gè)階段所觸發(fā)的,沒(méi)有保證的結(jié)果用于依賴階段的計(jì)算。 本系列關(guān)于concurrent的代碼示例,是被我分割成了小部分,在系列文章結(jié)束以后,我會(huì)將較為完整的代碼上傳,在寫的過(guò)程中我會(huì)參考官方API以及其他牛人的見(jiàn)解,大家有不同的看法可...
閱讀 2880·2023-04-25 22:51
閱讀 2261·2021-10-11 10:58
閱讀 3383·2019-08-30 10:49
閱讀 1945·2019-08-29 17:09
閱讀 3192·2019-08-29 10:55
閱讀 905·2019-08-26 10:34
閱讀 3625·2019-08-23 17:54
閱讀 1047·2019-08-23 16:06