定制打印输出
📦核心
sh
npm i modern-screenshot
-
css规则
- @page 边距 at 规则,用于
@page
at 规则内部 - @media print
- @page 边距 at 规则,用于
-
基本逻辑
-
通过 modern-screenshot 中的
domToForeignObjectSvg
函数将指定的DOM元素
转化为SVGSVGElement
固定其css样式 -
创建
HTMLIFrameElement
元素并将获取的SVGSVGElement
元素挂载到body
元素上并加载指定的 @page 边距 at 规则
-
🖥️ 代码
ts
const printReport = async () => {
// 浏览器版本检查
const chromeVersion = navigator.userAgent.match(/Chrome\/(\d+)/)?.[1];
if (!(chromeVersion && parseInt(chromeVersion) >= 131)) {
await showVersionWarning();
}
// 创建打印iframe
const printWindow = document.createElement('iframe');
printWindow.style.display = 'none';
printWindow.onload = async function () {
// 处理logo
const iconDataUrl = await domToDataUrl(logo.value!, { quality: 1 });
// 克隆报表内容
const reportClone = report.value.cloneNode(true) as HTMLDivElement;
reportClone.querySelector('footer')!.remove();
reportClone.querySelector('header')!.remove();
// 克隆节点替换原节点
document.querySelector('.report-container')!.replaceChild(reportClone, report.value);
const foreignObjectSvgElement = await domToForeignObjectSvg(reportClone, {
quality: 1,
backgroundColor: '#fff'
});
// 重新替换回来
document.querySelector('.report-container')!.replaceChild(report.value, reportClone);
// 注入打印样式
const linkElement = printWindow.contentWindow!.document.createElement('link');
const styleElement = printWindow.contentWindow!.document.createElement('style');
linkElement.type = 'text/css';
linkElement.rel = 'stylesheet';
linkElement.href = printCSS;
// 注入动态样式
styleElement.innerHTML = `
:root {
--file-no: "${documentNo}";
}
@page {
@top-center {
content: url("${iconDataUrl}") / "${companyName}";
}
}
`;
// 添加样式和内容
printWindow.contentWindow!.document.head.appendChild(linkElement);
printWindow.contentWindow!.document.head.appendChild(styleElement);
printWindow.contentWindow!.document.body.appendChild(
foreignObjectSvgElement.querySelector('.app-container')!
);
// 自动调整内容高度
const content = printWindow.contentWindow!.document.querySelector('.test-data') as HTMLElement;
content.style.height = 'auto';
content.style.blockSize = 'auto';
// 清理资源
printWindow.contentWindow!.addEventListener('afterprint', () => {
document.body.removeChild(printWindow);
myChart?.off('finished');
});
// 等待样式加载完成后打印
linkElement.addEventListener('load', () => {
printWindow.contentWindow!.print();
});
};
// 初始化iframe
printWindow.src = 'about:blank';
document.body.appendChild(printWindow);
};
printCSS.css
css
@page {
/* size: A4; */
/* margin: 0; */
margin-left: 0pt;
margin-right: 0pt;
margin-bottom: 3lh;
margin-top: 3lh;
@top-left {
content: "\A \A Fender Test Report";
font-family: 'Times New Roman';
font-size: 12pt;
font-weight: bold;
white-space: pre;
padding-left: 20px;
}
@top-right {
content: "\A \A" var(--file-no);
font-family: 'Times New Roman';
font-size: 0.75em;
font-weight: normal;
white-space: pre;
padding-right: 20px;
}
@bottom-center {
content: "Footer Content";
font-family: 'Times New Roman';
font-size: 10pt;
white-space: pre;
}
}
✨优点
- 自定义页眉与页脚且不只限于文本
- 直接复用页面样式,可以使用框架组件快速构建页面
🔦注意
SVG擅长的是绘制几何对象 ,而非承载文档流 ,SVG 采用基于坐标的绝对定位(通过 x
、y
属性定义位置),无法像 HTML/CSS 那样实现自适应流式布局。元素无法根据容器大小自动调整位置或换行。
ts
// 克隆节点替换原节点
document.querySelector('.report-container')!.replaceChild(reportClone, report.value);
const foreignObjectSvgElement = await domToForeignObjectSvg(reportClone, {
quality: 1,
backgroundColor: '#fff'
});
// 重新替换回来
document.querySelector('.report-container')!.replaceChild(report.value, reportClone);
此处就是通过HTML来处理文档流变化后的效果并通过svg
固定下来
💡兼容性(至2025.03.30)
