手把手封装UniApp蓝牙打印JS-SDK:以LPAPI插件为例打造可复用业务组件 UniApp蓝牙打印JS-SDK深度封装实战从API调用到企业级解决方案在移动应用开发中蓝牙打印功能已成为零售、物流、医疗等行业应用的标配需求。但大多数开发者面对原生插件提供的底层API时往往陷入重复编写连接管理、错误处理和模板设计的泥潭。本文将以LPAPI插件为例展示如何将零散的蓝牙打印API封装成高可用、易维护的JS-SDK最终打包为可跨项目复用的uni_modules模块。1. 架构设计构建Promise风格的基础通信层原始LPAPI插件采用回调函数方式返回结果这在现代前端开发中已显过时。我们首先需要构建统一的Promise封装为后续高级功能打下基础。class PrintService { constructor() { this._api uni.requireNativePlugin(DothanTech-LPAPI); this._pendingRequests new Map(); this._requestId 0; } _invoke(method, params {}) { return new Promise((resolve, reject) { const requestId this._requestId; this._pendingRequests.set(requestId, { resolve, reject }); this._api[method](params, (result) { const handler this._pendingRequests.get(requestId); if (!handler) return; this._pendingRequests.delete(requestId); result?.code 0 ? resolve(result.data) : reject( new PrintError(result?.data || Unknown error, result?.code || -1) ); }); }); } } class PrintError extends Error { constructor(message, code) { super(message); this.code code; } }关键改进点请求生命周期管理通过_pendingRequests映射表避免回调地狱标准化错误处理自定义PrintError包含原始错误码TypeScript支持为后续类型提示预留扩展空间提示建议添加15秒自动超时机制避免蓝牙设备无响应导致Promise永久挂起2. 打印机连接管理状态机模式实现智能重连蓝牙设备连接具有不稳定性需要完善的状态管理机制。我们引入状态机模式来规范打印机生命周期const PrinterStates { DISCONNECTED: 0, CONNECTING: 1, CONNECTED: 2, PRINTING: 3, ERROR: 4 }; class PrinterManager { constructor() { this._state PrinterStates.DISCONNECTED; this._retryCount 0; this._deviceInfo null; } async connect(deviceName, options {}) { if (this._state ! PrinterStates.DISCONNECTED) { throw new PrintError(Printer is not in disconnected state, 4001); } this._transition(PrinterStates.CONNECTING); try { this._deviceInfo await printService._invoke(openPrinter, { name: deviceName, timeout: options.timeout || 10000 }); this._transition(PrinterStates.CONNECTED); this._retryCount 0; return this._deviceInfo; } catch (e) { this._transition(PrinterStates.ERROR, e); if (this._retryCount (options.maxRetries || 3)) { await delay(2000); return this.connect(deviceName, options); } throw e; } } _transition(newState, payload) { // 状态转换校验逻辑... this._state newState; this.emit(stateChanged, { newState, oldState: this._state, payload }); } }状态转换矩阵设计当前状态允许转换至触发条件DISCONNECTEDCONNECTING调用connect()方法CONNECTINGCONNECTED/ERROR/DISCONNECTED连接成功/失败/用户取消CONNECTEDPRINTING/DISCONNECTED开始打印/主动断开PRINTINGCONNECTED/ERROR打印完成/打印错误ERRORDISCONNECTED/CONNECTING错误恢复/自动重试3. 打印模板引擎JSON配置驱动设计不同业务场景需要不同的打印模板商品标签、快递面单等我们设计基于JSON的模板描述语言// 商品标签模板示例 const productLabelTemplate { page: { width: 80, height: 60, orientation: portrait, margins: { top: 5, left: 5, right: 5, bottom: 5 } }, elements: [ { type: text, content: {{productName}}, position: { x: 10, y: 5 }, fontSize: 18, bold: true, align: center }, { type: barcode, content: {{sku}}, position: { x: 20, y: 25 }, width: 40, height: 15, format: CODE128 }, { type: text, content: {{price}}, position: { x: 10, y: 45 }, fontSize: 16, color: #FF0000 } ] }; class TemplateEngine { constructor(printerManager) { this._printer printerManager; } async print(template, data) { await this._printer.prepareJob(template.page); for (const element of template.elements) { const content this._interpolate(element.content, data); await this._renderElement({ ...element, content }); } return this._printer.commitJob(); } _interpolate(str, data) { return str.replace(/\{\{([^}])\}\}/g, (_, key) data[key.trim()] || ); } _renderElement(element) { switch (element.type) { case text: return this._printer.drawText(element); case barcode: return this._printer.draw1DBarcode(element); // 其他元素类型处理... } } }模板特性对比特性基础实现高级实现数据绑定简单变量替换条件渲染、循环列表布局系统绝对定位弹性布局、相对定位样式继承无全局样式局部覆盖动态计算不支持支持表达式计算多页处理单页自动分页页眉页脚4. 工程化封装发布为uni_modules模块将完整解决方案打包为uni_modules模块方便跨项目复用/uni_modules/print-sdk ├── package.json ├── readme.md ├── lib/ │ ├── core/ │ │ ├── print-service.js │ │ ├── printer-manager.js │ ├── templates/ │ │ ├── product-label.js │ │ ├── shipping-order.js │ ├── utils/ │ │ ├── error.js │ │ ├── validator.js ├── types/ │ ├── index.d.ts ├── demo/ │ ├── pages/ │ │ ├── index.vue关键配置项package.json{ name: print-sdk, version: 1.0.0, description: UniApp蓝牙打印SDK, uni-app: { scripts: { print-service: { autoinject: true, path: lib/core/print-service.js } } }, dependencies: { lodash.template: ^4.5.0 } }模块使用示例// 在vue组件中使用 import { PrinterManager, TemplateEngine } from /uni_modules/print-sdk; export default { data() { return { printer: new PrinterManager(), templateEngine: null }; }, async mounted() { this.templateEngine new TemplateEngine(this.printer); await this.printer.connect(DT-888); }, methods: { async printLabel(product) { try { await this.templateEngine.print( ProductLabelTemplate, { productName: product.name, sku: product.sku, price: product.price } ); } catch (e) { uni.showToast({ title: 打印失败: ${e.message}, icon: none }); } } } };5. 高级优化性能监控与体验增强企业级应用还需要考虑以下增强功能蓝牙连接优化策略class PrinterManager { constructor() { this._connectionMonitor setInterval(() { if (this._state PrinterStates.CONNECTED) { this._checkConnectionQuality(); } }, 30000); } async _checkConnectionQuality() { const start Date.now(); try { await this._invoke(getPrinterInfo); const latency Date.now() - start; if (latency 2000) { this._emit(connectionWarning, { latency }); } } catch (e) { this._reconnect(); } } }打印队列管理实现class PrintQueue { constructor() { this._queue []; this._isProcessing false; } add(task) { return new Promise((resolve, reject) { this._queue.push({ task, resolve, reject }); if (!this._isProcessing) this._processNext(); }); } async _processNext() { if (this._queue.length 0) { this._isProcessing false; return; } this._isProcessing true; const { task, resolve, reject } this._queue.shift(); try { const result await task(); resolve(result); } catch (e) { reject(e); } finally { this._processNext(); } } }性能指标收集可与监控平台集成指标名称采集方式业务意义连接成功率连接尝试结果统计评估蓝牙环境稳定性平均打印耗时任务开始到完成时间差评估设备性能重试频率失败后的重试次数统计发现设备兼容性问题内存占用峰值打印过程中的内存监控预防OOM崩溃在uni-app项目的pages.json中配置自动注入{ easycom: { autoscan: true, custom: { ^print-(.*): /uni_modules/print-sdk/components/print-$1/print-$1.vue } } }