人人都会AI编程

14.4 打印机调用、打印预览、静默打印

更新时间:2026-07-11

在桌面应用中,打印功能往往是一个“看起来简单、做起来容易踩坑”的模块。Electron 底层使用 Chromium 的打印引擎,因此对现代 CSS 打印样式(@media print@page 等)的支持非常好。但要把打印体验做到符合用户预期,你还需要理解 Electron 提供的几种打印模式,并知道如何在实际业务中组合它们。

这一节会从最基础的打印机调用讲起,逐步过渡到打印预览和静默打印,每个部分都会给出可运行的代码示例。

14.4.1 获取打印机列表与选择打印机

在 Electron 中,所有打印机相关的操作都集中在 webContents 上,因为打印总是针对某个具体的窗口内容。要获取当前系统已安装的打印机,可以使用 webContents.getPrintersAsync()(Electron 17+)或 webContents.getPrinters()

获取打印机列表

// 主进程
const { BrowserWindow, ipcMain } = require('electron');

ipcMain.handle('get-printers', async () => {
  const win = BrowserWindow.getFocusedWindow();
  if (!win) return [];
  const printers = await win.webContents.getPrintersAsync();
  // 返回打印机列表,每个打印机包含 name、displayName、isDefault 等信息
  return printers.map(p => ({
    name: p.name,
    displayName: p.displayName,
    isDefault: p.isDefault,
    status: p.status,
  }));
});

在渲染进程中通常不会直接调用 webContents,而是通过 IPC 请求主进程获取打印机信息,填充到一个下拉选择框中。

// 渲染进程(preload 已暴露 ipcRenderer.invoke)
async function loadPrinters() {
  const printers = await window.api.invoke('get-printers');
  const select = document.getElementById('printer-select');
  select.innerHTML = '';
  printers.forEach(p => {
    const option = document.createElement('option');
    option.value = p.name;
    option.text = `${p.displayName}${p.isDefault ? '(默认)' : ''}`;
    select.appendChild(option);
  });
}

14.4.2 打印预览:让用户确认设置

默认情况下,调用 webContents.print() 会直接弹出系统原生的打印对话框,这个对话框本身就包含预览功能。但有些产品希望在应用内提供一个更可控的“打印预览”区域,让用户在真正发送到打印机之前检查排版效果。

实现这一目的通常有两种思路:

  1. 利用 webContents.printToPDF 生成临时 PDF,然后渲染预览

将 PDF 数据转为 Blob URL,在 <iframe> 或专门的 PDF 预览组件中展示。这种方式的优势是可以完全控制预览界面的样式,缺点是 PDF 的渲染效果与最终打印机输出可能有一些细微差异(主要是因打印机设置不同)。

  1. 通过 CSS 模式切换模拟打印效果

在页面上动态添加一个 @media print 等效的样式类,让用户看到分页后的页面外观,但这并不能完全还原真实打印机的页面设置(如页边距、页眉页脚)。

在实际产品中,兼顾体验和准确度的常见做法是第一种——生成 PDF 预览。下面是一个完整示例:

主进程生成 PDF 并返回缓冲区

ipcMain.handle('generate-print-preview', async (event, options) => {
  const win = BrowserWindow.getFocusedWindow();
  if (!win) throw new Error('没有活动窗口');

  // options 可以包含 pageSize、margins、printBackground 等
  const pdfBuffer = await win.webContents.printToPDF({
    printBackground: true,
    marginsType: 1, // 默认边距
    pageSize: 'A4',
    landscape: false,
    ...options,
  });
  return pdfBuffer; // 返回 Buffer
});

渲染进程显示预览

async function showPreview() {
  const pdfBuffer = await window.api.invoke('generate-print-preview', {
    pageSize: 'A4',
    marginsType: 1,
    printBackground: true,
  });

  const blob = new Blob([pdfBuffer], { type: 'application/pdf' });
  const url = URL.createObjectURL(blob);

  const previewIframe = document.getElementById('preview-iframe');
  previewIframe.src = url; // 在 iframe 中显示 PDF
}

