macOS 如何实现网页静默打印?同一套 web-print-pdf 搞定

在 Apple 生态里做 B/S 打印,最容易踩的两个坑是:

  • macOS :Safari / Chrome 里 window.print() 照样弹系统对话框;打印机队列名带 URL 编码或空格,和业务里存的字符串对不上就「静默失败」。
  • iOS / iPadOS :业务方常问「iPad 上能不能像 Windows 一样一点就出纸?」------答案往往让人失望,但原因必须讲清楚,否则会在错误平台上投入错误方案。

本文分两部分:macOS 上可落地的完整静默打印链路 (与 Web打印专家 MAC 分支、web-print-pdf 对齐),以及 iOS 的能力边界与可行替代架构

npm 包https://www.npmjs.com/package/web-print-pdf

源码https://github.com/weixiaoyi/web-print-pdf


0. 先对齐:什么叫「网页静默打印」

与前几篇一致,这里指:

  1. 用户在 Web 系统里触发打印
  2. 不出现浏览器自带打印对话框(或仅少数角色保留预览)
  3. 任务按指定打印机、纸张、份数进入系统打印队列并出纸
  4. 有日志可查:是没下发,还是驱动 / 队列卡住

macOS 桌面 在引入本地客户端后,可以做到与 Windows、统信 UOS 同级的能力。

iOS / iPadOS 上的 Safari 页面 ,在现行系统策略下无法达到同等级别的纯 Web 静默打印------下文 §6 专门展开。


1. macOS:为什么浏览器 alone 不够

1.1 与 Windows / 国产 Linux 相同的沙箱边界

macOS 上的 Chromium、Safari 遵循同一套 Web 安全模型:

页面 JS 能做 页面 JS 不能做
window.print()必弹 macOS 打印面板 直接调用 CUPS lp 提交作业
有限的前端排版 指定队列、份数、纸盒并跳过用户确认
生成 PDF 供用户下载 读写 spooler 作业列表(无本地代理时)

这不是「Mac 特有问题」,而是 W3C / Apple WebKit 安全策略 的通用结果。

1.2 macOS 特有的额外摩擦

即便有了本地客户端,Mac 侧还会遇到:

  • CUPS 队列名lpstat -a 里的名称可能与「系统设置 → 打印机」显示名不完全一致,有时带 URI 编码(如 %20)。
  • AirPrint / IPP 队列 :网络打印机队列名较长,业务库里的 printerName 必须与 CUPS 完全一致。
  • Apple Silicon / Intel 双架构 :HTML→PDF 引擎必须是对应架构的 headless 二进制,混用会在运行时直接失败。
  • 权限与公证 :本地客户端需要 com.apple.security.print 等 entitlement,否则即使用 lp 也可能被 Gatekeeper / 沙箱拦住。

因此,Mac 上的生产方案同样是:浏览器 + 本地受信代理 + CUPS,而不是在网页里多写几行 JS。


2. 推荐架构:web-print-pdf + Web打印专家(macOS)

复制代码
┌─────────────────┐   WebSocket (web-print-pdf)   ┌──────────────────────────┐
│  Vue / React 页面 │ ────────────────────────────► │ Web打印专家 macOS 客户端   │
│  npm: web-print-pdf│                              │ HTML→PDF→lp (CUPS)       │
└─────────────────┘                              └────────────┬─────────────┘
                                                                ▼
                                                     macOS 本地打印机 / AirPrint 队列
  • Layer 1 --- 业务 Web :与 Windows、统信 UOS 同一套 import webPrintPdf from "web-print-pdf"
  • Layer 2 --- Web打印专家(Electron,MAC 分支):内嵌本地 HTTP/WebSocket 服务,负责 HTML 落盘、PDF 生成、任务队列
  • Layer 3 --- CUPS :macOS 内置打印栈,客户端通过 lp / lpstat 与系统交互

前提 :终端用户已安装并运行 Web打印专家 macOS 版 ;npm 包通过 WebSocket 连接本机客户端(默认探测 ws://127.0.0.1:16794 及备选端口),客户端未启动时调用会失败。

npm 包安装

bash 复制代码
npm install web-print-pdf
资源 地址
npm 包 https://www.npmjs.com/package/web-print-pdf
GitHub 源码 https://github.com/weixiaoyi/web-print-pdf

3. MAC 分支技术深解:与 Windows 差在哪

Web打印专家在 MAC 分支origin/MAC)上做了平台收敛 :Layer 2 的产品逻辑(WebSocket 协议、HTML→PDF 流水线、双队列、远程打印)与 Windows 版一致,最后一环「PDF → 纸」 从 SumatraPDF 换成了 CUPS 原生 lp

