一镰惦、 需求期望
根據(jù)需求生成指定html結(jié)構(gòu)外盯,通過(guò)web端調(diào)用打印功能將其或者指定區(qū)域打印出來(lái)
二舵匾、 打印方式
1. 預(yù)覽打印
??點(diǎn)擊打印按鈕以后飒炎,出現(xiàn)瀏覽器自帶的打印預(yù)覽設(shè)置面板,點(diǎn)擊確認(rèn)臀叙,進(jìn)行頁(yè)面打印
2. 直接打勇运(靜默打印)
??無(wú)預(yù)覽頁(yè)面展示劝萤,直接調(diào)用打印機(jī)渊涝,打印相應(yīng)內(nèi)容
??注:預(yù)覽打印方式在交互方式上不支持批量打印
三、 預(yù)覽打印方式—實(shí)現(xiàn)方案
1. 基于Dom結(jié)構(gòu)預(yù)覽打印
??a) 整個(gè)頁(yè)面全部打印
????i. 原理:調(diào)用window.print()
????ii. 優(yōu)點(diǎn):
??????1. 無(wú)兼容性問(wèn)題,展示效果完美跨释,頁(yè)面整體打印
????iii. 缺點(diǎn):
??????1. 無(wú)法指定區(qū)域打印胸私,會(huì)打印當(dāng)前全頁(yè)面內(nèi)容
??b) 預(yù)覽打印指定區(qū)域
????i. 當(dāng)前窗口直接打印指定區(qū)域
??????1. 原理:
代碼示例:
//(1)首先獲得元素的html內(nèi)容(這里建議如果有樣式最好是用內(nèi)聯(lián)樣式的方式)
var newstr = document.getElementById(myDiv).innerHTML; //得到需要打印的元素HTML
//(2)保存當(dāng)前頁(yè)面的整個(gè)html,因?yàn)閣indow.print()打印操作是打印當(dāng)前頁(yè)的所有內(nèi)容鳖谈,所以先將當(dāng)前頁(yè)面保存起來(lái)岁疼,之后便于恢復(fù)。
var oldstr = document.body.innerHTML; //保存當(dāng)前頁(yè)面的HTML
//(3)把當(dāng)前頁(yè)面替換為打印內(nèi)容HTML
document.body.innerHTML = newstr;
//(4)執(zhí)行打印操作
window.print();
//(5)還原當(dāng)前頁(yè)面
document.body.innerHTML = oldstr;
},
??????2. 缺點(diǎn):
????????a) 實(shí)現(xiàn)方式的特殊性缆娃,會(huì)導(dǎo)致當(dāng)前頁(yè)面狀態(tài)重置捷绒,用戶體驗(yàn)不好
????ii. 打開(kāi)新窗口,在其中預(yù)覽指定內(nèi)容然后打印
????iii. 當(dāng)前窗口內(nèi)嵌iframe贯要,或者彈框iframe暖侨,在iframe中預(yù)覽指定內(nèi)容,然后打印
??????1. 原理:
代碼示例:
//id-str 內(nèi)容中的id
printPart( id_str ) {
var cssStr = this.getCssBlock();
console.log(cssStr);
var cssStyle = document.createElement("style");
cssStyle.type = "text/css";
cssStyle.innerHTML = cssStr;
var el = document.getElementById(id_str);
var iframe = document.createElement("IFRAME");
var doc = null;
iframe.setAttribute(
"style",
"position:absolute;width:0px;height:0px;left:-500px;top:-500px;"
);
document.body.appendChild(iframe);
doc = iframe.contentWindow.document;
doc.getElementsByTagName("head")[0].appendChild(cssStyle);
doc.body.innerHTML = "<div id=" + id_str + ">" + el.innerHTML + "</div>";
// doc.write("<div id="+id_str+">" + el.innerHTML + "</div>");
doc.close();
iframe.contentWindow.focus();
iframe.contentWindow.print();
if (navigator.userAgent.indexOf("MSIE") > 0) {
console.log("------");
document.body.removeChild(iframe);
}
document.body.removeChild(iframe);
},
// 獲取vue文件中的style標(biāo)簽中的樣式
getCssBlock() {
const cssBlock = document.styleSheets;
console.log(cssBlock);
const styleData = [...cssBlock].reverse().find(({ cssRules }) => {
return [...cssRules].find((rule) => {
return ["#test-p"].includes(rule.selectorText);
});
});
return styleData.ownerNode.innerText;
},
??????2. 優(yōu)點(diǎn)
????????a) 打開(kāi)新窗口和當(dāng)前窗口內(nèi)嵌iframe方式崇渗,可以指定打印區(qū)域
????????b) 不會(huì)影響當(dāng)前頁(yè)面的頁(yè)面狀態(tài)
??????3. 缺點(diǎn)
????????a) 打開(kāi)新窗口和當(dāng)前窗口內(nèi)嵌iframe方式字逗,都是產(chǎn)生了新窗口,新窗口不會(huì)復(fù)用當(dāng)前窗口的css樣式宅广,需要為新窗口和iframe注入打印內(nèi)容的css樣式
????????b) 獲取指定打印區(qū)域css樣式葫掉,有很多不確定因素
????iv. 使用print.js插件
??????1. Print.js:
????????a) 大小:128 kB
????????b) Github-star:3.6k
??????c) 倉(cāng)庫(kù)地址:https://github.com/crabbly/Print.js
??????文檔地址:https://printjs.crabbly.com/#documentation
??????2. 原理:
????????原生js跟狱,將打印模塊的dom和style樣式(style/link)寫(xiě)入iframe挖息,然后調(diào)用document.execCommand(”print”)進(jìn)行打印
代碼示例:
// print-js 分頁(yè)樣式表現(xiàn)比較好
printMedical(idStr) {
// 調(diào)用打印插件,配置項(xiàng)參考官網(wǎng):https://printjs.crabbly.com/
print({
// printable為需要打印的DOM的id
printable: idStr,
// type為需要打印的類(lèi)型
type: "html",
// css為樣式文件或者直接css樣式兽肤,支持導(dǎo)入整個(gè)css文件,或者css文件數(shù)組
// css: medicalStyle,
// 可選項(xiàng)绪抛,這樣配置意味著應(yīng)用所有導(dǎo)入的css文件
targetStyles: ["*"],
});
},
??????3. 優(yōu)點(diǎn):
????????a) 邏輯簡(jiǎn)單资铡,展示效果完美
????????b) 支持打印的類(lèi)型多:PDF、HTML幢码、IMAGE笤休、JSON
????????c) 支持行內(nèi)樣式與外聯(lián)樣式,再也不用在DOM元素上寫(xiě)滿樣式了
????????d) 兼容性好症副,除了IE不支持PDF和IMAGE打印外店雅,其余主流瀏覽器全部支持
??????4. 缺點(diǎn):
????????a) 需要引入第三方插件,受控性不友好
????????b) 可能會(huì)出現(xiàn)的樣式異常
????????c) 無(wú)法直接打印/靜默打印贞铣,因?yàn)槭钦{(diào)用chrome打印闹啦,還是會(huì)彈出打印預(yù)頁(yè)面
2. 將Dom結(jié)構(gòu)轉(zhuǎn)換為pdf預(yù)覽打印
????a) 原理:
??????html2canvas+jsPDF 通過(guò)html2canvas將遍歷頁(yè)面打印元素,并渲染生成canvas辕坝,然后將canvas圖片格式添加到j(luò)sPDF實(shí)例窍奋,生成pdf,插入到iframe,然后打印
代碼示例:
downPdf(idStr) {
html2Canvas(document.querySelector(idStr), {
// onrendered:
background: "#0B1A48",
}).then((canvas) => {
var contentWidth = canvas.width;
var contentHeight = canvas.height;
// 一頁(yè)pdf顯示html頁(yè)面生成的canvas高度;
var pageHeight = (contentWidth / 592.28) * 841.89;
// 未生成pdf的html頁(yè)面高度
var leftHeight = contentHeight;
// pdf頁(yè)面偏移
var position = 0;
// a4紙的尺寸[595.28,841.89]琳袄,html頁(yè)面生成的canvas在pdf中圖片的寬高
var imgWidth = 595.28;
var imgHeight = (592.28 / contentWidth) * contentHeight;
var pageData = canvas.toDataURL("image/jpeg", 1.0);
// eslint-disable-next-line
var pdf = new jsPDF("", "pt", "a4");
// 有兩個(gè)高度需要區(qū)分江场,一個(gè)是html頁(yè)面的實(shí)際高度,和生成pdf的頁(yè)面高度(841.89)
// 當(dāng)內(nèi)容未超過(guò)pdf一頁(yè)顯示的范圍窖逗,無(wú)需分頁(yè)
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;
// 避免添加空白頁(yè)
if (leftHeight > 0) {
pdf.addPage();
}
}
}
// 導(dǎo)出pdf文件命名
// pdf.save("report_pdf_" + new Date().getTime() + ".pdf");
const iframe = document.createElement("iframe");
iframe.setAttribute(
"style",
// "position:absolute;width:0px;height:0px;left:-500px;top:-500px;"
"display:none"
);
iframe.src = pdf.output("bloburl");
document.body.appendChild(iframe);
iframe.contentWindow.focus();
iframe.contentWindow.print();
// iframe.contentWindow.document.close();
// document.body.removeChild(iframe);
// const myWindow = window.open(pdf.output("bloburl"));
// myWindow.print();
});
},
????b) 優(yōu)點(diǎn):
??????i. 可以設(shè)置分頁(yè)和不分頁(yè)
??????ii. 可以對(duì)多頁(yè)內(nèi)容添加頁(yè)眉頁(yè)腳
????c) 缺點(diǎn):
??????i. 需要引入兩個(gè)第三方插件html2canvas+jsPDF
??????ii. 前端生成pdf不太友好址否,會(huì)有一定的性能問(wèn)題
??????iii. 由于瀏覽器的樣式差異化,轉(zhuǎn)換后可能會(huì)出現(xiàn)樣式不統(tǒng)一問(wèn)題
??????iv. 分頁(yè)邏輯不好控制碎紊,會(huì)出現(xiàn)展示效果不理想
3.將Dom結(jié)構(gòu)轉(zhuǎn)換為image佑附,然后預(yù)覽打印
??a) 原理:
????將打印內(nèi)容html利用html2canvas生成canvas 通過(guò)canvas.toDataURL('image/jpeg', 1.0)生成圖片,插入到iframe中預(yù)覽打印
代碼示例:
// 生成圖片插入到iframe中矮慕,然后預(yù)覽打印
printBill() {
this.printDisabled = true; // 點(diǎn)擊打印按鈕禁止重復(fù)點(diǎn)擊
setTimeout(() => {
// 按鈕顯示為禁止了再去執(zhí)行截圖功能
html2Canvas(this.$refs.reconciliationWrapper, {
backgroundColor: null,
// scale: 1,
}).then((canvas) => {
let dataURL = canvas.toDataURL("image/png");
this.$refs.iframe.contentWindow.document.body.innerHTML = ""; // 清空上一次打印的內(nèi)容
this.$refs.iframe.contentWindow.document.write(
'<html><head><style media="print">@page { margin: 0mm 10mm; }body{margin-top: 50px; text-align: center;}</style></head><body><img src=' +
dataURL +
"></body></html>"
);
setTimeout(() => {
this.$refs.iframe.contentWindow.print();
}, 0);
this.printDisabled = false;
});
}, 100);
}
??b) 優(yōu)點(diǎn):無(wú)
??c) 缺點(diǎn):
????i. 轉(zhuǎn)換的圖片帮匾,樣式差異化很大,不推薦
????ii. 需要引入第三方插件html2canvas
四痴鳄、 直接打印方式—實(shí)現(xiàn)方案
- 前端將需要打印文件轉(zhuǎn)換為pdf瘟斜,然后導(dǎo)出blob流,然后通過(guò)post請(qǐng)求痪寻,將Blob流傳給后端服務(wù)螺句,調(diào)用后端部署的打印服務(wù)
??a) 優(yōu)點(diǎn):
????i. 可以直接打印,無(wú)需預(yù)覽
??b) 缺點(diǎn):
????i. 前端需要將打印內(nèi)容生成為blob流橡类,需要后端部在服務(wù)器部署打印機(jī)直接進(jìn)行靜默打印蛇尚。
????參考:https://blog.csdn.net/ma_nong33/article/details/128974474
- 利用第三方插件
??a) LODOP官方地址
??b) 使用參考:文檔1文檔2
??c) 優(yōu)點(diǎn):
????1.支持直接打印
????2.支持打印類(lèi)型豐富:HTML、TABLE顾画、URL取劫、TEXT、文檔模板
????3.配置項(xiàng)十分豐富研侣,大到是否顯示打印預(yù)覽谱邪,小到分頁(yè)設(shè)置、邊框設(shè)置等等
????4.兼容性好庶诡,除了兼容各類(lèi)瀏覽器外惦银,甚至還有LINUX版本
??d) 缺點(diǎn):
????1.直接打印功能需要付費(fèi)解鎖,但價(jià)格不算離譜末誓,210RMB起
????2.需要單獨(dú)下載exe安裝到電腦上扯俱,不過(guò)也沒(méi)辦法,能實(shí)現(xiàn)如此強(qiáng)大的打印功能喇澡,只有此途徑
????3.只支持內(nèi)聯(lián)樣式迅栅,樣式只能卸載DOM元素上
3.示例代碼:
import { getLodop } from "@/assets/lodop/LodopFuncs";
// 生成樣式文件
createPrintHtml(idStr) {
var cssStr = this.getCssBlock();
console.log(cssStr);
var cssStyle = document.createElement("style");
cssStyle.type = "text/css";
cssStyle.innerHTML = cssStr;
const strCss = "<style>" + cssStr + "</style>";
const printHtml = strCss + '<body>'+ document.getElementById(idStr).outerHTML + '</body>'
return printHtml
},
printLodop(idStr) {
// 獲取打印對(duì)象
const LODOP = getLodop();
// 打印初始化
LODOP.PRINT_INIT("打印任務(wù)名");
console.log(idStr);
// 設(shè)定紙張大小,指定需要打印的DOM元素
LODOP.ADD_PRINT_HTM(
0,
0,
"100%",
"100%",
// document.getElementById(idStr).innerHTML
this.createPrintHtml(idStr)
);
LODOP.PREVIEW();
// 執(zhí)行打印-直接打印
// LODOP.PRINT();
}
五晴玖、 調(diào)研方案總結(jié)
對(duì)以上預(yù)覽打印和直接打印兩種方式的實(shí)現(xiàn)方案優(yōu)缺點(diǎn)分析:
1.基于預(yù)覽打印實(shí)現(xiàn)方案;
??需要支持指定區(qū)域打印库继,無(wú)需單獨(dú)處理樣式打印模塊樣式箩艺,不需要前端進(jìn)行打印內(nèi)容文件轉(zhuǎn)換,兼容性以及穩(wěn)定性宪萄,支持指定打印內(nèi)容是多頁(yè)艺谆,推薦使用Printjs插件打印方式
PrintJS:
??基于Dom直接渲染到iframe,預(yù)覽打印
??無(wú)打印內(nèi)容格式轉(zhuǎn)換過(guò)程
??大邪萦ⅰ:128 kB
??Github-star:3.6k
2.基于直接預(yù)覽實(shí)現(xiàn)方案:
??1.由于LODOP無(wú)預(yù)覽打印模式需要付費(fèi)静汤,且需客戶安裝打印程序,不推薦居凶。
??2.前端將需要打印文件轉(zhuǎn)換為pdf虫给,然后導(dǎo)出blob流,然后通過(guò)post請(qǐng)求侠碧,將Blob流傳給后端服務(wù)抹估,調(diào)用后端部署的打印服務(wù)這種方式,需要前端轉(zhuǎn)換文件格式(需要插件)弄兜,存在性能和轉(zhuǎn)換出錯(cuò)問(wèn)題药蜻,后端需要部署打印機(jī)服務(wù),安裝打印服務(wù)替饿,不確定后端是否可以實(shí)現(xiàn)语泽。(暫不推薦,需要和服務(wù)端確認(rèn))