一、 需求期望
根据需求生成指定html结构,通过web端调用打印功能将其或者指定区域打印出来
二、 打印方式
1. 预览打印
点击打印按钮以后,出现浏览器自带的打印预览设置面板,点击确认,进行页面打印
2. 直接打印(静默打印)
无预览页面展示,直接调用打印机,打印相应内容
注:预览打印方式在交互方式上不支持批量打印
三、 预览打印方式—实现方案
1. 基于Dom结构预览打印
a) 整个页面全部打印
i. 原理:调用window.print()
ii. 优点:
1. 无兼容性问题,展示效果完美,页面整体打印
iii. 缺点:
1. 无法指定区域打印,会打印当前全页面内容
b) 预览打印指定区域
i. 当前窗口直接打印指定区域
1. 原理:
代码示例:
//(1)首先获得元素的html内容(这里建议如果有样式最好是用内联样式的方式)
var newstr = document.getElementById(myDiv).innerHTML; //得到需要打印的元素HTML
//(2)保存当前页面的整个html,因为window.print()打印操作是打印当前页的所有内容,所以先将当前页面保存起来,之后便于恢复。
var oldstr = document.body.innerHTML; //保存当前页面的HTML
//(3)把当前页面替换为打印内容HTML
document.body.innerHTML = newstr;
//(4)执行打印操作
window.print();
//(5)还原当前页面
document.body.innerHTML = oldstr;
},
2. 缺点:
a) 实现方式的特殊性,会导致当前页面状态重置,用户体验不好
ii. 打开新窗口,在其中预览指定内容然后打印
iii. 当前窗口内嵌iframe,或者弹框iframe,在iframe中预览指定内容,然后打印
1. 原理:
代码示例:
//id-str 内容中的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标签中的样式
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. 优点
a) 打开新窗口和当前窗口内嵌iframe方式,可以指定打印区域
b) 不会影响当前页面的页面状态
3. 缺点
a) 打开新窗口和当前窗口内嵌iframe方式,都是产生了新窗口,新窗口不会复用当前窗口的css样式,需要为新窗口和iframe注入打印内容的css样式
b) 获取指定打印区域css样式,有很多不确定因素
iv. 使用print.js插件
1. Print.js:
a) 大小:128 kB
b) Github-star:3.6k
c) 仓库地址:https://github.com/crabbly/Print.js
文档地址:https://printjs.crabbly.com/#documentation
2. 原理:
原生js,将打印模块的dom和style样式(style/link)写入iframe,然后调用document.execCommand(”print”)进行打印
代码示例:
// print-js 分页样式表现比较好
printMedical(idStr) {
// 调用打印插件,配置项参考官网:https://printjs.crabbly.com/
print({
// printable为需要打印的DOM的id
printable: idStr,
// type为需要打印的类型
type: "html",
// css为样式文件或者直接css样式,支持导入整个css文件,或者css文件数组
// css: medicalStyle,
// 可选项,这样配置意味着应用所有导入的css文件
targetStyles: ["*"],
});
},
3. 优点:
a) 逻辑简单,展示效果完美
b) 支持打印的类型多:PDF、HTML、IMAGE、JSON
c) 支持行内样式与外联样式,再也不用在DOM元素上写满样式了
d) 兼容性好,除了IE不支持PDF和IMAGE打印外,其余主流浏览器全部支持
4. 缺点:
a) 需要引入第三方插件,受控性不友好
b) 可能会出现的样式异常
c) 无法直接打印/静默打印,因为是调用chrome打印,还是会弹出打印预页面
2. 将Dom结构转换为pdf预览打印
a) 原理:
html2canvas+jsPDF 通过html2canvas将遍历页面打印元素,并渲染生成canvas,然后将canvas图片格式添加到jsPDF实例,生成pdf,插入到iframe,然后打印
代码示例:
downPdf(idStr) {
html2Canvas(document.querySelector(idStr), {
// onrendered:
background: "#0B1A48",
}).then((canvas) => {
var contentWidth = canvas.width;
var contentHeight = canvas.height;
// 一页pdf显示html页面生成的canvas高度;
var pageHeight = (contentWidth / 592.28) * 841.89;
// 未生成pdf的html页面高度
var leftHeight = contentHeight;
// pdf页面偏移
var position = 0;
// a4纸的尺寸[595.28,841.89],html页面生成的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");
// 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
// 当内容未超过pdf一页显示的范围,无需分页
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;
// 避免添加空白页
if (leftHeight > 0) {
pdf.addPage();
}
}
}
// 导出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) 优点:
i. 可以设置分页和不分页
ii. 可以对多页内容添加页眉页脚
c) 缺点:
i. 需要引入两个第三方插件html2canvas+jsPDF
ii. 前端生成pdf不太友好,会有一定的性能问题
iii. 由于浏览器的样式差异化,转换后可能会出现样式不统一问题
iv. 分页逻辑不好控制,会出现展示效果不理想
3.将Dom结构转换为image,然后预览打印
a) 原理:
将打印内容html利用html2canvas生成canvas 通过canvas.toDataURL('image/jpeg', 1.0)生成图片,插入到iframe中预览打印
代码示例:
// 生成图片插入到iframe中,然后预览打印
printBill() {
this.printDisabled = true; // 点击打印按钮禁止重复点击
setTimeout(() => {
// 按钮显示为禁止了再去执行截图功能
html2Canvas(this.$refs.reconciliationWrapper, {
backgroundColor: null,
// scale: 1,
}).then((canvas) => {
let dataURL = canvas.toDataURL("image/png");
this.$refs.iframe.contentWindow.document.body.innerHTML = ""; // 清空上一次打印的内容
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) 优点:无
c) 缺点:
i. 转换的图片,样式差异化很大,不推荐
ii. 需要引入第三方插件html2canvas
四、 直接打印方式—实现方案
- 前端将需要打印文件转换为pdf,然后导出blob流,然后通过post请求,将Blob流传给后端服务,调用后端部署的打印服务
a) 优点:
i. 可以直接打印,无需预览
b) 缺点:
i. 前端需要将打印内容生成为blob流,需要后端部在服务器部署打印机直接进行静默打印。
参考:https://blog.csdn.net/ma_nong33/article/details/128974474
- 利用第三方插件
a) LODOP官方地址
b) 使用参考:文档1文档2
c) 优点:
1.支持直接打印
2.支持打印类型丰富:HTML、TABLE、URL、TEXT、文档模板
3.配置项十分丰富,大到是否显示打印预览,小到分页设置、边框设置等等
4.兼容性好,除了兼容各类浏览器外,甚至还有LINUX版本
d) 缺点:
1.直接打印功能需要付费解锁,但价格不算离谱,210RMB起
2.需要单独下载exe安装到电脑上,不过也没办法,能实现如此强大的打印功能,只有此途径
3.只支持内联样式,样式只能卸载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) {
// 获取打印对象
const LODOP = getLodop();
// 打印初始化
LODOP.PRINT_INIT("打印任务名");
console.log(idStr);
// 设定纸张大小,指定需要打印的DOM元素
LODOP.ADD_PRINT_HTM(
0,
0,
"100%",
"100%",
// document.getElementById(idStr).innerHTML
this.createPrintHtml(idStr)
);
LODOP.PREVIEW();
// 执行打印-直接打印
// LODOP.PRINT();
}
五、 调研方案总结
对以上预览打印和直接打印两种方式的实现方案优缺点分析:
1.基于预览打印实现方案;
需要支持指定区域打印,无需单独处理样式打印模块样式,不需要前端进行打印内容文件转换,兼容性以及稳定性,支持指定打印内容是多页,推荐使用Printjs插件打印方式
PrintJS:
基于Dom直接渲染到iframe,预览打印
无打印内容格式转换过程
大小:128 kB
Github-star:3.6k
2.基于直接预览实现方案:
1.由于LODOP无预览打印模式需要付费,且需客户安装打印程序,不推荐。
2.前端将需要打印文件转换为pdf,然后导出blob流,然后通过post请求,将Blob流传给后端服务,调用后端部署的打印服务这种方式,需要前端转换文件格式(需要插件),存在性能和转换出错问题,后端需要部署打印机服务,安装打印服务,不确定后端是否可以实现。(暂不推荐,需要和服务端确认)