终于实现 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 进行交互的方法为:
- 把页面地址填入 package.json 文件 page 处;
- 然后启动 main.exe ,完毕。
启动后的 web 页面(也就是我们的页面)会自动注入一个 Sys 方法来与系统进行交互。
具体的打印逻辑如何实现
- 向页面注入 html2canvas
- 调用 html2canvas 把要打印的节点转换成图片
- 把要打印的图片发个 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...