更新记录

1.0.0(2026-06-26)

初版


平台兼容性

uni-app(5.01)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
1.0.0 1.0.0 - - - - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.01)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - -

ios-printer

iOS 端 CoreBluetooth BLE 打印机 UTS 原生插件。使用原生 CBCentralManager 连接佳博等 BLE 打印机,支持服务/特征值自动匹配、分包发送、连接状态与上行数据订阅。

背景

iOS 上原先用uni BLE API 发送数据需要每20字节分割发送, 大数据会导致不停进行原生与webview层交互导致发送时间延长。本插件将连接、写入、订阅逻辑下沉到 CoreBluetooth + Swift 混编,与 uni BLE API 对齐。

平台兼容

平台 支持
App-iOS
App-Android
App-鸿蒙
H5 / 小程序

环境要求

  • HBuilderX 3.90+(建议与 package.jsonengines 一致)
  • 自定义基座或云打包(含 UTS 插件编译)
  • utssdk/app-ios/Info.plist 已配置 NSBluetoothAlwaysUsageDescription
  • 连接前须先通过 uni.startBluetoothDevicesDiscovery 发现设备,再使用列表中的 deviceId

目录结构

ios-printer/
├── package.json
├── index.ts                 # 非 iOS 平台兜底
├── readme.md
└── utssdk/
    ├── interface.uts        # API 类型契约
    ├── common.uts           # 错误码
    └── app-ios/
        ├── index.uts        # UTS 入口
        ├── global.uts       # 连接池、回调
        ├── util.uts         # UUID 匹配、hex 校验
        ├── IosPrinterBleNative.swift  # CoreBluetooth 实现
        └── Info.plist       # 蓝牙权限说明

快速开始

示例只样式插件, 平台需要自行判断.

import {
  connect,
  disconnect,
  writeData,
  onConnectionStateChange,
  onDataReceived,
} from '@/uni_modules/ios-printer'

// uni 标准 API 扫描到想要连接的设备 ID
const connectedDeviceID = '连接得到的 UUID'
/** 佳博等 BLE 打印机常用 UUID */
const uuidConfigs = [
  {
    serviceId: '000018F0-0000-1000-8000-00805F9B34FB',
    txCharacteristicId: '00002AF0-0000-1000-8000-00805F9B34FB',
    rxCharacteristicId: '00002AF1-0000-1000-8000-00805F9B34FB',
  },
  {
    serviceId: '49535343-FE7D-4AE5-8FA9-9FAFD205E455',
    txCharacteristicId: '49535343-1E4D-4BD9-BA61-23C647249616',
    rxCharacteristicId: '49535343-8841-43F4-A8D4-ECBE34729BB3',
  },
  {
    serviceId: 'E7810A71-73AE-499D-8C15-FAA9AEF0C3F2',
    txCharacteristicId: 'BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F',
    rxCharacteristicId: 'BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F',
  },
]

// 注册全局回调(建议在 App 启动或蓝牙页初始化一次)
onConnectionStateChange((e) => {
  connectedDeviceID = null;
})
onDataReceived((event: { deviceId: string; data: number[] }) => {
  const callback = blePrinterNotifyCallBack.get(event.deviceId);
  if (!callback) {
    return;
  }
  const buffer = new ArrayBuffer(event.data.length);
  const view = new Uint8Array(buffer);
  for (let i = 0; i < event.data.length; i++) {
    view[i] = event.data[i];
  }
  // 注意如果回调数据大. 可能需要多次触发才能拿到完整数据
  callback(buffer);
});

// 全局记录打印机订阅回调
const blePrinterNotifyCallBack = new Map();

// 如果是 IOS 连接打印机就调用
function IosConnectPrinter(deviceId: string) {
  return await new Promise<void>((resolve, reject) => {
    connect({
      deviceId,
      uuidConfigs,
      success: () => resolve(
        connectedDeviceID = deviceId
      ),
      fail: reject,
    })
  })
}

/**
 * 发送数据到指定的打印机 并且等待响应
 * @param {string} deviceId 指定的设备id
 * @param {string} data  要发送的数据 16进制字符串 大写
 * @param {number} sleepMs 等待响应的超时时间 毫秒
 */
