已知Sunmi打印机支持的方式 蓝牙、局域网、usb 连接, 这里主要是讲述usb连接的方式,终端控制打印指令
- 第一步对接Sunmi打印sdk,根据打印机型号来配对不同的sdk
这里以V2 云打印机为例
kt
// android/app/build.gradle
dependencies {
implementation('com.sunmi:external-printerlibrary2:latest.release')
}
- 第二步,暴露RN桥接方法,暴露sdk 提供的api 方法,在 js 端调用(方便不懂kotlin安卓原生的同学后续迭代)
kt
//android/app/src/main/java/com/app/SimplePrinterModule.kt
package com.app
import android.util.Log
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments
import com.sunmi.externalprinterlibrary2.ConnectCallback
import com.sunmi.externalprinterlibrary2.ResultCallback
import com.sunmi.externalprinterlibrary2.SearchCallback
import com.sunmi.externalprinterlibrary2.SearchMethod
import com.sunmi.externalprinterlibrary2.SunmiPrinterManager
import com.sunmi.externalprinterlibrary2.printer.CloudPrinter
import com.sunmi.externalprinterlibrary2.style.AlignStyle
import com.sunmi.externalprinterlibrary2.style.BarcodeType
import com.sunmi.externalprinterlibrary2.style.CloudPrinterStatus
import com.sunmi.externalprinterlibrary2.style.ErrorLevel
import com.sunmi.externalprinterlibrary2.style.HriStyle
import com.sunmi.externalprinterlibrary2.style.UnderlineStyle
import com.sunmi.externalprinterlibrary2.style.ImageAlgorithm
import com.sunmi.externalprinterlibrary2.style.EncodeType
/**
* 商米云打印机模块
* 基于 externalprinterlibrary2 SDK 实现
*
* 主要功能:
* 1. USB/网络/蓝牙打印机搜索和连接
* 2. 打印样式控制(对齐、字体、加粗等)
* 3. 打印内容(文本、图片、二维码、条形码等)
* 4. 打印控制(换行、走纸、切纸等)
*
* 参考文档:https://developer.sunmi.com/docs/zh-CN/cdixeghjk491/xfzxeghjk491
*/
class SimplePrinterModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private val TAG = "SimplePrinterModule"
private var cloudPrinter: CloudPrinter? = null
private var connectPromise: Promise? = null
private var printPromise: Promise? = null
private var disconnectPromise: Promise? = null
override fun getName(): String {
return "SimplePrinter"
}
/**
* 搜索并连接 USB 打印机
*
* 搜索方式:USB
* 连接成功后会自动停止搜索
*
* @param promise Promise 对象,返回连接结果
*/
@ReactMethod
fun connectUSBPrinter(promise: Promise) {
Log.d(TAG, "开始搜索 USB 打印机...")
connectPromise = promise
try {
SunmiPrinterManager.getInstance().searchCloudPrinter(
reactApplicationContext,
SearchMethod.USB,
object : SearchCallback {
override fun onFound(printer: CloudPrinter) {
val printerInfo = printer.getCloudPrinterInfo()
Log.d(TAG, "找到打印机: ${printerInfo.name}")
cloudPrinter = printer
// 停止搜索
SunmiPrinterManager.getInstance().stopSearch(
reactApplicationContext,
SearchMethod.USB
)
// 连接打印机
connectToPrinter(printer)
}
}
)
} catch (e: Exception) {
Log.e(TAG, "搜索失败", e)
promise.reject("SEARCH_ERROR", e.message ?: "搜索失败")
}
}
private fun connectToPrinter(printer: CloudPrinter) {
Log.d(TAG, "正在连接打印机...")
printer.connect(
reactApplicationContext,
object : ConnectCallback {
override fun onConnect() {
Log.d(TAG, "打印机连接成功")
val result = Arguments.createMap()
result.putBoolean("success", true)
result.putString("message", "打印机连接成功")
result.putString("name", printer.getCloudPrinterInfo().name)
connectPromise?.resolve(result)
connectPromise = null
}
override fun onFailed(error: String) {
Log.e(TAG, "打印机连接失败: $error")
connectPromise?.reject("CONNECT_ERROR", error)
connectPromise = null
}
override fun onDisConnect() {
Log.d(TAG, "打印机断开连接")
}
}
)
}
/**
* 断开打印机连接
*
* 释放打印机资源,断开连接
*
* @param promise Promise 对象,返回断开结果
*/
@ReactMethod
fun disconnect(promise: Promise) {
Log.d(TAG, "断开打印机连接...")
disconnectPromise = promise
if (cloudPrinter == null) {
val result = Arguments.createMap()
result.putBoolean("success", true)
result.putString("message", "打印机未连接")
promise.resolve(result)
return
}
try {
cloudPrinter!!.release(reactApplicationContext)
cloudPrinter = null
val result = Arguments.createMap()
result.putBoolean("success", true)
result.putString("message", "已断开连接")
disconnectPromise?.resolve(result)
disconnectPromise = null
} catch (e: Exception) {
Log.e(TAG, "断开连接异常", e)
promise.reject("DISCONNECT_ERROR", e.message ?: "断开连接失败")
disconnectPromise = null
}
}
/**
* 获取打印机连接状态和信息
*
* @param promise Promise 对象,返回打印机信息
*/
@ReactMethod
fun getPrinterInfo(promise: Promise) {
try {
val result = Arguments.createMap()
if (cloudPrinter == null) {
result.putBoolean("connected", false)
result.putString("message", "打印机未连接")
promise.resolve(result)
return
}
val printer = cloudPrinter!!
val isConnected = printer.isConnected()
result.putBoolean("connected", isConnected)
if (isConnected) {
val printerInfo = printer.getCloudPrinterInfo()
result.putString("name", printerInfo.name)
// 注意:sn 和 model 可能不在 CloudPrinterInfo 中,根据实际 SDK 调整
// result.putString("sn", printerInfo.sn)
// result.putString("model", printerInfo.model ?: "")
result.putString("message", "打印机已连接")
} else {
result.putString("message", "打印机未连接")
}
promise.resolve(result)
} catch (e: Exception) {
Log.e(TAG, "获取打印机信息异常", e)
promise.reject("ERROR", e.message ?: "获取信息失败")
}
}
/**
* 检查打印机连接状态
*
* @return CloudPrinter? 如果已连接返回打印机对象,否则返回 null
*/
private fun checkPrinterConnection(): CloudPrinter? {
if (cloudPrinter == null || !cloudPrinter!!.isConnected()) {
return null
}
return cloudPrinter
}
/**
* 执行同步打印操作的辅助方法
* 统一处理连接检查和 Promise resolve
*
* @param promise Promise 对象
* @param action 要执行的打印操作
*/
private fun executeSyncOperation(promise: Promise, action: (CloudPrinter) -> Unit) {
try {
val printer = checkPrinterConnection()
if (printer == null) {
promise.reject("NOT_CONNECTED", "打印机未连接,请先连接打印机")
return
}
action(printer)
promise.resolve(null) // 同步操作立即 resolve,不返回结果
} catch (e: Exception) {
Log.e(TAG, "操作异常", e)
promise.reject("ERROR", e.message ?: "操作失败")
}
}
// ========== 打印样式控制方法 ==========
/**
* 重新初始化布局样式
* 重置所有样式设置(对齐、字体大小、加粗、下划线等)为默认值
*/
@ReactMethod
fun initStyle(promise: Promise) {
executeSyncOperation(promise) {
it.initStyle()
// 设置编码模式为 UTF-8
it.setEncodeMode(EncodeType.UTF_8)
}
}
/**
* 设置编码模式
*
* @param encodeType 编码类型:0=UTF_8, 其他值根据 SDK 定义
*/
@ReactMethod
fun setEncodeMode(encodeType: Int, promise: Promise) {
executeSyncOperation(promise) {
// 根据传入的参数设置编码模式,默认使用 UTF_8
val encode = when (encodeType) {
0 -> EncodeType.UTF_8
else -> {
// 尝试通过枚举值查找
try {
val values = EncodeType.values()
values.firstOrNull { it.name.equals("UTF_8", ignoreCase = true) }
?: values.getOrNull(encodeType)
?: EncodeType.UTF_8
} catch (e: Exception) {
EncodeType.UTF_8
}
}
}
it.setEncodeMode(encode)
}
}
/**
* 设置可打印区域宽度
*
* @param printWidth 打印宽度(单位:点)
*/
@ReactMethod
fun setPrintWidth(printWidth: Int, promise: Promise) {
executeSyncOperation(promise) { it.setPrintWidth(printWidth) }
}
/**
* 设置左边距的宽度
*
* @param leftSpace 左边距宽度(单位:点)
*/
@ReactMethod
fun setLeftSpace(leftSpace: Int, promise: Promise) {
executeSyncOperation(promise) { it.setLeftSpace(leftSpace) }
}
/**
* 设置行间距
*
* @param lineSpacing 行间距(单位:点)
*/
@ReactMethod
fun setLineSpacing(lineSpacing: Int, promise: Promise) {
executeSyncOperation(promise) { it.setLineSpacing(lineSpacing) }
}
/**
* 设置反白模式
*
* @param enable true=开启反白,false=关闭反白
*/
@ReactMethod
fun setBlackWhiteReverseMode(enable: Boolean, promise: Promise) {
executeSyncOperation(promise) { it.setBlackWhiteReverseMode(enable) }
}
/**
* 设置下划线模式
*
* @param mode 下划线模式:0=关闭, 1=单下划线, 2=双下划线
*/
@ReactMethod
fun setUnderlineMode(mode: Int, promise: Promise) {
executeSyncOperation(promise) {
// 根据实际 SDK,使用枚举值数组索引或名称匹配
val underlineStyle = try {
val values = UnderlineStyle.values()
when (mode) {
0 -> values.firstOrNull { it.name.equals("NONE", ignoreCase = true) || it.name.equals("OFF", ignoreCase = true) } ?: values.getOrNull(0) ?: values[0]
1 -> values.firstOrNull { it.name.equals("SINGLE", ignoreCase = true) || it.name.equals("ON", ignoreCase = true) } ?: values.getOrNull(1) ?: values[0]
2 -> values.firstOrNull { it.name.contains("DOUBLE", ignoreCase = true) } ?: values.getOrNull(2) ?: values[0]
else -> values[0]
}
} catch (e: Exception) {
// 如果出错,使用第一个枚举值
UnderlineStyle.values()[0]
}
it.setUnderlineMode(underlineStyle)
}
}
/**
* 设置加粗模式
*
* @param enable true=开启加粗,false=关闭加粗
*/
@ReactMethod
fun setBoldMode(enable: Boolean, promise: Promise) {
executeSyncOperation(promise) { it.setBoldMode(enable) }
}
/**
* 设置倒置模式
*
* @param enable true=开启倒置,false=关闭倒置
*/
@ReactMethod
fun setUpsideDownMode(enable: Boolean, promise: Promise) {
executeSyncOperation(promise) { it.setUpsideDownMode(enable) }
}
/**
* 设置打印文本倍高和倍宽
*
* @param characterWidth 字符宽度倍数(1-8)
* @param characterHeight 字符高度倍数(1-8)
*/
@ReactMethod
fun setCharacterSize(characterWidth: Int, characterHeight: Int, promise: Promise) {
executeSyncOperation(promise) { it.setCharacterSize(characterWidth, characterHeight) }
}
/**
* 设置 Latin 字符的大小(字库为矢量字库有效)
*
* @param size 字符大小,4-255像素点,默认Latin字母12像素
*/
@ReactMethod
fun setAsciiSize(size: Int, promise: Promise) {
executeSyncOperation(promise) { it.setAsciiSize(size) }
}
/**
* 设置中日韩字符的大小(字库为矢量字库有效)
*
* @param size 字符大小,4-255像素点,默认其他字符24像素
*/
@ReactMethod
fun setCjkSize(size: Int, promise: Promise) {
executeSyncOperation(promise) { it.setCjkSize(size) }
}
/**
* 设置其他字符的大小(字库为矢量字库有效)
*
* @param size 字符大小,4-255像素点,默认其他字符24像素
*/
@ReactMethod
fun setOtherSize(size: Int, promise: Promise) {
executeSyncOperation(promise) { it.setOtherSize(size) }
}
/**
* 选择 Latin 字符的字库
*
* @param select 选择当前使用的字库,0默认使用点阵字库(点阵字库比较清晰但不能自由调整大小),1使用内置矢量字库,大于127表示使用其他第三方字库,需要预置进打印设备
*/
@ReactMethod
fun selectAsciiCharFont(select: Int, promise: Promise) {
executeSyncOperation(promise) { it.selectAsciiCharFont(select) }
}
/**
* 选择中日韩字符的字库
*
* @param select 选择当前使用的字库,0默认使用点阵字库(点阵字库比较清晰但不能自由调整大小),1使用内置矢量字库,大于127表示使用其他第三方字库,需要预置进打印设备
*/
@ReactMethod
fun selectCjkCharFont(select: Int, promise: Promise) {
executeSyncOperation(promise) { it.selectCjkCharFont(select) }
}
/**
* 选择其他字符的字库
*
* @param select 选择当前使用的字库,0默认使用点阵字库(点阵字库比较清晰但不能自由调整大小),1使用内置矢量字库,大于127表示使用其他第三方字库,需要预置进打印设备
*/
@ReactMethod
fun selectOtherCharFont(select: Int, promise: Promise) {
executeSyncOperation(promise) { it.selectOtherCharFont(select) }
}
/**
* 横向跳格数(即tab数)
*
* @param n 跳格数量
*/
@ReactMethod
fun horizontalTab(n: Int, promise: Promise) {
executeSyncOperation(promise) { it.horizontalTab(n) }
}
/**
* 设置绝对打印位置
*
* @param horizontalPosition 绝对水平位置(单位:点)
*/
@ReactMethod
fun setAbsolutePrintPosition(horizontalPosition: Int, promise: Promise) {
executeSyncOperation(promise) { it.setAbsolutePrintPosition(horizontalPosition) }
}
/**
* 设置相对打印位置
*
* @param horizontalPosition 相对水平位置(单位:点)
*/
@ReactMethod
fun setRelativePrintPosition(horizontalPosition: Int, promise: Promise) {
executeSyncOperation(promise) { it.setRelativePrintPosition(horizontalPosition) }
}
/**
* 设置对齐方式
*
* @param alignment 对齐方式:0=LEFT, 1=CENTER, 2=RIGHT
*/
@ReactMethod
fun setAlignment(alignment: Int, promise: Promise) {
executeSyncOperation(promise) {
val alignStyle = when (alignment) {
0 -> AlignStyle.LEFT
1 -> AlignStyle.CENTER
2 -> AlignStyle.RIGHT
else -> AlignStyle.LEFT
}
it.setAlignment(alignStyle)
}
}
// ========== 打印内容方法 ==========
/**
* 打印文本
* 打印文本并自动换行
*
* @param text 要打印的文本内容
*/
@ReactMethod
fun printText(text: String, promise: Promise) {
executeSyncOperation(promise) { it.printText(text) }
}
/**
* 追加文本
* 在当前行追加文本,不换行
*
* @param text 要追加的文本内容
*/
@ReactMethod
fun appendText(text: String, promise: Promise) {
executeSyncOperation(promise) { it.appendText(text) }
}
/**
* 打印二维码
*
* @param content 二维码内容
* @param size 二维码大小(1-16,推荐 9)
* @param errorLevel 错误纠正级别:0=L(低), 1=M(中), 2=Q(中高), 3=H(高)
*/
@ReactMethod
fun printQrcode(content: String, size: Int, errorLevel: Int, promise: Promise) {
executeSyncOperation(promise) {
val level = when (errorLevel) {
0 -> ErrorLevel.L
1 -> ErrorLevel.M
2 -> ErrorLevel.Q
3 -> ErrorLevel.H
else -> ErrorLevel.L
}
it.printQrcode(content, size, level)
}
}
/**
* 打印条形码
*
* @param content 条形码内容
* @param barcodeType 条形码类型:0=CODE128, 1=CODE39, 2=EAN13, 3=EAN8, 4=ITF, 5=UPC_A, 6=UPC_E
* @param width 条形码宽度(单位:点,推荐 200)
* @param height 条形码高度(单位:点,推荐 3)
* @param hriStyle 可读性位置:0=OFF, 1=ABOVE, 2=BELOW, 3=ABOVE_AND_BELOW
*/
@ReactMethod
fun printBarcode(content: String, barcodeType: Int, width: Int, height: Int, hriStyle: Int, promise: Promise) {
executeSyncOperation(promise) {
val type = when (barcodeType) {
0 -> BarcodeType.CODE128
1 -> BarcodeType.CODE39
2 -> BarcodeType.EAN13
3 -> BarcodeType.EAN8
4 -> BarcodeType.ITF
5 -> BarcodeType.CODE128 // UPC_A 不存在,使用 CODE128
6 -> BarcodeType.CODE128 // UPC_E 不存在,使用 CODE128
else -> BarcodeType.CODE128
}
val hri = when (hriStyle) {
0 -> {
// 尝试找到不显示 HRI 的枚举值
HriStyle.values().firstOrNull {
it.name.equals("NONE", ignoreCase = true) ||
it.name.equals("OFF", ignoreCase = true)
} ?: HriStyle.values().getOrNull(0) ?: HriStyle.BELOW
}
1 -> HriStyle.ABOVE
2 -> HriStyle.BELOW
3 -> {
// 尝试找到上下都显示的枚举值
HriStyle.values().firstOrNull {
it.name.equals("BOTH", ignoreCase = true) ||
it.name.equals("ABOVE_AND_BELOW", ignoreCase = true)
} ?: HriStyle.BELOW
}
else -> HriStyle.BELOW
}
it.printBarcode(content, type, width, height, hri)
}
}
/**
* 向前走 n 行
*
* @param n 行数
*/
@ReactMethod
fun lineFeed(n: Int, promise: Promise) {
executeSyncOperation(promise) { it.lineFeed(n) }
}
/**
* 走纸(按点数)
*
* @param dots 走纸点数
*/
@ReactMethod
fun dotsFeed(dots: Int, promise: Promise) {
executeSyncOperation(promise) { it.dotsFeed(dots) }
}
/**
* 提交打印缓冲区
* 所有打印操作完成后必须调用此方法,才会实际执行打印
* 这是异步操作,会等待打印完成
*
* @param promise Promise 对象,返回打印结果
*/
@ReactMethod
fun commitTransBuffer(promise: Promise) {
try {
val printer = checkPrinterConnection()
if (printer == null) {
promise.reject("NOT_CONNECTED", "打印机未连接,请先连接打印机")
return
}
printer.commitTransBuffer(
object : ResultCallback {
override fun onComplete() {
Log.d(TAG, "提交打印缓冲区成功")
promise.resolve(Arguments.createMap().apply {
putBoolean("success", true)
putString("message", "打印完成")
})
}
override fun onFailed(status: CloudPrinterStatus) {
Log.e(TAG, "提交打印缓冲区失败: ${status.name}")
promise.reject("PRINT_ERROR", "打印失败: ${status.name}")
}
}
)
} catch (e: Exception) {
Log.e(TAG, "commitTransBuffer 异常", e)
promise.reject("ERROR", e.message ?: "操作失败")
}
}
/**
* 打印列文本(用于表格)
*
* @param columns 列文本数组
* @param columnWidths 列宽度数组(单位:字符数)
* @param alignments 对齐方式数组:0=LEFT, 1=CENTER, 2=RIGHT
*/
@ReactMethod
fun printColumnsText(columns: com.facebook.react.bridge.ReadableArray, columnWidths: com.facebook.react.bridge.ReadableArray, alignments: com.facebook.react.bridge.ReadableArray, promise: Promise) {
executeSyncOperation(promise) {
// 转换 ReadableArray 到 Kotlin 数组
val columnArray = arrayListOf<String>()
for (i in 0 until columns.size()) {
columnArray.add(columns.getString(i) ?: "")
}
val widthArray = IntArray(columnWidths.size())
for (i in 0 until columnWidths.size()) {
widthArray[i] = columnWidths.getInt(i)
}
val alignmentArray = arrayListOf<AlignStyle>()
for (i in 0 until alignments.size()) {
val alignment = when (alignments.getInt(i)) {
0 -> AlignStyle.LEFT
1 -> AlignStyle.CENTER
2 -> AlignStyle.RIGHT
else -> AlignStyle.LEFT
}
alignmentArray.add(alignment)
}
it.printColumnsText(columnArray.toTypedArray(), widthArray, alignmentArray.toTypedArray())
}
}
/**
* 切纸
*
* @param cut 是否切纸
* @param feed 切纸前走纸点数
*/
@ReactMethod
fun postCutPaper(cut: Boolean, feed: Int, promise: Promise) {
executeSyncOperation(promise) { it.postCutPaper(cut, feed) }
}
}
//android/app/src/main/java/com/app/SimplePrinterPackage.kt
package com.app
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class SimplePrinterPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(SimplePrinterModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
//android/app/src/main/java/com/app/MainApplication.kt
import com.fnbposapp.SimplePrinterPackage
ckageList(this).packages.apply {
add(SimplePrinterPackage())
}
-
第三步, js 端调用桥接方法,进行打印
-
封装打印类
ts// SimpleiPrinter.ts import {NativeModules} from 'react-native'; const {SimplePrinter} = NativeModules; /** * 打印机操作结果接口 */ interface PrinterResult { success: boolean; message: string; name?: string; } // 对齐方式枚举 export enum AlignStyle { LEFT = 0, CENTER = 1, RIGHT = 2, } // 二维码错误级别 export enum ErrorLevel { L = 0, // 低 M = 1, // 中 Q = 2, // 中高 H = 3, // 高 } // 条形码类型 export enum BarcodeType { CODE128 = 0, CODE39 = 1, EAN13 = 2, EAN8 = 3, ITF = 4, UPC_A = 5, UPC_E = 6, } // 条形码可读性位置 export enum HriStyle { OFF = 0, // 不显示 ABOVE = 1, // 上方 BELOW = 2, // 下方 ABOVE_AND_BELOW = 3, // 上下都显示 } // 下划线模式 export enum UnderlineMode { OFF = 0, // 关闭 ON = 1, // 单下划线 DOUBLE = 2, // 双下划线 } class SunmiPrinter { /** * 连接 USB 打印机 */ async connectUSBPrinter(): Promise<PrinterResult> { try { const result = await SimplePrinter.connectUSBPrinter(); return { success: result.success === true || result.success === 'true', message: result.message || '连接成功', name: result.name, }; } catch (error: any) { return { success: false, message: error?.message || '连接失败', }; } } /** * 断开连接 */ async disconnect(): Promise<PrinterResult> { try { const result = await SimplePrinter.disconnect(); return { success: result.success === true || result.success === 'true', message: result.message || '断开成功', }; } catch (error: any) { return { success: false, message: error?.message || '断开失败', }; } } /** * 获取打印机连接状态和信息 */ async getPrinterInfo(): Promise<{ success: boolean; connected: boolean; name?: string; sn?: string; model?: string; message: string; }> { try { const result = await SimplePrinter.getPrinterInfo(); return { success: true, connected: result.connected === true || result.connected === 'true', name: result.name, sn: result.sn, model: result.model, message: result.message || '获取成功', }; } catch (error: any) { return { success: false, connected: false, message: error?.message || '获取失败', }; } } // ========== 打印样式控制方法 ========== // 注意:以下方法都是同步操作,立即执行,不返回结果 // 只有 commitTransBuffer 是异步操作,需要 await /** * 重新初始化布局样式 * 重置所有样式设置(对齐、字体大小、加粗、下划线等)为默认值 */ initStyle(): void { SimplePrinter.initStyle().catch((error: any) => { console.error('initStyle 失败:', error); }); } /** * 设置编码模式 * @param encodeType 编码类型:0=UTF_8 */ setEncodeMode(encodeType: number = 0): void { SimplePrinter.setEncodeMode(encodeType).catch((error: any) => { console.error('setEncodeMode 失败:', error); }); } /** * 设置可打印区域宽度 * @param printWidth 打印宽度(单位:点) */ setPrintWidth(printWidth: number): void { SimplePrinter.setPrintWidth(printWidth).catch((error: any) => { console.error('setPrintWidth 失败:', error); }); } /** * 设置左边距的宽度 * @param leftSpace 左边距宽度(单位:点) */ setLeftSpace(leftSpace: number): void { SimplePrinter.setLeftSpace(leftSpace).catch((error: any) => { console.error('setLeftSpace 失败:', error); }); } /** * 设置行间距 * @param lineSpacing 行间距(单位:点) */ setLineSpacing(lineSpacing: number): void { SimplePrinter.setLineSpacing(lineSpacing).catch((error: any) => { console.error('setLineSpacing 失败:', error); }); } /** * 设置加粗模式 * @param enable true=开启加粗,false=关闭加粗 */ setBoldMode(enable: boolean): void { SimplePrinter.setBoldMode(enable).catch((error: any) => { console.error('setBoldMode 失败:', error); }); } /** * 设置打印文本倍高和倍宽 * @param characterWidth 字符宽度倍数(1-8) * @param characterHeight 字符高度倍数(1-8) */ setCharacterSize(characterWidth: number, characterHeight: number): void { SimplePrinter.setCharacterSize(characterWidth, characterHeight).catch( (error: any) => { console.error('setCharacterSize 失败:', error); }, ); } /** * 设置 Latin 字符的大小(字库为矢量字库有效) * @param size 字符大小,4-255像素点,默认Latin字母12像素 */ setAsciiSize(size: number): void { SimplePrinter.setAsciiSize(size).catch((error: any) => { console.error('setAsciiSize 失败:', error); }); } /** * 设置中日韩字符的大小(字库为矢量字库有效) * @param size 字符大小,4-255像素点,默认其他字符24像素 */ setCjkSize(size: number): void { SimplePrinter.setCjkSize(size).catch((error: any) => { console.error('setCjkSize 失败:', error); }); } /** * 设置其他字符的大小(字库为矢量字库有效) * @param size 字符大小,4-255像素点,默认其他字符24像素 */ setOtherSize(size: number): void { SimplePrinter.setOtherSize(size).catch((error: any) => { console.error('setOtherSize 失败:', error); }); } /** * 选择 Latin 字符的字库 * @param select 选择当前使用的字库,0默认使用点阵字库(点阵字库比较清晰但不能自由调整大小),1使用内置矢量字库,大于127表示使用其他第三方字库,需要预置进打印设备 */ selectAsciiCharFont(select: number): void { SimplePrinter.selectAsciiCharFont(select).catch((error: any) => { console.error('selectAsciiCharFont 失败:', error); }); } /** * 选择中日韩字符的字库 * @param select 选择当前使用的字库,0默认使用点阵字库(点阵字库比较清晰但不能自由调整大小),1使用内置矢量字库,大于127表示使用其他第三方字库,需要预置进打印设备 */ selectCjkCharFont(select: number): void { SimplePrinter.selectCjkCharFont(select).catch((error: any) => { console.error('selectCjkCharFont 失败:', error); }); } /** * 选择其他字符的字库 * @param select 选择当前使用的字库,0默认使用点阵字库(点阵字库比较清晰但不能自由调整大小),1使用内置矢量字库,大于127表示使用其他第三方字库,需要预置进打印设备 */ selectOtherCharFont(select: number): void { SimplePrinter.selectOtherCharFont(select).catch((error: any) => { console.error('selectOtherCharFont 失败:', error); }); } /** * 设置对齐方式 * @param alignment 对齐方式:0=LEFT, 1=CENTER, 2=RIGHT */ setAlignment(alignment: AlignStyle): void { SimplePrinter.setAlignment(alignment).catch((error: any) => { console.error('setAlignment 失败:', error); }); } // ========== 打印内容方法 ========== // 注意:以下方法都是同步操作,立即执行,不返回结果 // 只有 commitTransBuffer 是异步操作,需要 await /** * 打印文本 * 打印文本并自动换行 * @param text 要打印的文本内容 */ printText(text: string): void { SimplePrinter.printText(text).catch((error: any) => { console.error('printText 失败:', error); }); } /** * 追加文本 * 在当前行追加文本,不换行 * @param text 要追加的文本内容 */ appendText(text: string): void { SimplePrinter.appendText(text).catch((error: any) => { console.error('appendText 失败:', error); }); } /** * 打印列文本(用于表格) * @param columns 列文本数组 * @param columnWidths 列宽度数组(单位:字符数) * @param alignments 对齐方式数组:0=LEFT, 1=CENTER, 2=RIGHT */ printColumnsText( columns: string[], columnWidths: number[], alignments: AlignStyle[], ): void { SimplePrinter.printColumnsText(columns, columnWidths, alignments).catch( (error: any) => { console.error('printColumnsText 失败:', error); }, ); } /** * 打印二维码 * @param content 二维码内容 * @param size 二维码大小(1-16,推荐 9) * @param errorLevel 错误纠正级别:0=L(低), 1=M(中), 2=Q(中高), 3=H(高) */ printQrcode( content: string, size: number, errorLevel: ErrorLevel = ErrorLevel.L, ): void { SimplePrinter.printQrcode(content, size, errorLevel).catch((error: any) => { console.error('printQrcode 失败:', error); }); } /** * 打印条形码 * @param content 条形码内容 * @param barcodeType 条形码类型:0=CODE128, 1=CODE39, 2=EAN13, 3=EAN8, 4=ITF, 5=UPC_A, 6=UPC_E * @param width 条形码宽度(单位:点,推荐 200) * @param height 条形码高度(单位:点,推荐 3) * @param hriStyle 可读性位置:0=OFF, 1=ABOVE, 2=BELOW, 3=ABOVE_AND_BELOW */ printBarcode( content: string, barcodeType: BarcodeType = BarcodeType.CODE128, width: number = 200, height: number = 3, hriStyle: HriStyle = HriStyle.BELOW, ): void { SimplePrinter.printBarcode( content, barcodeType, width, height, hriStyle, ).catch((error: any) => { console.error('printBarcode 失败:', error); }); } /** * 向前走 n 行 * @param n 行数 */ lineFeed(n: number): void { SimplePrinter.lineFeed(n).catch((error: any) => { console.error('lineFeed 失败:', error); }); } /** * 走纸(按点数) * @param dots 走纸点数 */ dotsFeed(dots: number): void { SimplePrinter.dotsFeed(dots).catch((error: any) => { console.error('dotsFeed 失败:', error); }); } /** * 切纸 * @param cut 是否切纸 * @param feed 切纸前走纸点数 */ postCutPaper(cut: boolean = true, feed: number = 0): void { SimplePrinter.postCutPaper(cut, feed).catch((error: any) => { console.error('postCutPaper 失败:', error); }); } /** * 提交打印缓冲区(所有打印操作完成后必须调用此方法) */ async commitTransBuffer(): Promise<PrinterResult> { try { const result = await SimplePrinter.commitTransBuffer(); return { success: result.success === true || result.success === 'true', message: result.message || '打印完成', }; } catch (error: any) { return { success: false, message: error?.message || '打印失败', }; } } } export default new SunmiPrinter(); -
消费打印,这里采用的是前端写死打印的模版
ts/** * 格式化金额 */ const formatAmount = (amount: number): string => { return amount.toFixed(2); }; /** * 打印结算单模板 * @param data 结算单数据 */ export const printCheckoutReceipt = async ( data: CheckoutReceiptData, ): Promise<void> => { // 初始化打印机样式 SunmiPrinter.initStyle(); SunmiPrinter.setEncodeMode(0); // UTF-8 SunmiPrinter.setPrintWidth(576); // 80mm 小票纸宽度(576像素) SunmiPrinter.selectCjkCharFont(1); SunmiPrinter.selectAsciiCharFont(1); SunmiPrinter.dotsFeed(12); SunmiPrinter.setAlignment(AlignStyle.CENTER); // 商品列表表头 SunmiPrinter.printColumnsText( ['数量', '商品列表', 'RM'], [6, 24, 10], [AlignStyle.LEFT, AlignStyle.LEFT, AlignStyle.RIGHT], ); SunmiPrinter.printText('--------------------------------------------'); // 商品列表 data.items.forEach((item, index) => { // 主商品 // const itemName = // item.name.length > 20 ? item.name.substring(0, 20) : item.name; SunmiPrinter.printColumnsText( [item.quantity.toString(), item.name, formatAmount(item.price)], [6, 24, 10], [AlignStyle.LEFT, AlignStyle.LEFT, AlignStyle.RIGHT], ); // 如果商品名称过长,换行显示剩余部分 // if (item.name.length > 20) { // SunmiPrinter.printText(` ${item.name.substring(20)}`); // } // 子项(如加料、配菜等) if (item.subItems && item.subItems.length > 0) { // SunmiPrinter.setAlignment(AlignStyle.LEFT); item.subItems.forEach(subItem => { const subItemText = subItem.price ? `${subItem.name}*${subItem.quantity}: ${formatAmount( subItem.price, )}` : `${subItem.name}*${subItem.quantity}`; SunmiPrinter.printColumnsText( [' ', subItemText, ' '], [6, 24, 10], [AlignStyle.LEFT, AlignStyle.LEFT, AlignStyle.RIGHT], ); // SunmiPrinter.printText(` ${subItemText}`); }); // SunmiPrinter.setAlignment(AlignStyle.CENTER); } // 最后一项后不加间距 if (index !== data.items.length - 1) { SunmiPrinter.dotsFeed(6); } }); SunmiPrinter.lineFeed(1); SunmiPrinter.lineFeed(2); // 切纸 SunmiPrinter.postCutPaper(true, 0); // 提交打印 await SunmiPrinter.commitTransBuffer(); };
-