3.1 打印后端:printBackend.js

Windows 主分支典型路径:

复制代码
printPDF64.exe -print-to <printer> -silent -print-settings paper=A4,1x ...

MAC 分支改为:

复制代码
lp -d <队列名> -n <份数> -o job-sheets=none,none -o media=A4 ... <file.pdf>

核心设计点:

能力 Windows(Sumatra) macOS(CUPS / lp)
指定打印机 -print-to -d queueName
静默(无横幅页) -silent -o job-sheets=none,none
纸张 paper=A4 -o media=A4
彩色 color / monochrome -o print-color-mode=color
双面 duplexlong -o sides=two-sided-long-edge
缩放 fit / shrink / noscale -o fit-to-page
纸盒 bin= -o InputSlot=(经 lpoptions -l 解析)

printOptions 对外字段保持一致printerNamepaperFormatcopiesduplexModecolorfulscaleModepageRangesbin 等),前端无需为 Mac 单独写一套参数------差异全部收在 printBackend.buildCommand() 里。

下发前会调用 ensureReady() 执行 lpstat -r,确认 CUPS 已就绪;若打印服务未启动,客户端会提示「请确认已安装并启动 CUPS」类错误,而不是让 lp 静默挂起。

3.2 系统打印机:version1.js(仅 darwin)

MAC 分支的 SystemPrinter 只适配 macOS,不再走 Windows 的 PowerShell / WMI / wmic 链路,而是:

  1. lpstat -d --- 读系统默认打印机
  2. lpstat -v / -e / -p --- 枚举队列名
  3. lpoptions -p <queue> -l --- 读 PageSize、InputSlot 等驱动选项
  4. /private/etc/cups/ppd/ --- 必要时解析 PPD 补全纸张尺寸
  5. Electron getPrintersAsync() --- CUPS 命令失败时的兜底列表

队列名会做 normalize (trim、URL decode),避免 %20 与空格混用导致匹配失败。

异常任务检测走 lpstat -W not-completed -l -o,按 reason 正则识别 error / paper-out / blocked 等状态------与 Windows 侧「打印超时 + 异常任务提醒」的产品行为对齐。

3.3 HTML → PDF:mac-chrome 内置引擎

统信 / 麒麟桌面可在运行时拉 Chromium;macOS 安装包策略不同 ------终端用户环境不应在首次打印时再下载约 160MB 引擎。MAC 分支因此将 Playwright chromium-headless-shell 预置进安装包:

复制代码
appAsarUnpack/chrome-mac/x64/headless_shell
appAsarUnpack/chrome-mac/arm64/headless_shell
  • 开发阶段yarn startnode scripts/mac-chrome/preflight.js 可自动下载到上述目录
  • 打包阶段node scripts/mac-pack-prep.js / yarn package:mac 校验引擎完整性后再 electron-builder
  • 运行时 :缺引擎直接报错,不会在线下载------避免柜面 / 办公网无外网时首次打印失败

打包 hook(beforeBuild / beforePack / afterPack)会校验架构匹配(Apple Silicon 包内不能塞 x64 的 shell),并在 afterPackheadless_shell 执行 chmod 755

3.4 权限:entitlements.mac.plist

macOS 客户端声明了打印与网络相关 entitlement,其中包括:

xml 复制代码
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>

本地 WebSocket 监听 127.0.0.1 需要 server 权限;调用 CUPS 出纸需要 print 权限。企业分发时还需配合代码签名 + 公证(notarization),否则用户首次打开会被 Gatekeeper 拦截------这是 Mac 交付里独立于代码本身的运维项。

3.5 与统信 UOS / 银河麒麟的异同

维度 国产 Linux 桌面 macOS
出纸命令 lp(CUPS) lp(CUPS)
printOptions 映射 类似 MAC 分支 printBackend 专用实现
PDF 引擎 运行时 Playwright 拉取为主 安装包内置 headless_shell
打印机枚举 lpstat + 部分 Electron 兜底 lpstat + PPD + Electron 兜底
前端 npm 包 相同 web-print-pdf 相同

结论:前端一行 webPrintPdf.printHtml(...) 可跨 Windows / 国产 Linux / macOS;只需在 Mac 终端安装 macOS 版 Web打印专家。


