- Published on
Vue 实现打印功能的两种方法(Vue 2 & Vue 3)
- Authors
- Name
- Tian Haipeng
话不多说,先上代码
Vue 2 打印方法
function handleBatchPrint() {
// 业务代码
// ......
// 需要打印的数据
let data = {};
// PrintTable 是打印模板组件
const printOrderComponent = Vue.extend(PrintTable);
// 获取组件的样式,用于打印时应用
const styles = printOrderComponent.options.style;
// 创建组件实例,并冻结数据,避免数据动态响应
const instanceCom = new printOrderComponent({
propsData: {
data: Object.freeze(data),
},
});
// 使用 Vue 的 nextTick 来确保 DOM 已更新
this.$nextTick(() => {
// 获取 iframe DOM 元素,作为打印区域
const printArea = this.$refs.batchPrintArea;
// 获取 iframe 内的文档对象
const printContent = printArea.contentDocument || printArea.contentWindow.document;
// 设置打印区域的 HTML 结构
printContent.documentElement.innerHTML = `
<html>
<head></head>
<body>
<div id="print-box"></div>
</body>
</html>
`;
// 创建样式元素,将之前获取的样式插入到 iframe 的 head 中
const style = document.createElement("style");
style.innerHTML = styles;
printContent.head.appendChild(style);
// 将创建的组件实例挂载到 iframe 中的 #print-box 元素
instanceCom.$mount(printContent.getElementById("print-box"));
// 打印完成后执行自定义逻辑,比如更新打印状态
printArea.contentWindow.onafterprint = () => {
this.afterPrint(this.getOrderIds());
};
// 调用 iframe 的 print 方法,启动打印
printArea.contentWindow.print();
});
}
Vue 3 打印方法
const printClick = async (row: any, command: boolean) => {
// 业务代码
// ......
// 准备打印数据
let data = {};
// 创建打印组件实例,将数据传递给组件
const printOrderComponent = createApp(PrintTable, { data });
// 等待 DOM 更新
await nextTick(() => {
// 获取 iframe DOM 元素,作为打印区域
const printArea = batchPrintArea.value;
// 获取 iframe 内的文档对象
const printContent = printArea.contentDocument || printArea.contentWindow.document;
// 获取当前页面所有样式,并将其转换为文本形式插入到打印页面中
const stylesText = Array.from(document.styleSheets)
.map((s) => {
try {
// 将每个样式表的规则提取出来,拼接成字符串
return Array.from(s.cssRules)
.map((z) => z.cssText || "")
.join("\n");
} catch (e) {
// 捕获跨域样式表引发的错误并跳过这些样式
console.warn("无法访问的样式表: ", s.href, e);
return "";
}
})
.join("\n");
// 设置 iframe 的 HTML 结构,并插入所有样式
printContent.documentElement.innerHTML = `
<html>
<head>
<style>${stylesText}</style>
</head>
<body>
<div id="print-box"></div>
</body>
</html>
`;
// 将创建的打印组件挂载到 iframe 的 #print-box 元素
printOrderComponent.mount(printContent.getElementById("print-box"));
// 调用 iframe 的 print 方法,启动打印
printArea.contentWindow.print();
});
};
在这两个 Vue 打印功能实现中,核心原理是通过创建一个隐藏的 iframe
标签,将打印内容插入到 iframe
中,然后调用浏览器的 print()
方法从 iframe
中打印出内容。这是为了确保页面的打印内容独立于当前主页面,并且避免直接在主页面上操作 DOM 造成的副作用。
实现原理讲解
1. 打印模板的作用
打印模板是一个 Vue 组件,通常是为了定义需要打印的内容结构。这个模板可以根据需要动态生成,并且包含特定的样式和布局。
- 在 Vue 2 中,打印模板使用
Vue.extend(PrintTable)
动态创建组件实例,便于将数据传入并生成对应的 DOM 元素。 - 在 Vue 3 中,使用
createApp(PrintTable)
动态创建应用实例,然后挂载到指定的 DOM 元素。
这些模板组件和其他普通 Vue 组件类似,可以通过 props
或 data
传入数据,生成想要展示的 HTML 结构。
2. iframe 的作用
iframe
是打印内容的容器。之所以使用 iframe
,是为了隔离打印内容与当前页面的内容,避免主页面的样式或其他内容干扰打印效果。
iframe
元素是隐藏的,通常通过ref
或document.querySelector
来获取它。- 我们通过向
iframe
的contentDocument
或contentWindow.document
插入打印的 HTML 内容,确保在打印时,只打印 iframe 内的内容。
在实际应用中,我们通过 Vue 的 $refs
或 ref
属性获取到 iframe
元素。
3. printContent.documentElement.innerHTML 的作用
printContent.documentElement.innerHTML
是用来直接向 iframe
的 document
中插入 HTML 结构的关键操作。这个操作的原理是:
printContent
:指向iframe
内的文档对象,可以理解为iframe
内的 DOM 树。documentElement
:是iframe
文档的根节点,一般是<html>
标签。innerHTML
:允许我们直接重写整个iframe
的 HTML 内容。
具体步骤如下:
- 我们首先获取
iframe
的文档对象(即printContent
),然后通过documentElement
来操作它的 HTML 结构。 - 使用
innerHTML
重写整个iframe
的文档,包括<html>
、<head>
和<body>
标签。这一步很重要,它让我们能够插入自定义的 HTML 结构和样式。 - 在这个 HTML 结构中,我们动态插入组件的容器(例如
<div id="print-box"></div>
),然后将打印模板组件挂载到这个容器中。 - 通过 Vue 的
mount
或instanceCom.$mount
,将组件的内容渲染到iframe
中的指定元素(即#print-box
中),从而将打印的内容动态展示到iframe
中。
4. 关联与执行流程
打印模板(PrintTable):定义了要打印的内容布局和样式,这个模板将会被插入到
iframe
中作为打印内容。iframe:作为一个独立的页面,用来承载要打印的 HTML 内容,并通过
print()
方法触发浏览器打印功能。printContent.documentElement.innerHTML:将打印模板的 HTML 结构和样式插入到
iframe
中,使打印内容和主页面完全隔离。流程:
- 创建
iframe
标签并将其隐藏。 - 在
iframe
中动态插入 HTML 结构,包括打印模板和必要的样式。 - 将打印模板挂载到
iframe
中的容器内。 - 调用
iframe.contentWindow.print()
打印iframe
中的内容。
- 创建
这样做的好处是保证打印内容的独立性和完整性,不会被主页面的样式或内容干扰。
5. 为什么不直接修改主页面打印?
直接在主页面上进行打印有几个问题:
- 样式污染:如果打印的内容和主页面共享样式,打印时可能会出现不必要的样式,影响打印效果。
- DOM 操作复杂:为了打印而修改主页面的 DOM 结构,会破坏主页面的原始布局,导致页面行为异常或错误。
- 内容独立:通过
iframe
可以将打印内容与主页面内容完全隔离,使打印内容的控制更灵活。
总结:通过创建一个 iframe
,我们可以在不影响主页面的情况下加载并打印所需内容。printContent.documentElement.innerHTML
用于将打印模板插入到 iframe
中,而 Vue 组件的动态挂载确保了打印内容的动态性和数据绑定。