手把手封装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 }); } }状态转换矩阵设计:
| 当前状态 | 允许转换至 | 触发条件 |
|---|---|---|
| DISCONNECTED | CONNECTING | 调用connect()方法 |
| CONNECTING | CONNECTED/ERROR/DISCONNECTED | 连接成功/失败/用户取消 |
| CONNECTED | PRINTING/DISCONNECTED | 开始打印/主动断开 |
| PRINTING | CONNECTED/ERROR | 打印完成/打印错误 |
| ERROR | DISCONNECTED/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" } } }