0 代码静默打印任意 html 节点

终于实现 0 代码静默打印任意页面节点。

注:这里的 0 代码是指无需写任何与打印机控制逻辑,直接调用 tool.print() 即可。

需求

在当前的订单系统里添加一个打印功能,打印订单小票。不需要按 ctrl+p,不需要有预览弹窗。直接点击就直接从打印机出来小票。

实现结果

比如以后有某个系统需要对接打印机功能,这个系统是部署在服务器上的 web 页面,例如 baidu.com

那么我们只需把地址填入 package.json 文件的 page 处,运行 main.exe 即可,main.exe 会像一个浏览器一样打开此页面。

如果需要在打印某个元素时,只需要把元素传给 tool.print() 方法即可。

例如:

js 复制代码
window.tool.print(document.querySelector(`span`));

打印前也可调用 tool.view() 方法预览打印效果。

如何实现的

基本功能

由 node-escpos 来连接 usb 打印机实现基本的打印功能。

js 复制代码
const escpos = require("escpos");
escpos.USB = require("escpos-usb");
const device = new escpos.USB();
const options = { encoding: "GB18030" };
const printer = new escpos.Printer(device, options);

device.open(function (error) {
  printer.align("ct").text("这是要打印的文本", function (err) {
    this.cut();
    this.close();
  });
});

但是它需要有 node 环境,还需要安装上 escpos 和 escpos-usb 这些包。这些环境如果让用户安装,十分麻烦,而且就算安装了,一运行就是一个黑框框,有点吓人。

另外,escpos 只能使用自带的指令来进行打印,比如 printer.style / printer.text / printer.table ... 在这种情况下,如果我们先就已经写好页面了,那还行把页面重新用它这种写法来实现一遍。写的时候还看不了预览效果,要测试打印又非常浪费纸,效率相当低。

好在它提供了一个 printer.image 方法用于打印图片。那这就好办了,我们把已写好的页面直接转换成图片就好。

与已有的 web 系统进行交互

现在的前端系统比较多,前端系统基本都是跑在 web 浏览器中。那我们如何把浏览器中的数据传到刚写好的 node 中去呢?实现方式有很多总。由于最近在折腾 mian 这个项目,索性就用它来实现浏览器与系统/nodejs 进行交互。

为什么选择 mian 呢?

因为它被设计为对于前端来说,无需接触任何三方语言即可实现与系统进行交互。如果需要使用 nodejs ,也可以自动安装。

在 mian 中让 web 页面与 nodejs 进行交互的方法为:

  1. 把页面地址填入 package.json 文件 page 处;
  2. 然后启动 main.exe ,完毕。

启动后的 web 页面(也就是我们的页面)会自动注入一个 Sys 方法来与系统进行交互。

具体的打印逻辑如何实现

  1. 向页面注入 html2canvas
  2. 调用 html2canvas 把要打印的节点转换成图片
  3. 把要打印的图片发个 nodejs 调用打印机,完毕。

我们添加一个 preload.js 文件,这个文件会自动被注入到 web 页面中。

preload.js 文件内容为:

js 复制代码
window.tool = {
  view,
  print,
};

if (window.Sys) {
  new Sys().then(async (main) => {
    window.main = main;
    msg = new window.main.Msg();
    window.main.form.show();
  });
} else {
  console.warn(`请在宿主中打开,才可以访问系统功能`);
}

// 用于预览方法,在打印之前可以调用此方法来预览效果
async function view(source, target) {
  const canvasdom = document.createElement("canvas");
  const width = parseInt(window.getComputedStyle(source).width, 10);
  const height = parseInt(window.getComputedStyle(source).height, 10);
  const scaleBy = Number((203 / 96).toFixed(2)); // 转换打印机的打印比例
  canvasdom.width = width * scaleBy;
  canvasdom.height = height * scaleBy;
  const canvas = await html2canvas(source, {
    scale: scaleBy,
    useCORS: true,
  }).catch((err) => console.log(err));
  if (target) {
    target.innerHTML = ``;
    target.appendChild(canvas);
  }
  const url = canvas.toDataURL();
  return url;
}

// 直接打印节点
async function print(source) {
  const url = await view(source);
  console.log("打印", url);
  msg.emit(`img`, url);
}

解决了哪些问题

  • node-escpos 报错

需向 node-escpos 注入一个支持 windows 的方法,已通过 node-gyp 编译到 lib 中。

  • 打印图片不清晰

通过自动调整图片的分辨率。

  • 打印 html 不会结束

在测试阶段,在浏览器中按 ctrl+p 来打印内容时,就算要打印的面积很少,但是会一直打印下去,直到纸张用完,浏览器打印都会出问题,可能是浏览器与打印机的兼容问题,所以没有选择浏览器的静默模式打印(设置浏览器启动参数)。

  • 打印文字难写

原始的打印指令难写,现在可以直接打印写好的 html 布局。

已投入使用的环境

系统:

  • os: win11 / win10 / win7 x64
  • node: 13.14+

打印机:

  • 厂家: Xprinter 芯烨
  • 型号: XP-58IIH 热敏小票打印机
  • 接口: USB
  • 纸宽: 58mm

你有其他疑问?

  • Q: 不使用嵌入的方式直接在浏览器里能调用吗?

  • A: 可以。main 可以作为服务运行,会向外暴露一个 websoket url,你的前端页面链接此 url 即可通过它运行系统命令。

  • Q: 为什么需要 nodejs ?main 不能直接实现打印吗?

  • A: 能,但我想做一个 node 的集成示例。

  • Q: 这个 exe 能随便用吗?安全吗?

  • A: 相关介绍请参考 juejin.cn/post/730453...

相关推荐
你挚爱的强哥23 分钟前
【sgCreateCallAPIFunctionParam】自定义小工具:敏捷开发→调用接口方法参数生成工具
前端·javascript·vue.js
米老鼠的摩托车日记32 分钟前
【vue element-ui】关于删除按钮的提示框,可一键复制
前端·javascript·vue.js
猿饵块1 小时前
cmake--get_filename_component
java·前端·c++
大表哥61 小时前
在react中 使用redux
前端·react.js·前端框架
十月ooOO1 小时前
【解决】chrome 谷歌浏览器,鼠标点击任何区域都是 Input 输入框的状态,能看到输入的光标
前端·chrome·计算机外设
qq_339191141 小时前
spring boot admin集成,springboot2.x集成监控
java·前端·spring boot
pan_junbiao2 小时前
Vue使用代理方式解决跨域问题
前端·javascript·vue.js
明天…ling2 小时前
Web前端开发
前端·css·网络·前端框架·html·web
ROCKY_8172 小时前
web前端-HTML常用标签-综合案例
前端·html
海石2 小时前
从0到1搭建一个属于自己的工作流站点——羽翼渐丰(bpmn-js、Next.js)
前端·javascript·源码