更新记录
1.0.0(2026-08-30)
- 支持 ESC/POS、TSPL/TSPL2 打印指令编译。
- 支持 Android、iOS BLE 与 TCP 打印。
- 支持 uni-app x、uni-app Vue 2 和 Vue 3。
- HarmonyOS 支持协议编译,暂不支持 BLE/TCP 传输。
平台兼容性
uni-app(5.24)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | × | × | √ | - | 5.0 | 13 | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | - | × | × |
uni-app x(5.24)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | 5.0 | 13 | 5.0.0 | × |
hans-thermal-printer
hans-thermal-printer 是适用于 uni-app 和 uni-app x 的热敏打印插件。它可以把结构化任务编译为 ESC/POS 或 TSPL/TSPL2 字节,并通过 Android/iOS BLE 或 TCP 完成扫描、连接、排队和发送。协议编译 API 也可以独立使用。
安装与兼容性
- 使用 HBuilderX
5.24+导入插件;付费授权版本需按 DCloud 规则通过云打包或自定义基座运行。 - 当前只直接编码 ASCII。中文或其他字符应由调用方先编码为 raw 字节,或转换为单色 raster。
| 宿主 | Android | iOS | HarmonyOS |
|---|---|---|---|
| uni-app x | ESC/POS、TSPL、BLE、TCP | ESC/POS、TSPL、BLE、TCP | ESC/POS、TSPL;BLE/TCP 返回 9092011 |
| uni-app Vue 2/3 | ESC/POS、TSPL、BLE、TCP | ESC/POS、TSPL、BLE、TCP | 不支持 |
Web、小程序和快应用不支持。Android/iOS BLE + ESC/POS 已在 Xprinter XP-58IIQ 实际出纸;profile 和已验证机型不代表其他打印机型号、字库、切刀或标签定位一定兼容,接入新型号时仍应逐项实测。
导入 API 和类型
只从模块根目录导入,不要直接引用 utssdk 内部文件:
import {
ThermalPrinter,
compileEscPos,
compileTspl,
getProtocolCapabilities,
printDataToHex,
validateEscPos,
validateTspl,
EscPosCompileOptions,
EscPosOperation,
PrintCompileOutcome,
PrintCompileResult,
PrintDataToHexOutcome,
PrintIssue,
PrintValidationResult,
ProtocolCapabilitiesOutcome,
RasterData,
ThermalPrinterError,
ThermalPrinterStateSnapshot,
ThermalPrinterTaskEvent,
BleConnectOptions,
BleScanDevice,
BleScanOptions,
PrinterSendOptions,
TcpConnectOptions,
TsplCompileOptions,
TsplOperation,
} from '@/uni_modules/hans-thermal-printer'
常用公共类型:
| 类型 | 用途 |
|---|---|
EscPosCompileOptions / EscPosOperation |
ESC/POS 任务和操作 |
TsplCompileOptions / TsplOperation |
TSPL 标签任务和操作 |
RasterData |
两种协议共用的单色位图 |
PrintCompileOutcome / PrintCompileResult |
编译结果、字节和 warnings |
PrintValidationResult / PrintIssue |
参数校验结果 |
ThermalPrinterError |
编译或查询失败信息 |
ThermalPrinterStateSnapshot / ThermalPrinterTaskEvent |
连接状态和发送任务事件 |
BleScanDevice |
BLE 扫描结果 |
BleScanOptions / BleConnectOptions |
BLE 扫描、连接和写特征选择参数 |
TcpConnectOptions |
TCP host、port 和连接超时 |
PrinterSendOptions |
发送超时、BLE 写模式、分包和间隔 |
PrintDataToHexOutcome |
十六进制诊断结果 |
ProtocolCapabilitiesOutcome |
profile 能力查询结果 |
uni-app x 类型化调用
为了兼容 uni-app x 与传统 uni-app 的数组桥接,跨宿主函数使用动态签名。uni-app x 页面建议在调用边界把强类型 options 转为动态对象,并把返回值恢复为导出的 DTO:
function toApiOptions(value: any): UTSJSONObject {
return JSON.parse<UTSJSONObject>(JSON.stringify(value) ?? '{}') as UTSJSONObject
}
function toApiBytes(values: number[]): Array<any> {
const result: Array<any> = []
let index = 0
while (index < values.length) {
result.push(values[index] as any)
index++
}
return result
}
function asCompileOutcome(value: any): PrintCompileOutcome {
const normalized = new UTSJSONObject(value)
if (normalized['result'] != null) {
const compiled = new UTSJSONObject(normalized['result'])
if (compiled['warnings'] == null) compiled.set('warnings', [])
normalized.set('result', compiled)
}
return JSON.parse<PrintCompileOutcome>(
JSON.stringify(normalized) ?? '{"result":null,"error":null}',
) as PrintCompileOutcome
}
function asValidation(value: any): PrintValidationResult {
const normalized = new UTSJSONObject(value)
if (normalized['errors'] == null) normalized.set('errors', [])
if (normalized['warnings'] == null) normalized.set('warnings', [])
return JSON.parse<PrintValidationResult>(
JSON.stringify(normalized) ?? '{"valid":false,"errors":[],"warnings":[]}',
) as PrintValidationResult
}
function asHexOutcome(value: any): PrintDataToHexOutcome {
return JSON.parse<PrintDataToHexOutcome>(
JSON.stringify(value) ?? '{"value":null,"error":null}',
) as PrintDataToHexOutcome
}
uni-app Vue 2/3 的 JavaScript 调用可直接读取返回对象,不需要上述 UTS 类型恢复函数。
权限与首次调用
- Android 已声明网络、蓝牙扫描/连接权限,以及 Android 11 及以下系统所需的位置权限。首次扫描或连接会发起系统授权并返回
9092002,用户同意后应重新调用startBleScan()或connectBle()。 - iOS 已声明蓝牙和本地网络用途。首次使用 BLE 或局域网 TCP 时,系统可能弹出授权提示。
- 扫描前应确认系统蓝牙已打开。Android 11 及以下系统还可能要求打开定位服务。
- 权限被拒绝后不要循环请求;应提示用户前往系统设置授权。
编译 ESC/POS 小票
const operations: EscPosOperation[] = [
{ type: 'initialize' },
{ type: 'align', align: 'center' },
{ type: 'textStyle', bold: true, widthScale: 2, heightScale: 2 },
{ type: 'text', text: 'PICKUP 42' },
{ type: 'newLine' },
{ type: 'qrCode', text: 'PICKUP42', qrSize: 5, qrErrorLevel: 'M' },
{ type: 'feed', lines: 3 },
{ type: 'cut', cutMode: 'partial' },
]
const options: EscPosCompileOptions = {
profileId: 'escpos-generic',
operations: operations,
}
const validation: PrintValidationResult = asValidation(
validateEscPos(toApiOptions(options)),
)
if (!validation.valid) {
console.error(validation.errors[0].code, validation.errors[0].path)
}
const outcome: PrintCompileOutcome = asCompileOutcome(
compileEscPos(toApiOptions(options)),
)
if (outcome.error != null) {
const error: ThermalPrinterError = outcome.error
console.error(error.errCode, error.errMsg, error.data?.path)
}
const receipt: PrintCompileResult | null = outcome.result
if (receipt != null) {
const hex = asHexOutcome(printDataToHex(toApiBytes(receipt.data)))
console.log(receipt.byteLength.toString(), hex.value ?? '')
// 将 receipt.data 交给 BLE、TCP、USB 或串口传输层。
}
支持初始化、ASCII 文本、换行、走纸、对齐、粗体、下划线、字号、QR Model 2、Code 128、EAN-13、Code 39、单色 raster、全切/半切和 raw。
编译 TSPL 标签
const operations: TsplOperation[] = [
{ type: 'text', x: 2, y: 2, font: '0', text: 'PICKUP 42', xScale: 2, yScale: 2 },
{ type: 'box', x: 1, y: 1, width: 48, height: 28, thickness: 0.5 },
{ type: 'barcode', x: 5, y: 20, barcodeType: 'code128', text: '123456' },
]
const options: TsplCompileOptions = {
width: 50,
height: 30,
unit: 'mm',
dpi: 203,
gap: 2,
copies: 2,
operations: operations,
}
const validation: PrintValidationResult = asValidation(
validateTspl(toApiOptions(options)),
)
const outcome: PrintCompileOutcome = asCompileOutcome(
compileTspl(toApiOptions(options)),
)
支持 SIZE、GAP/BLINE、DIRECTION、REFERENCE、SPEED、DENSITY、CLS、PRINT,以及 TEXT、BAR、BOX、QRCODE、BARCODE、BITMAP 和 raw。坐标单位支持 mm/dot,DPI 支持 203/300;毫米转点使用 round(mm * dpi / 25.4)。
TSPL 默认拒绝可证明的越界。设置 allowOverflow: true 后会生成 CONTENT_OUT_OF_BOUNDS warning 并继续。文本、QR 和条码的完整尺寸依赖设备字体或编码结果,因此可能返回 UNVERIFIED_CONTENT_BOUNDS warning。
使用 RasterData
const logo: RasterData = {
widthDots: 9,
heightDots: 2,
rowBytes: 2,
data: [0xAA, 0x80, 0x55, 0x00],
}
const operation: EscPosOperation = {
type: 'rasterImage',
rasterImage: logo,
}
RasterData 使用行优先、MSB-first 格式,1 为黑点,0 为白点:
rowBytes必须等于ceil(widthDots / 8)。data.length必须等于rowBytes * heightDots。- 每个字节必须是
0..255的整数。 - 每行末尾未使用的 padding 位必须为
0。
插件不解码 PNG/JPEG/PDF,也不做缩放、阈值或抖动处理。
错误和 warnings
validateEscPos() 和 validateTspl() 返回完整的 PrintValidationResult,用于在编译前展示全部 errors 和 warnings。
compileEscPos()、compileTspl()、printDataToHex() 和 capability 查询返回显式 outcome,不通过同步异常表示可预期的参数错误:
- 成功值位于
result或value。 - 失败信息位于
error: ThermalPrinterError。 - 错误码范围为
9091xxx。 error.data.issueCode和error.data.path提供结构化定位。PrintCompileResult.warnings保留不阻断输出的兼容性提示。
常见错误码:
| 错误码 | 含义与处理 |
|---|---|
9091xxx |
协议、参数、编码、位图或输出大小错误;查看 error.data.issueCode/path |
9092001 |
蓝牙未开启 |
9092002 |
权限未授予;授权后重新调用 |
9092006 |
配置的 BLE Service 不存在 |
9092007 |
没有可写特征 |
9092008 |
存在多个可写特征;显式填写 serviceId 和 writeCharacteristicId |
9092009 |
当前没有可用连接 |
9092010 |
TCP 连接失败;检查 host、port 和网络 |
9092011 |
当前平台不支持 BLE/TCP 传输 |
9093xxx |
队列、发送、超时、取消或连接中断错误 |
查询 profile
const capabilities = JSON.parse<ProtocolCapabilitiesOutcome>(
JSON.stringify(getProtocolCapabilities('escpos', 'escpos-generic'))
?? '{"result":null,"error":null}',
) as ProtocolCapabilitiesOutcome
console.log(capabilities.result?.paperWidthDots.toString() ?? '-')
内置 profile:
escpos-generictspl-generic-203tspl-generic-300
profile 说明编译方言和参数上限,不代表具体打印机型号已经验证。
Raw、日志与输出限制
raw 只绕过命令语义校验,仍受总输出上限约束,并返回 RAW_BYPASSES_VALIDATION warning。不要把不受信任的输入直接作为 raw 命令。
日志默认关闭:
import { isLogEnabled, setLogEnabled } from '@/uni_modules/hans-thermal-printer'
setLogEnabled(true)
console.log(isLogEnabled().toString())
开启后,UTS 层会通过 console.log 记录 API、输出长度和传输事件;Android 原生 BLE 层会使用 Log.d 输出连接、服务发现、特征选择和每个分包的状态,tag 为 hans-thermal-printer。这些诊断日志只记录 UUID、状态、长度等元数据,不记录完整打印内容。
通过 HBuilderX CLI 运行 Android 项目时,可以用下面的命令查看同一份原生日志:
"<HX_CLI_PATH>" logcat app-android --project <project> --deviceId <adb-serial>
在终端中筛选 hans-thermal-printer 或 BluetoothGatt,分别查看插件诊断和 Android GATT 回调。默认输出上限为 256 KiB,硬上限为 1 MiB,单任务最多包含 1024 个 operation。
传输 API
| 方法 | 用途 |
|---|---|
startBleScan() / stopBleScan() |
开始或停止 BLE 扫描 |
connectBle() / connectTcp() |
连接一台 BLE 或 TCP 打印机 |
sendData() |
发送已经生成的原始字节 |
printEscPos() / printTspl() |
编译任务并加入发送队列,立即返回 taskId |
onStateChange() / offStateChange() |
监听或移除实例状态监听器 |
onTaskEvent() / offTaskEvent() |
监听或移除当前实例的任务监听器 |
getState() |
获取当前连接、队列和任务快照 |
cancel() |
取消指定任务 |
disconnect() / destroy() |
断开连接;页面销毁时释放全部资源 |
状态事件对应 ThermalPrinterStateSnapshot,state 可能为 idle/scanning/connecting/discovering/ready/sending/disconnected/error/destroyed。任务事件对应 ThermalPrinterTaskEvent,依次产生 queued,可选的 compiling,然后是 sending,最终进入 written/failed/cancelled。动态回调可通过 JSON.stringify/JSON.parse<T> 恢复为导出的 DTO。
通过 TCP 连接和打印
import { ThermalPrinter } from '@/uni_modules/hans-thermal-printer'
const printer = new ThermalPrinter()
printer.onStateChange((event: UTSJSONObject) => {
const state = JSON.parse<ThermalPrinterStateSnapshot>(
JSON.stringify(event) ?? '{}',
) as ThermalPrinterStateSnapshot
console.log('printer state', state.state)
})
printer.onTaskEvent((event: UTSJSONObject) => {
const task = JSON.parse<ThermalPrinterTaskEvent>(
JSON.stringify(event) ?? '{}',
) as ThermalPrinterTaskEvent
console.log('task event', task.taskId.toString(), task.state)
})
const tcpOptions: TcpConnectOptions = {
host: '192.168.1.80',
port: 9100,
connectTimeoutMs: 15000,
}
printer.connectTcp(toApiOptions(tcpOptions), (event: UTSJSONObject) => {
if (event['state'] != 'ready') return
printer.printEscPos({
operations: [
{ type: 'initialize' },
{ type: 'text', text: 'PICKUP 42' },
{ type: 'newLine' },
{ type: 'feed', lines: 3 },
],
}, { sendTimeoutMs: 30000 }, (task: UTSJSONObject) => {
console.log('terminal task state', task['state'])
})
})
TCP 没有通用默认端口,必须使用打印机说明或配置页给出的 host/port。Android 在字节写入并 flush 后、iOS 在 contentProcessed 后产生 written。
通过 BLE 扫描和打印
const printer = new ThermalPrinter()
const scanOptions: BleScanOptions = { timeoutMs: 10000 }
printer.startBleScan(toApiOptions(scanOptions), (event: UTSJSONObject) => {
const device = event['device'] as UTSJSONObject | null
if (device == null) return
const scanDevice = JSON.parse<BleScanDevice>(
JSON.stringify(device) ?? '{}',
) as BleScanDevice
printer.stopBleScan()
const connectOptions: BleConnectOptions = {
deviceId: scanDevice.deviceId,
serviceId: '0000ffe0-0000-1000-8000-00805f9b34fb',
writeCharacteristicId: '0000ffe2-0000-1000-8000-00805f9b34fb',
autoDiscover: false,
withoutResponse: false,
segmentSize: 20,
segmentInterval: 20,
}
printer.connectBle(toApiOptions(connectOptions), (state: UTSJSONObject) => {
if (state['state'] != 'ready') return
printer.sendData([0x1B, 0x40, 0x0A], { sendTimeoutMs: 30000 })
})
})
写特征选择顺序为显式 UUID,然后才是自动发现。自动发现只有一个可写候选时成功;没有候选返回 9092007,多个候选返回 9092008。segmentSize: 20 和 segmentInterval: 20 只是保守起点,应按设备手册和实测调整。
iOS 扫描结果可能暂时没有名称,不要只按 name 过滤设备;优先使用打印机公开的广播 Service 过滤,并使用扫描结果中的 deviceId 连接。
队列、取消和完成语义
- 每个
ThermalPrinter实例只管理一个活动连接和一条 FIFO 队列;多台打印机应创建多个实例。 sendData()、printEscPos()、printTspl()立即返回taskId,任务 listener 接收queued -> compiling/sending -> written/failed/cancelled。cancel(taskId)可移除排队任务;发送中取消会关闭当前传输,结果标记mayHavePrinted=true。- 失败任务默认不重发。发送中失败可能已经被设备接收一部分,自动重试会造成重复出纸。
disconnect()释放连接,destroy()还会释放 listener、回调和原生资源。written只表示全部字节已被本地 BLE/TCP 传输层接受,不表示打印机已经出纸。
协议编译成功只证明生成了符合当前 profile 的字节;连接或 written 也不证明具体打印机的字库、切刀、二维码、条码、走纸精度或打印结果可用。这些能力仍需在目标机型上验证。

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 392
赞赏 0
下载 12542089
赞赏 1947
赞赏
京公网安备:11010802035340号