UniApp实战:集成原生插件实现蓝牙标签打印全流程
1. 为什么选择UniApp开发蓝牙标签打印应用用UniApp开发跨平台移动应用已经成了不少开发者的首选方案。我去年接手过一个仓库管理系统的项目需要给安卓和iOS设备同时开发标签打印功能。当时尝试过原生开发光是双平台代码维护就让人头疼后来改用UniApp配合原生插件开发效率直接翻倍。UniApp最大的优势在于一次开发多端部署。你只需要写一套Vue.js代码就能编译成iOS、Android和各种小程序。对于蓝牙标签打印这种硬件交互场景原生插件机制完美弥补了H5能力不足的问题。像LPAPI这样的专业打印插件已经把底层蓝牙通信、协议转换这些复杂操作封装好了开发者只需要调用简单的JavaScript API。市面上的蓝牙标签打印机种类繁多从便携式到工业级都有。我们项目用的是德佟DT系列实测下来稳定性不错。这类打印机通常支持两种连接方式蓝牙BLE和传统蓝牙。BLE功耗低但传输速度慢适合小数据量打印传统蓝牙速度快但耗电高适合大批量标签打印。通过UniApp插件可以自动适配这两种模式开发者不用关心底层差异。2. 开发环境准备与插件集成2.1 基础环境搭建首先确保你的HBuilderX是最新版本我吃过老版本兼容性问题的亏。新建UniApp项目时选择默认模板就行不需要特殊配置。重点是要勾选App开发模式因为我们要用到原生插件功能。安卓开发需要特别注意在manifest.json里配置minSdkVersion至少21Android 5.0因为很多蓝牙API在这个版本才稳定。iOS方面则建议target设为12.0以上避免权限问题。这是我的推荐配置app-plus: { android: { minSdkVersion: 21 }, ios: { targetVersion: 12.0 } }2.2 插件购买与配置在插件市场搜索LPAPI时会发现有多个版本。建议选择云打包版本这样不需要本地配置原生开发环境。购买后要在HBuilderX中关联项目右键项目 - 选择原生插件配置 - 勾选已购插件。有个坑要注意如果项目之前用过其他蓝牙插件最好先clean工程。我遇到过插件冲突导致蓝牙服务无法启动的情况。配置完成后建议先运行到基座测试确保插件加载正常。可以在onLoad里加个简单调用测试onLoad() { const api uni.requireNativePlugin(DothanTech-LPAPI) console.log(插件加载:, api ? 成功 : 失败) }3. 蓝牙权限与设备连接实战3.1 权限配置详解蓝牙权限配置是第一个容易踩坑的地方。安卓从6.0开始需要动态申请位置权限因为蓝牙扫描需要用到位置服务。在manifest.json里要配置这些权限uses-permission android:nameandroid.permission.BLUETOOTH/ uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN/ uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION/ !-- Android 12新增权限 -- uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-permission android:nameandroid.permission.BLUETOOTH_SCAN /iOS需要在manifest.json的plus - distribute - plugins下添加蓝牙权限声明plugins: { BLUETOOTH: { description: 蓝牙打印功能所需 } }3.2 设备搜索与连接建议封装一个打印机管理类统一处理设备搜索、连接状态维护。以下是核心代码片段class PrinterManager { constructor() { this.api uni.requireNativePlugin(DothanTech-LPAPI) this.currentPrinter null } async searchDevices() { try { const list await this.api.getPrinters() return list.filter(item item.name.includes(DT)) // 过滤德佟设备 } catch (e) { console.error(搜索失败:, e) return [] } } async connect(deviceName) { return new Promise((resolve) { this.api.openPrinter(deviceName, (success) { if (success) { this.currentPrinter deviceName } resolve(success) }) }) } }实际项目中建议添加自动重连机制。我们遇到过蓝牙信号不稳定导致断连的情况可以通过监听蓝牙状态变化来实现uni.onBluetoothDeviceFound((res) { if(res.devices[0].name this.currentPrinter) { this.reconnect() } })4. 标签设计与打印任务处理4.1 绘制标签内容LPAPI提供了丰富的绘制API可以组合出各种复杂的标签样式。先看个简单的文本二维码标签示例async printSimpleLabel(orderInfo) { // 开始打印任务 await this.api.startJob({ width: 50, // 标签宽度50mm height: 30 // 高度30mm }) // 绘制标题文本 await this.api.drawText({ text: 订单编号, x: 5, y: 2, fontHeight: 4, fontStyle: 1 // 加粗 }) // 绘制订单号 await this.api.drawText({ text: orderInfo.no, x: 5, y: 8, fontHeight: 3 }) // 绘制二维码 await this.api.draw2DQRCode({ text: orderInfo.url, x: 30, y: 5, width: 20 }) // 提交打印 await this.api.commitJob() }4.2 高级排版技巧对于复杂的商品标签可能需要用到这些技巧自动换行设置autoReturn参数让长文本自动换行await api.drawText({ text: 这是一段很长的商品描述文字..., x: 5, y: 10, width: 40, autoReturn: true })表格布局通过计算坐标绘制表格线// 绘制横线 await api.drawLine({ x1: 5, y1: 15, x2: 45, y2: 15, lineWidth: 0.2 }) // 绘制竖线 await api.drawLine({ x1: 25, y1: 15, x2: 25, y2: 25, lineWidth: 0.2 })混合排版文本条形码组合// 商品名称 await api.drawText({ text: product.name, x: 5, y: 5, fontHeight: 4 }) // 商品条码 await api.draw1DBarcode({ text: product.barcode, x: 5, y: 10, width: 40, height: 8 })5. 常见问题排查与性能优化5.1 典型问题解决方案问题1搜索不到蓝牙设备检查手机蓝牙是否开启确认打印机处于可发现模式通常需要长按某个按键安卓设备需要开启GPS定位权限问题2打印内容错位检查标签纸尺寸设置是否正确确认打印机DPI参数常见的有203dpi和300dpi测试调整x/y坐标偏移量问题3iOS连接不稳定在Info.plist中添加NSBluetoothAlwaysUsageDescription确保CBCentralManager状态为poweredOn5.2 性能优化建议批量打印优化// 不好的做法循环提交 for(const item of list) { await printItem(item) } // 推荐做法批量构建指令 await api.startJob({width: 50, height: 30}) for(const item of list) { await buildItemCommands(item) } await api.commitJob()图片打印处理提前将图片转为黑白二值图使用threshold参数调整打印效果await api.drawImage({ image: base64Data, x: 5, y: 5, width: 40, threshold: 180 // 灰度阈值 })内存管理长时间打印时定期调用gc()避免在单次任务中绘制过多元素实际项目中我们还实现了打印任务队列机制避免并发操作导致指令混乱。核心思路是用一个数组存储待打印任务通过递归调用来顺序处理class PrintQueue { constructor() { this.tasks [] this.isPrinting false } add(task) { this.tasks.push(task) if(!this.isPrinting) { this.processNext() } } async processNext() { if(this.tasks.length 0) { this.isPrinting false return } this.isPrinting true const task this.tasks.shift() try { await task.execute() } catch(e) { console.error(打印失败:, e) } this.processNext() } }