小票打印跨平台解决方案
一、背景
作为一家生鲜供应链平台,店铺在每个订单都有打印小票需求,这个是顾客的消费凭证,同时商家也会留档,因此对于我们平台来说,小票打印能力就必不可少。
打印需求端
以react-native为主体的iOS,安卓系统,手机端,平板端
小票业务场景
订单,码单,货单等
小票打印机设备类型

过去我们存在的痛点:
- 每种打印机类型的实现方案不一样,比如云打印是Java写了一套然后调易联云等接口,a4是前端vue,escpos等一体机是安卓开发
- 打印小票的业务场景比较多,每个业务都自己实现模板封装及打印逻辑,模板及触发逻辑不统一,维护成本大。
- 多种小票设备的适配,对于每种方案都要分别适配
其中最主要的痛点还是在于第一点,多种方案实现的不统一问题。由于不统一,导致开发和维护的成本成倍级增长。
针对以上痛点,小票打印技术方案需要解决的三个主要问题:
- iOS 、安卓需要提供小票样式设置和打印的能力,如何降低小票打印代码的维护和更新成本。
- 如何定制显示不同业务场景的小票内容:不同业务场景下的小票信息都不尽相同,比如订单,码单,对账单等不同小票样式不一样,并且支持商铺的自定义字段隐藏和展示,如何一种小票一套模板描述信息。
- 如何更灵活的适配多种多样的小票打印机,从连接方式上分为蓝牙连接、 WIFI 连接、云连接,从纸张样式分为 80mm 和 110mm 两种宽度。
打印方案流程图
我们的业务主要是RN,跨平台方案中,我们把他拆成了
- 打印机的编译库,这个是纯js,可以用于任何可以执行js的地方 (主要介绍)
- 打印机连接库,这个主要是RN连接app
编译库的业务边界
正常的打印流程如下:
- 业务触发打印需求
- SDK 容器接收订单数据与模板数据
- 将订单数据与模板数据融合得到融合数据
- 融合数据翻译成对应打印机指令
- 客户端传送打印机指令给打印机
- 打印机接收指令完成打印操作
其中步骤3与步骤4的功能就是打印库所负责的功能。订单数据再抽象就是业务数据,从而可以得到以下公式:
- 模板数据 + 业务数据 = 融合数据
- 融合数据 + 打印机信息 = 指令数据
模板数据 = 包含占位符的模板
业务数据 = 需要填入模板里的数据
融合数据 = 占位符已被填充的模板
编译库的设计
根据业务边界,我们可以将打印库进行分层:
- 模板渲染层:业务数据与模板的拼接融合
- 翻译层:将融合数据解析为打印指令
模板渲染层: 用Handlebars,用的HTML结合数据得到最终模板,因为是HTML语法,前端易维护,并且合成后仍然是HTML,可以方便预览最终效果
模板
<p><span style="width:100%;text-align:center;font-size:32px">{{origin.title}}</span></p>
数据
{
title:'xxx店铺'
}
合成后模板
<p><span style="width:100%;text-align:center;font-size:32px">xxx店铺</span></p>
为了对应后续的排版,我们定义了几种字体大小
**
* 模板字符大小
*/
export const TEMPLATE_FONT_SIZE = {
/**
* 16px
*/
FONT_SIZE_SM: "16px",
/**
* 20px
*/
FONT_SIZE_XS: "20px",
/**
* 24px
*/
FONT_SIZE_MD: "24px",
/**
* 28px
*/
FONT_SIZE_XM: "28px",
/**
* 32px
*/
FONT_SIZE_LG: "32px"
};
翻译层:因为对应不同类型的打印机,指令不一样,比如escpos类型的是escpos,而a4这种事HTML调用系统打印,易联云这种是自己的标签,所以我们统一讲HTML转换成了抽象语法树,然后用js对象去描述布局,我们将js操作成不同的类型的指令
典型难题
多行多列显示问题
在小票中存在货品,重量,单价以多行多列的形式展示,就涉及了换行,
品名 单价 数量 金额 商品名称(规格)¥5.00 2 份 ¥10.00
分析原因本质在于,品名这一列只占据了25%的空间,在商品名称过长的时候,挤压了后续的空间。所以针对这种情况,我们需要进行内容切割。 最终排版调整为:
品名 单价 数量 金额 商品名 ¥5.00 2份 ¥10.00 称(规 格)
该纸张一排可以打多少个英文字符,一般中文占两个英文字符,然后每一列占据的宽度,算出该列应该占据的字符数,在结合字符,左右对齐,然后在前面或者后面补上空格,1中文宽度 = 2空格宽度 = 2英文宽度,如果某个单元格换行了,就要先变成多行,找到最大行,其他小于最大行数的,都要用空格补上,最后形成一个二维数据,按行列遍历出来即可。
如果遇到某个单元格字号大的,安装倍率进行换算
图片问题
如果是浏览器或者node环境,用node-canvas绘制,RN环境,原生提供幕布,在上面绘制后,在转换
为什么图片需要进行灰度二值化处理?
因为对于票据打印机来说,图片像素点只有打与不打,所以不支持灰度与彩色图片。而我们的图片大多数都是灰度或者彩色图片,因此我们需要进行二值化处理。在 ESC/POS
协议中,打印图片的指令如下:

其中d1~dk
就是图片的数据块,并且值只有0
与1
,1表示打印该点,0为不打印该点。