4. macOS 使用案例(web-print-pdf)

以下示例与统信 / Windows 文档完全一致,便于混部项目复制粘贴。

4.1 HTML 片段静默打印

javascript 复制代码
import webPrintPdf from "web-print-pdf";

const pdfOptions = {
  paperFormat: "A4",
  margin: { top: "20px", bottom: "20px", left: "20px", right: "20px" },
  printBackground: true,
};

const printOptions = {
  printerName: "HP_LaserJet_Pro", // 与 lpstat -a 队列名一致
  paperFormat: "A4",
  copies: 1,
  duplexMode: "duplexlong",
};

const extraOptions = {
  action: "print", // 静默;预览用 "preview"
};

await webPrintPdf.printHtml(
  "<div><h1>收费凭证</h1><p>......</p></div>",
  pdfOptions,
  printOptions,
  extraOptions
);

4.2 按 URL 打印 PDF / 整页 HTML

javascript 复制代码
await webPrintPdf.printPdfByUrl(
  "https://your-intranet/api/report/123.pdf",
  pdfOptions,
  printOptions,
  extraOptions
);

await webPrintPdf.printHtmlByUrl(
  "https://your-intranet/oa/printPreview?id=10086",
  pdfOptions,
  printOptions,
  { ...extraOptions, requestTimeout: 30 }
);

4.3 批量静默打印

javascript 复制代码
await webPrintPdf.batchPrint(
  [
    { data: "<div>凭证一</div>", type: "printHtml" },
    { data: "https://your-intranet/a.pdf", type: "printPdfByUrl" },
  ],
  pdfOptions,
  printOptions,
  extraOptions
);

4.4 获取打印机与纸张(联调常用)

javascript 复制代码
const printers = await webPrintPdf.getPrinterList();
// 在 Mac 上,name 应对齐 CUPS 队列名

const papers = await webPrintPdf.getPrinterPapers({ name: printers[0].name });

客户端内置「运行示例」页提供可运行 Demo;Mac 联调时建议先用「打印测试」确认 CUPS 队列正常,再接入业务页。


5. macOS 实施与 PoC 清单

环节 建议
芯片架构 确认部署机器为 Apple Silicon(arm64) 还是 Intel(x64),安装对应构建的客户端
客户端 终端安装并运行 Web打印专家 macOS 版
CUPS 终端执行 lpstat -r,应看到 scheduler 运行;lpstat -a 列出目标队列
队列名 业务 printerNamelpstat -a 逐字一致(注意空格、编码)
前端 npm install web-print-pdf,WebSocket 连本机客户端
字体 HTML 片段尽量内联字体或使用系统已安装字体,避免 PDF 阶段「豆腐块」
验收 全程无浏览器打印框;lpstat -o 可见作业;打印日志 success=true

5.1 常见故障树(Mac)

复制代码
打印 API 报错
├─ WebSocket 连接失败 → 客户端未启动 / 端口被占 / 防火墙
├─ lpstat -r 失败 → 系统「打印机与扫描仪」未添加设备或 cupsd 未运行
├─ lp 退出非 0 → printerName 不存在;查看客户端打印日志 message
├─ PDF 引擎报错 → 安装包不完整,需重装对应架构客户端
└─ 任务进队列不出纸 → lpstat -o 看 reason;getAbnormalTask 类提醒查卡纸/缺纸

6. iOS / iPadOS:能力边界与可行路径

这一节必须直说:在 iPhone / iPad 的 Safari 或 WKWebView 里,目前没有与 macOS 桌面等价的「纯 Web 静默打印」方案。 这不是某个 npm 包没写好,而是 iOS 沙箱 + WebKit 策略 的结构限制。

6.1 iOS 上浏览器不能做什么

能力 iOS Safari / WKWebView macOS Safari
window.print() 弹 AirPrint 面板,无法跳过 弹系统打印面板
连接 127.0.0.1 WebSocket 到本机 Electron Mac 上那种「任意本地打印代理」生态 可以连 Web打印专家
安装 Web打印专家类桌面客户端 不可以(无 macOS 同款 Electron 常驻代理) 可以
直接调用 CUPS / lp 不可以 通过本地客户端可以
后台无 UI 出纸 不可以 通过本地客户端可以

iOS App Store 上的应用即使用 UIPrintInteractionController,也必须在用户可见的打印界面中确认(除非走企业 MDM 定制的私有方案,且不适用于普通 B/S 网页)。

