更新记录

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.json 中 engines 一致)
  • 自定义基座或云打包(含 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.ts 中 blePrinterUUIDArray 一致,可按设备扩展:

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

错误码

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. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。