使用@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 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端