注意:printToPDF 会尊重当前页面的 CSS @page 规则,因此你可以在样式表中定义纸张大小、页边距等,让预览更接近最终打印结果。

14.4.3 静默打印:直接发送到打印机

很多业务场景(如餐饮小票打印、物流面单打印)不需要用户每次都看到打印对话框,而是直接使用预设配置将内容输出到打印机。此时可以使用 webContents.print() 并传入 silent: true 选项,或者使用 webContents.printToPDF 得到文件后再通过系统打印命令输出。

方式一:使用 webContents.print() 静默打印

// 主进程
ipcMain.handle('silent-print', async (event, options) => {
  const win = BrowserWindow.getFocusedWindow();
  if (!win) throw new Error('没有活动窗口');

  // options 指定打印机名、份数、页面设置等
  await win.webContents.print({
    silent: true,
    printBackground: true,
    deviceName: options.printerName, // 之前获取的打印机 name
    copies: options.copies || 1,
    margins: { top: 0, bottom: 0, left: 0, right: 0 },
    color: true,
    landscape: false,
  });
});

调用这个方法时,不会弹出任何对话框,内容会直接发送到指定的打印机。如果 deviceName 不存在,则使用系统默认打印机。

方式二:生成 PDF 后用命令行打印(适用于热敏打印机等特殊场景)

有些热敏打印机不支持通过 Chromium 直接打印,或者需要特殊的纸张尺寸(如 80mm × 无限长)。此时可以先将内容渲染为一个特定尺寸的 HTML 页面,再通过 printToPDF 生成 PDF,最后调用系统命令(如 Windows 的 print 命令或 Linux 的 lp)来发送给打印机。

const { exec } = require('child_process');

async function silentPdfPrint(pdfPath, printerName) {
  const cmd = `lp -d "${printerName}" "${pdfPath}"`; // Linux/macOS
  // Windows 可以使用 SumatraPDF 或 PDFtoPrinter 等工具
  exec(cmd, (error, stdout, stderr) => {
    if (error) console.error('打印失败', error);
  });
}

这种方式虽然步骤稍多,但灵活性极高,完全绕过了 Chromium 的打印限制,适合对打印精度和格式有严格要求的场景。

14.4.4 常见问题与注意点

  1. 打印内容必须是可见的窗口

printprintToPDF 只会打印当前 webContents 中实际渲染的内容,如果某个元素设置了 display: none,它不会出现在打印结果中。你可以专门创建一个隐藏的打印窗口,在里面加载需要打印的 HTML,然后对该窗口进行打印操作。

  1. CSS 打印样式的调试

在开发阶段,你可以打开 Chrome DevTools 的 Rendering 面板,勾选“Emulate CSS media type > print”,这样就能在不实际打印的情况下看到打印效果。

  1. 静默打印对打印机名称的要求

deviceName 必须与 getPrintersAsync() 返回的 name 字段完全一致,否则 Electron 会忽略该参数并使用默认打印机。某些打印机(特别是网络打印机)的名称可能包含特殊字符,注意处理。

  1. 跨平台打印差异
  • Windows 下静默打印通常比较稳定。
  • macOS 下静默打印可能需要额外的权限,如果应用沙盒化,还需要配置 com.apple.security.print 权限。
  • Linux 下依赖 CUPS 系统,确保打印机驱动已正确安装。
  1. 打印大批量任务

如果需要连续打印大量页面,建议使用队列机制,避免同时生成多个 PDF 导致内存飙升。

综合来看,Electron 提供的打印 API 已经能够覆盖绝大多数的业务需求。从简单的调起系统打印对话框,到完全自控的静默打印方案,你都可以在同一个技术栈中实现,无需依赖任何外部插件。