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...

相关推荐
bysking32 分钟前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓1 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4111 小时前
无网络安装ionic和运行
前端·npm
理想不理想v1 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云1 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205871 小时前
web端手机录音
前端
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹1 小时前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
GIS程序媛—椰子2 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html