React Native对接Sunmi打印sdk

已知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();
      	};
相关推荐
雨季66618 小时前
构建 OpenHarmony 简易文字行数统计器:用字符串分割实现纯文本结构感知
开发语言·前端·javascript·flutter·ui·dart
雨季66618 小时前
Flutter 三端应用实战:OpenHarmony 简易倒序文本查看器开发指南
开发语言·javascript·flutter·ui
小北方城市网18 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
2401_8920005219 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加支出实现
前端·javascript·flutter
天马379819 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
天天向上102419 小时前
vue3 实现el-table 部分行不让勾选
前端·javascript·vue.js
qx0919 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
摘星编程20 小时前
在OpenHarmony上用React Native:SectionList吸顶分组标题
javascript·react native·react.js
摘星编程20 小时前
React Native鸿蒙版:StackNavigation页面返回拦截
react native·react.js·harmonyos
Mr Xu_20 小时前
前端实战:基于Element Plus的CustomTable表格组件封装与应用
前端·javascript·vue.js·elementui