更新记录
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]
注意事项
deviceId为 iOS 外设 UUID,与 uni 扫描列表一致- 同一打印机请勿同时用 uni BLE 与本插件连接
data必须为 大写 hex,长度为偶数

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 193
赞赏 0
下载 12342749
赞赏 1924
赞赏
京公网安备:11010802035340号