使用@page 边距 at 规则 + modern-screenshot 定制打印输出内容

定制打印输出

📦核心

sh 复制代码
npm i modern-screenshot

🖥️ 代码

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;
  }
}

✨优点

  1. 自定义页眉与页脚且不只限于文本
  2. 直接复用页面样式,可以使用框架组件快速构建页面

🔦注意

SVG擅长的是​​绘制几何对象​ ​,而非​​承载文档流​ ​,SVG 采用基于坐标的绝对定位(通过 xy 属性定义位置),无法像 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)

相关推荐
前端小张同学2 分钟前
前端行情好起来了,但是我依然没拿到offer
前端
程序员小续5 分钟前
React 官方严令禁止:Hook 不能写在 if/else,真相竟然是…
前端·javascript·程序员
懒得不想起名字5 分钟前
flutter_riverpod: ^2.6.1 应用笔记
前端
CrabXin5 分钟前
让网页在 PC 缩放时“纹丝不动”的 4 个技巧
前端·react.js
Juchecar15 分钟前
Naive UI 学习指南 - Vue3 初学者完全教程
前端·vue.js
用户81686947472516 分钟前
从0到1教你开发一个Mini-ESLint
前端·开源
coding随想16 分钟前
JavaScript中的DOM事件对象Event全解析
前端
专研狂17 分钟前
React 的闭包陷阱 + 状态异步更新机制
前端
zabr22 分钟前
AI黑箱解密:开发者必须了解的AI内部机制真相,原来我们一直被忽悠了
前端·aigc·ai编程
Sokach38634 分钟前
vue3引入tailwindcss 4.1
前端·css