6.2 为什么「把 Mac 客户端装到 iPad 上」行不通

Web打印专家依赖:

  • Electron 主进程 + 本地子进程(headless_shell、lp
  • 监听 localhost 端口的 WebSocket 服务
  • 文件系统工作目录写入 HTML/PDF

iPadOS 既不允许随意安装未签名的 Mac 应用,也没有开放给 Web 页面的「本地打印守护进程」接口。web-print-pdf 在 iPad 的 Safari 里无法找到可连接的 Layer 2。

6.3 混合办公下的四种可行架构

若业务必须同时覆盖 iPad 看单 + 桌面出纸,可按场景选型:

方案 A --- 桌面负责打印(推荐,改动最小)

  • iPad:浏览、审批、查询
  • Mac / Windows / 统信终端:安装 Web打印专家,业务 Web 同一套 web-print-pdf 代码
  • iPad 上不触发静默打印,或隐藏打印按钮 / 提示「请在工作站操作」

方案 B --- 服务端生成 PDF,iPad 仅 AirPrint

  • 后端生成 PDF URL,iPad 用 window.open(pdf) 或内嵌预览
  • 用户通过 AirPrint 手动选打印机------有一键预览,但非静默
  • 适合对静默无硬性要求的移动查阅场景

方案 C --- 原生 iOS 壳 + 受控打印(需 App 研发)

  • 企业自研 iOS App,内嵌 WKWebView 加载同一套 H5
  • 通过 JSBridge 把打印意图交给 Native;Native 侧用 UIPrintInteractionController 或 MDM 打印 SDK
  • 仍难做到完全无 UI 静默,但可缩短操作步骤、固定打印机

方案 D --- 远程推送到 Mac 柜面(复用远程打印)

  • iPad 仅发起业务指令;Mac 柜面页常开 Web打印专家并订阅远程 WebSocket
  • 服务端推送 printHtml / batchPrint 消息到柜面机执行
  • iPad 不直接出纸,静默发生在 Mac 端------适合「移动受理、固定窗口打印」的政务 / 银行布局

6.4 给产品 / 甲方的表述建议

可以明确写进方案书:

iOS / iPadOS 浏览器不支持生产级静默打印。 若移动侧必须无对话框出纸,需改为原生 App + MDM,或把出纸职责下沉到 Mac / Windows / 国产桌面终端,Web 层继续使用 web-print-pdf 与 Web打印专家。

避免承诺「安装某个 JS 库后 iPad 也能静默打」,这在当前 Apple 政策下无法兑现。


7. 跨 Apple + PC 的统一集成策略

复制代码
                    ┌── Windows → Web打印专家 → Sumatra / Spooler
                    │
  同一套 Vue/React ──┼── macOS   → Web打印专家 → lp / CUPS
  + web-print-pdf   │
                    ├── 统信 / 麒麟 → Web打印专家 → lp / CUPS
                    │
                    └── iPad / iPhone → 不直连客户端;走 A/B/C/D 替代架构

实践建议:

  1. navigator.userAgent 或业务登录岗位区分「移动浏览」与「打印工作站」,UI 上不要一刀切显示「静默打印」。
  2. 打印工作站统一安装 Web打印专家;printerNamegetPrinterList() 动态取,不要硬编码开发机队列名。
  3. Mac 与国产 Linux 虽都走 CUPS,仍应分别在目标 OS 上做 PoC------PPD、AirPrint 队列与国产驱动行为并不完全相同。

8. 小结

平台 纯 Web 静默打印 推荐方案
macOS 浏览器 alone:❌ web-print-pdf + Web打印专家(MAC 分支) ,CUPS lp 出纸,前端 API 与 Windows 一致
iOS / iPadOS Safari alone:❌ 桌面代理出纸、AirPrint 手动、原生壳或远程推 Mac 柜面

macOS 不是不能做网页静默打印,而是不能让网页独自完成------这一点与 Windows、统信 UOS 同源。 MAC 分支把最后一环换成了 CUPS 原生链路,并内置双架构 PDF 引擎,使 Apple 桌面也能接入同一套 web-print-pdf 生态。

iOS 则要在需求阶段就划清边界:移动 Web 负责看与办,出纸交给桌面客户端或企业级 Native / 远程方案------而不是在 Safari 里寻找一个不存在的「静默打印开关」。

具体 macOS 版本(Ventura / Sonoma / Sequoia)、芯片架构与打印机驱动组合以项目兼容性测试为准。