在 Apple 生态里做 B/S 打印,最容易踩的两个坑是:
- macOS :Safari / Chrome 里
window.print()照样弹系统对话框;打印机队列名带 URL 编码或空格,和业务里存的字符串对不上就「静默失败」。 - iOS / iPadOS :业务方常问「iPad 上能不能像 Windows 一样一点就出纸?」------答案往往让人失望,但原因必须讲清楚,否则会在错误平台上投入错误方案。
本文分两部分:macOS 上可落地的完整静默打印链路 (与 Web打印专家 MAC 分支、web-print-pdf 对齐),以及 iOS 的能力边界与可行替代架构。
0. 先对齐:什么叫「网页静默打印」
与前几篇一致,这里指:
- 用户在 Web 系统里触发打印
- 不出现浏览器自带打印对话框(或仅少数角色保留预览)
- 任务按指定打印机、纸张、份数进入系统打印队列并出纸
- 有日志可查:是没下发,还是驱动 / 队列卡住
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 对外字段保持一致 (printerName、paperFormat、copies、duplexMode、colorful、scaleMode、pageRanges、bin 等),前端无需为 Mac 单独写一套参数------差异全部收在 printBackend.buildCommand() 里。
下发前会调用 ensureReady() 执行 lpstat -r,确认 CUPS 已就绪;若打印服务未启动,客户端会提示「请确认已安装并启动 CUPS」类错误,而不是让 lp 静默挂起。
3.2 系统打印机:version1.js(仅 darwin)
MAC 分支的 SystemPrinter 只适配 macOS,不再走 Windows 的 PowerShell / WMI / wmic 链路,而是:
lpstat -d--- 读系统默认打印机lpstat -v/-e/-p--- 枚举队列名lpoptions -p <queue> -l--- 读 PageSize、InputSlot 等驱动选项/private/etc/cups/ppd/--- 必要时解析 PPD 补全纸张尺寸- 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 start或node scripts/mac-chrome/preflight.js可自动下载到上述目录 - 打包阶段 :
node scripts/mac-pack-prep.js/yarn package:mac校验引擎完整性后再electron-builder - 运行时 :缺引擎直接报错,不会在线下载------避免柜面 / 办公网无外网时首次打印失败
打包 hook(beforeBuild / beforePack / afterPack)会校验架构匹配(Apple Silicon 包内不能塞 x64 的 shell),并在 afterPack 对 headless_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 列出目标队列 |
| 队列名 | 业务 printerName 与 lpstat -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 替代架构
实践建议:
- 用
navigator.userAgent或业务登录岗位区分「移动浏览」与「打印工作站」,UI 上不要一刀切显示「静默打印」。 - 打印工作站统一安装 Web打印专家;
printerName从getPrinterList()动态取,不要硬编码开发机队列名。 - 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)、芯片架构与打印机驱动组合以项目兼容性测试为准。