export async function writeToPrinterAndWaitResult(
    deviceId: string,
    data: string,
    sleepMs = 2000
) {
    // 设置回调
    let result: number[] = [];

  blePrinterNotifyCallBack.set(deviceId, (buffer) => {
    const unit8Data = new Uint8Array(buffer);
    const hexArray = Array.from(unit8Data);
    result.push(...hexArray);
  });

    // 已订阅 发送数据
    try {
        await writeToPrinter(deviceId, data);
    } catch (e) {
    blePrinterNotifyCallBack.delete(deviceId);
        throw e;
    }

    if (result.length === 0) {
        let startTime = Date.now();
        let stopTime = startTime + sleepMs;

        while (Date.now() < stopTime) {
            if (result.length === 0) {
                await sleep(500);
            } else {
                console.log("响应时间:", Date.now() - startTime, result);
                break;
            }
        }

        if (result.length === 0) {
            console.log("响应超时, 未获取到响应数据.", Date.now() - startTime);
        }
    }

    // 移除数据回调
    blePrinterNotifyCallBack.delete(deviceId);

    return result;
}

/**
 * 发送数据到指定的打印机
 * @param {string} deviceId 指定的设备id
 * @param {string} data  要发送的数据 16进制字符串 大写
 */
export function writeToPrinter(deviceId: string, data: string) {
    return new Promise((resolve, reject) => {
    IosPrinterWriteData({
      deviceId: deviceId,
      data: data.toUpperCase(),
      success: () => {
        resolve(null);
      },
      fail: (e: any) => {
        reject(e);
      },
    });
  });
}

// 标签打印
async function printLabel(hex: string) {
  if (!connectedDeviceID) {
    return // 未连接 或 异常断开
  }

  // 如果需要查询打印机模式
  if (true) {
    const codes = {
      'tsc': "1B213F",
      'esc': "100402"
    }
      const queryCode = codes['tsc'];

      try {
          // 检查打印机状态
      const res = await writeToPrinterAndWaitResult(connectedDeviceID, queryCode, 5000);
      console.log("打印机状态", JSON.stringify(res));
      if (res.length) {
        const statusCode = res[0];
        if (type === "esc" && statusCode - 18 !== 0) {
          throw new Error("请检查打印机状态");
        }
        if (type === "tsc" && statusCode !== 0) {
          throw new Error("请检查打印机状态:" + statusCode);
        }
      } else {
        throw new Error("请检查打印机模式");
      }
    } catch (e: any) {
      // @ts-ignore
      const message = e?.errMsg ? e.errMsg : e.message || "未知异常";
      return Promise.reject(new Error(message));
    }
  }

  try {
    // 发送数据
    await writeToPrinter(connectedDeviceID, hex.toUpperCase());

    console.log("数据发送完成", Date.now());
  } catch (e: any) {
    // @ts-ignore
    const message = e?.errMsg ? e.errMsg : e.message || "未知异常";
    return Promise.reject(new Error(message));
  }
}
// 断开
function disconnectPrinter() {
  if (!connectedDeviceID) {
    return
  }
  disconnect({
    deviceId: connectedDeviceID,
    success: () => {
      connectedDeviceID = null;
    },
  })
}

插件 API 说明

导出列表

方法 说明
connect 连接 BLE 打印机,自动匹配服务与 Tx/Rx
disconnect 断开指定设备
writeData 一次传入 hex,原生内部分包发送
onConnectionStateChange 订阅 / 取消订阅连接状态(传 null 取消)
onDataReceived 订阅 / 取消订阅上行数据(传 null 取消)

公共类型

/** 一组服务 + 特征值配置 */
type PrinterUuidConfig = {
  serviceId: string
  txCharacteristicId: string   // 设备 → 手机,notify 接收
  rxCharacteristicId: string   // 手机 → 设备,write 发送
}

type BleResult = {
  code: number    // 0 成功,见错误码表
  errMsg: string
}

type ConnectSuccessResult = {
  serviceId: string
  txCharacteristicId: string
  rxCharacteristicId: string
}

type ConnectionStateChangeEvent = {
  deviceId: string
  connected: boolean
}

type DataReceivedCallbackEvent = {
  deviceId: string
  data: number[]   // 字节数组,每项 0–255
}

connect

连接指定 deviceId 的 BLE 打印机。原生会:检索/扫描外设 → 连接 → 发现全部服务 → 按 uuidConfigs 顺序匹配第一组可用配置 → 对 tx 开启 notify → 返回匹配结果。

参数 ConnectOptions

字段 类型 必填 说明
deviceId string uni 扫描得到的设备 UUID
uuidConfigs PrinterUuidConfig[] 候选配置,按优先级排序
success (res: ConnectSuccessResult) => void 连接并匹配成功
fail (err: BleResult) => void 失败
complete (res: BleResult) => void 结束(成功或失败都会调)

success 回调示例

connect({
  deviceId,
  uuidConfigs: PRINTER_UUID_CONFIGS,
  success: (res) => {
    console.log('匹配服务', res.serviceId)
    console.log('写入特征 rx', res.rxCharacteristicId)
    console.log('通知特征 tx', res.txCharacteristicId)
  },
  fail: (err) => {
    if (err.code === 10012) {
      console.error('连接超时,请确认已扫描且打印机已开机')
    }
  },
})

