需登录权限的页面如何打印

常见痛点

管理后台的报表、工单详情、对账单等往往挂在需登录才能访问的 URL 上。你在浏览器里已登录,但直接调用 printHtmlByUrl(url) 时,客户端会用独立的无会话上下文去拉取页面,结果经常是登录页或 401,而不是业务内容。

解决办法:在 printHtmlByUrl第四个参数 extraOptions 里,把当前用户会话所需的 Cookie、Header 或 Storage 一并传给本地客户端,由它在抓取 URL 时携带这些凭证。

printHtmlByUrl 做了什么

printHtml 传入 HTML 字符串不同,printHtmlByUrl 只提供一个地址,由本地客户端完成后续步骤:

  1. 前端通过 WebSocket 把 urlpdfOptionsprintOptionsextraOptions 发给客户端
  2. 客户端用内置引擎请求该 URL,并按 extraOptions 附加 Cookies / HTTP 头 / Storage
  3. 将渲染后的页面排版为 PDF(受 pdfOptions 控制)
  4. printOptions 投递到打印机,或 action: 'preview' 仅返回预览地址

因此鉴权信息必须写在 extraOptions 里——浏览器标签页里的登录态不会自动同步到这一步。

extraOptions 鉴权字段

以下字段在 printHtmlByUrlprintPdfByUrlprintImageByUrl 及批量任务中均可使用(批量时写在单条任务的 extraOptions 内):

字段 类型 鉴权场景
cookies object 服务端 Session、JSESSIONID、token 写在 Cookie 的传统后台
httpHeaders object JWT / API Key:Authorization: 'Bearer …'值必须是字符串
localStorages object Vue/React SPA 把 access_token 存在 localStorage 时
sessionStorages object 仅当前标签会话有效的 token 或临时参数
requestTimeout number(秒) 鉴权后页面加载慢、含大量 XHR 时适当加大,默认 15
action 'print' | 'preview' 联调阶段建议先用 preview 确认抓到的是业务页而非登录页

客户端还会在页面注入 localStorage._printMode_ = 'true',便于业务脚本识别打印模式、隐藏导航栏等(可在页面内读取该键做布局收缩)。

适用于 Java、PHP、.NET 等把会话放在 Cookie 里的系统。在用户已登录的业务页点击打印时,把相关 Cookie 传给客户端:

import webPrintPdf from 'web-print-pdf';

/** 从 document.cookie 解析单个键(示例,可按项目封装) */
function getCookie(name) {
  const m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
  return m ? decodeURIComponent(m[1]) : '';
}

async function printReport(reportUrl) {
  const sessionId = getCookie('JSESSIONID'); // 或 SESSION、sid 等,与后端一致

  await webPrintPdf.printHtmlByUrl(
    reportUrl,
    { paperFormat: 'A4', printBackground: true, margin: { top: '12mm', bottom: '12mm' } },
    { paperFormat: 'A4' },
    {
      cookies: { JSESSIONID: sessionId },
      requestTimeout: 30,
      action: 'print'
    }
  );
}

示例二:Bearer Token(httpHeaders)

前后端分离、网关校验 Authorization 头时,从 Pinia / localStorage 取出 access_token,写入 httpHeaders

import webPrintPdf from 'web-print-pdf';
import { useUserStore } from '@/stores/user';

async function printProtectedPage(url) {
  const token = useUserStore().accessToken; // 或 localStorage.getItem('access_token')

  await webPrintPdf.printHtmlByUrl(
    url,
    { paperFormat: 'A4', printBackground: true },
    { paperFormat: 'A4' },
    {
      httpHeaders: {
        Authorization: `Bearer ${token}`
      },
      requestTimeout: 30,
      action: 'preview' // 联调时先预览,确认非登录页后再改为 print
    }
  );
}

示例三:localStorage / sessionStorage

若页面脚本在加载时从 localStorage 读 token 再发 API,仅传 Header 可能不够。可把键值注入被打印页:

await webPrintPdf.printHtmlByUrl(
  'https://erp.example.com/print/order/10086',
  { paperFormat: 'A4', printBackground: true },
  { paperFormat: 'A4' },
  {
    localStorages: {
      access_token: localStorage.getItem('access_token') || '',
      tenant_id: localStorage.getItem('tenant_id') || ''
    },
    cookies: { lang: 'zh-CN' },
    requestTimeout: 45
  }
);

示例四:Cookie + Header 组合

部分系统同时校验会话 Cookie 与 CSRF / 自定义头,可组合传入:

await webPrintPdf.printHtmlByUrl(
  orderDetailUrl,
  { paperFormat: 'A4', printBackground: true },
  { paperFormat: 'A4' },
  {
    cookies: {
      SESSION: getCookie('SESSION'),
      XSRF-TOKEN: getCookie('XSRF-TOKEN')
    },
    httpHeaders: {
      'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
      Authorization: `Bearer ${token}`
    },
    requestTimeout: 30
  }
);

凭证从哪来

  • 当前页已登录:打印按钮的回调里读 document.cookie、Vuex/Pinia、localStorage,即时组装 extraOptions(最常见)
  • 服务端签发:后端为「打印专用」生成短期 token 或带签名的 URL,前端只负责传给 printHtmlByUrl
  • 改用 printHtml:后端接口返回已渲染好的 HTML 字符串,走 printHtml,无需 URL 鉴权(见主文档

其他方案对比

方式 适用
printHtmlByUrl + extraOptions 已有可访问的报表 URL,希望少改后端、由前端带凭证打印
printHtml / printHtmlByBase64 后端直接输出 HTML 或 Base64,鉴权在 API 层完成
一次性打印链接 服务端生成带签名 query 的 URL,extraOptions 只需较短超时

安全建议

  • 不要把长期账号密码写进前端代码;优先短期 token、打印专用权限
  • 生产环境对报表 URL 使用 HTTPS;避免在日志里打印完整 Cookie / Token
  • 联调时用 action: 'preview' 检查 PDF 内容,确认未泄露其他用户数据
  • 失败提示统一用 formatPrintError,见前端的错误处理可参考这个标准处理

排障对照

现象 排查方向
PDF 里是登录页或空白 核对 Cookie 名、Domain 是否与后端一致;是否还需 httpHeaders;用 preview 查看
401 / 403 Token 是否过期;Authorization 值是否为字符串;是否缺少 CSRF 头
样式错乱、数据未加载 加大 requestTimeout;SPA 是否需要 localStorages;页面是否依赖 WebSocket
timeout 内网慢查询;默认 15 秒不够时提高 requestTimeout(应大于页内 XHR 超时)
免费下载客户端 打开在线样例