disconnect

断开已连接的打印机。

参数 DisconnectOptions

字段 类型 必填 说明
deviceId string 要断开的设备 UUID
success (res: BleResult) => void 断开指令已执行
fail (err: BleResult) => void 未连接等
complete (res: BleResult) => void 结束
disconnect({
  deviceId: '9A71F94B-B7A3-2019-418E-7C9331CC085C',
  success: () => console.log('已断开'),
  fail: (err) => console.error(err),
})

writeData

向已连接设备的 rx 特征值写入数据。Vue 层传入完整 hex 字符串,插件在原生层按 splitSize 分包,避免大数据频繁跨 JSBridge。

参数 WriteDataOptions

字段 类型 必填 说明
deviceId string 已连接设备 UUID
data string 大写 hex,长度为偶数,如 1B4000
splitSize number 每包字节数,默认 20
success (res: BleResult) => void 全部分包发送完成
fail (err: BleResult) => void 未连接、hex 非法、发送失败等
complete (res: BleResult) => void 结束
// 发送 ESC/POS 初始化等指令(示例 hex)
writeData({
  deviceId,
  data: '1B40001B6100',
  splitSize: 20,
  success: () => console.log('打印数据已发完'),
  fail: (err) => {
    if (err.code === 10006) console.error('请先 connect')
    if (err.code === 10013) console.error('hex 格式错误')
  },
})

大数据发送(推荐用法)

// 整段标签/小票 hex 一次传入,无需在 JS 层 for 循环 slice
const labelHex = '1D6100...' // 可能数千字节
writeData({
  deviceId,
  data: labelHex.toUpperCase(),
  // splitSize 不传则默认 20
  success: () => {},
  fail: (err) => {},
})

onConnectionStateChange

注册连接状态监听。多次调用会覆盖上一次回调;传 null 取消。

参数 类型 说明
callback (event: ConnectionStateChangeEvent) => void \| null 监听函数或 null

触发时机:连接成功、主动断开、异常断开、蓝牙关闭等。

onConnectionStateChange((event) => {
  const { deviceId, connected } = event
  if (connected) {
    console.log(`${deviceId} 已连接`)
  } else {
    console.log(`${deviceId} 已断开`)
  }
})

// 不需要时卸载时取消
onConnectionStateChange(null)

onDataReceived

注册打印机上行数据监听(tx 特征 notify)。传 null 取消。

参数 类型 说明
callback (event: DataReceivedCallbackEvent) => void \| null 监听函数或 null
onDataReceived((event) => {
  const hex = event.data.map((b) => b.toString(16).padStart(2, '0').toUpperCase()).join('')
  console.log('收到数据', event.deviceId, hex)
})

发送并等待回传示例

let received: number[] = []

onDataReceived((event) => {
  if (event.deviceId === deviceId) {
    received.push(...event.data)
  }
})

writeData({
  deviceId,
  data: '1B760100',
  success: async () => {
    // 简单轮询等待(业务层也可用 Promise + 超时)
    await new Promise((r) => setTimeout(r, 2000))
    console.log('回传字节', received)
    onDataReceived(null)
  },
})

佳博打印机默认 UUID(三组)

src/utils/ble.tsblePrinterUUIDArray 一致,可按设备扩展:

组别 serviceId tx(设备→手机) rx(手机→设备)
1 000018F0-... 00002AF0-... 00002AF1-...
2 49535343-FE7D-... 49535343-1E4D-... 49535343-8841-...
3 E7810A71-... BEF8D6C9-...(tx/rx 相同) 同左
  • txsetNotifyValue,接收打印机回传
  • rxwriteValue,发送打印指令

错误码

code 说明
0 成功
10000 蓝牙未初始化
10001 蓝牙未开启 / 未授权
10002 未找到设备(请先扫描)
10003 连接失败
10004 未找到匹配服务
10005 未找到特征值
10006 当前设备未连接
10007 不支持 / 非 iOS 平台
10008 系统错误 / 发送冲突
10012 连接超时(20s)
10013 参数或 hex 数据无效

开发与调试

  • 修改 utssdk/app-ios 后需 重新制作自定义基座 或云打包
  • 真机日志可过滤关键字:IosPrinterBle[ios-printer]

注意事项

  1. deviceId 为 iOS 外设 UUID,与 uni 扫描列表一致
  2. 同一打印机请勿同时用 uni BLE 与本插件连接
  3. data 必须为 大写 hex,长度为偶数

隐私、权限声明

1. 本插件需要申请的系统权限列表:

NSBluetoothAlwaysUsageDescription

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。