taro-bluetooth-print 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +269 -40
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +903 -55
- package/dist/index.umd.js +1 -1
- package/dist/types/adapters/TaroAdapter.d.ts +52 -2
- package/dist/types/adapters/TaroAdapter.d.ts.map +1 -1
- package/dist/types/config/PrinterConfig.d.ts +52 -0
- package/dist/types/config/PrinterConfig.d.ts.map +1 -0
- package/dist/types/core/BluetoothPrinter.d.ts +205 -9
- package/dist/types/core/BluetoothPrinter.d.ts.map +1 -1
- package/dist/types/core/EventEmitter.d.ts +70 -0
- package/dist/types/core/EventEmitter.d.ts.map +1 -0
- package/dist/types/drivers/EscPos.d.ts +90 -1
- package/dist/types/drivers/EscPos.d.ts.map +1 -1
- package/dist/types/errors/BluetoothError.d.ts +50 -0
- package/dist/types/errors/BluetoothError.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.d.ts +15 -2
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/encoding.d.ts +47 -5
- package/dist/types/utils/encoding.d.ts.map +1 -1
- package/dist/types/utils/image.d.ts +2 -0
- package/dist/types/utils/image.d.ts.map +1 -1
- package/dist/types/utils/logger.d.ts +82 -0
- package/dist/types/utils/logger.d.ts.map +1 -0
- package/package.json +20 -2
- package/src/adapters/TaroAdapter.ts +181 -40
- package/src/config/PrinterConfig.ts +95 -0
- package/src/core/BluetoothPrinter.ts +381 -22
- package/src/core/EventEmitter.ts +113 -0
- package/src/drivers/EscPos.ts +159 -9
- package/src/errors/BluetoothError.ts +78 -0
- package/src/index.ts +28 -1
- package/src/types.ts +16 -1
- package/src/utils/encoding.ts +80 -13
- package/src/utils/image.ts +47 -11
- package/src/utils/logger.ts +129 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@
|
|
|
5
5
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),并且本项目遵循
|
|
6
6
|
[语义化版本](https://semver.org/lang/zh-CN/)。
|
|
7
7
|
|
|
8
|
+
## [2.0.2] - 2025-11-21
|
|
9
|
+
|
|
10
|
+
### ✨ 新增功能
|
|
11
|
+
|
|
12
|
+
- **图片打印**: 支持 Floyd-Steinberg 抖动算法,提供高质量图片打印
|
|
13
|
+
- **二维码打印**: 原生支持二维码打印,可自定义大小和纠错级别
|
|
14
|
+
- **弱网适配**: 新增分片传输和重试机制,提升不稳定网络下的打印成功率
|
|
15
|
+
- **断点续传**: 支持打印任务的暂停、恢复和取消
|
|
16
|
+
|
|
17
|
+
### 📚 文档
|
|
18
|
+
|
|
19
|
+
- **在线文档**: 全新的 VitePress 文档站点,部署于 GitHub Pages
|
|
20
|
+
- **使用指南**: 新增快速开始、功能特性和 API 参考文档
|
|
21
|
+
|
|
22
|
+
### 🔧 优化
|
|
23
|
+
|
|
24
|
+
- **测试重构**: 清理遗留测试代码,简化测试配置
|
|
25
|
+
- **构建优化**: 修复 CI/CD 流程中的依赖问题
|
|
26
|
+
|
|
8
27
|
## [2.0.1] - 2025-11-21
|
|
9
28
|
|
|
10
29
|
### 🎉 主要变更
|
package/README.md
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
# Taro Bluetooth Print
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/npm/v/taro-bluetooth-print?style=flat-square&color=00d8ff" alt="npm version">
|
|
5
|
+
<img src="https://img.shields.io/npm/dm/taro-bluetooth-print?style=flat-square&color=00d8ff" alt="downloads">
|
|
6
|
+
<img src="https://img.shields.io/npm/l/taro-bluetooth-print?style=flat-square&color=00d8ff" alt="license">
|
|
7
|
+
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs Welcome">
|
|
8
|
+
</p>
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>轻量级、高性能的 Taro 蓝牙打印库</strong><br>
|
|
12
|
+
支持图片、二维码、断点续传与弱网适配
|
|
13
|
+
</p>
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
* **Cross-Platform**: Built on top of Taro's Bluetooth API, works on Weapp, H5, RN, etc.
|
|
9
|
-
* **High Performance**: Direct byte buffer manipulation.
|
|
10
|
-
* **Rich Support**: Text, Images, Barcodes (coming soon), QR Codes (coming soon).
|
|
11
|
-
* **Extensible**: Modular driver architecture (ESC/POS included).
|
|
15
|
+
---
|
|
12
16
|
|
|
13
|
-
##
|
|
17
|
+
## ✨ 特性
|
|
18
|
+
|
|
19
|
+
- 🚀 **高性能** - 直接字节缓冲区操作,服务缓存优化
|
|
20
|
+
- 📱 **跨平台** - 基于 Taro 蓝牙 API,支持微信小程序、H5、React Native 等
|
|
21
|
+
- 🎨 **简洁 API** - 链式调用,易于使用
|
|
22
|
+
- 🖼️ **图片打印** - 内置 Floyd-Steinberg 抖动算法,高质量图片转换
|
|
23
|
+
- 📲 **二维码支持** - 原生 ESC/POS 二维码指令
|
|
24
|
+
- 🔄 **断点续传** - 支持打印任务暂停/恢复/取消
|
|
25
|
+
- 📶 **弱网适配** - 智能分片与重试机制
|
|
26
|
+
- 📊 **进度追踪** - 实时打印进度事件
|
|
27
|
+
- 🛠️ **TypeScript** - 完整的类型定义和 JSDoc 文档
|
|
28
|
+
- 🧪 **高测试覆盖** - 76%+ 代码覆盖率
|
|
29
|
+
|
|
30
|
+
## 📦 安装
|
|
14
31
|
|
|
15
32
|
```bash
|
|
16
33
|
npm install taro-bluetooth-print
|
|
17
|
-
#
|
|
34
|
+
# 或
|
|
18
35
|
yarn add taro-bluetooth-print
|
|
36
|
+
# 或
|
|
37
|
+
pnpm add taro-bluetooth-print
|
|
19
38
|
```
|
|
20
39
|
|
|
21
|
-
##
|
|
40
|
+
## 🚀 快速开始
|
|
22
41
|
|
|
23
|
-
###
|
|
42
|
+
### 基础示例
|
|
24
43
|
|
|
25
44
|
```typescript
|
|
26
45
|
import { BluetoothPrinter } from 'taro-bluetooth-print';
|
|
@@ -29,53 +48,263 @@ const printer = new BluetoothPrinter();
|
|
|
29
48
|
|
|
30
49
|
async function printReceipt(deviceId: string) {
|
|
31
50
|
try {
|
|
51
|
+
// 连接设备
|
|
32
52
|
await printer.connect(deviceId);
|
|
33
53
|
|
|
54
|
+
// 构建打印内容
|
|
34
55
|
await printer
|
|
35
|
-
.text('
|
|
56
|
+
.text('=== 欢迎光临 ===', 'GBK')
|
|
57
|
+
.feed()
|
|
58
|
+
.text('商品A x1 ¥10.00')
|
|
59
|
+
.text('商品B x2 ¥20.00')
|
|
36
60
|
.feed()
|
|
37
|
-
.text('
|
|
61
|
+
.text('------------------------')
|
|
62
|
+
.text('合计: ¥30.00')
|
|
63
|
+
.feed(2)
|
|
64
|
+
.qr('https://example.com', { size: 8 })
|
|
38
65
|
.feed(2)
|
|
39
66
|
.cut()
|
|
40
|
-
.print();
|
|
41
|
-
|
|
42
|
-
console.log('
|
|
67
|
+
.print(); // 发送到打印机
|
|
68
|
+
|
|
69
|
+
console.log('打印成功!');
|
|
43
70
|
} catch (error) {
|
|
44
|
-
console.error('
|
|
71
|
+
console.error('打印失败:', error);
|
|
45
72
|
} finally {
|
|
46
73
|
await printer.disconnect();
|
|
47
74
|
}
|
|
48
75
|
}
|
|
49
76
|
```
|
|
50
77
|
|
|
51
|
-
###
|
|
78
|
+
### 监听打印进度
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { BluetoothPrinter, LogLevel, Logger } from 'taro-bluetooth-print';
|
|
82
|
+
|
|
83
|
+
// 启用调试日志(可选)
|
|
84
|
+
Logger.setLevel(LogLevel.DEBUG);
|
|
85
|
+
|
|
86
|
+
const printer = new BluetoothPrinter();
|
|
87
|
+
|
|
88
|
+
// 监听进度
|
|
89
|
+
printer.on('progress', ({ sent, total }) => {
|
|
90
|
+
const percent = ((sent / total) * 100).toFixed(1);
|
|
91
|
+
console.log(`打印进度: ${percent}%`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 监听错误
|
|
95
|
+
printer.on('error', error => {
|
|
96
|
+
console.error('打印错误:', error.code, error.message);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// 监听完成
|
|
100
|
+
printer.on('print-complete', () => {
|
|
101
|
+
console.log('打印完成!');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await printer.connect(deviceId);
|
|
105
|
+
await printer.text('Hello').feed().print();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 断点续传示例
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const printer = new BluetoothPrinter();
|
|
112
|
+
|
|
113
|
+
await printer.connect(deviceId);
|
|
114
|
+
|
|
115
|
+
// 构建大量打印内容
|
|
116
|
+
printer
|
|
117
|
+
.text('第1页内容...')
|
|
118
|
+
.feed(10)
|
|
119
|
+
.text('第2页内容...')
|
|
120
|
+
.feed(10)
|
|
121
|
+
.text('第3页内容...');
|
|
122
|
+
|
|
123
|
+
// 开始打印(异步)
|
|
124
|
+
const printPromise = printer.print();
|
|
125
|
+
|
|
126
|
+
// 5秒后暂停
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
printer.pause();
|
|
129
|
+
console.log('已暂停,剩余:', printer.remaining(), '字节');
|
|
130
|
+
}, 5000);
|
|
131
|
+
|
|
132
|
+
// 再过5秒恢复
|
|
133
|
+
setTimeout(async () => {
|
|
134
|
+
await printer.resume();
|
|
135
|
+
console.log('已恢复打印');
|
|
136
|
+
}, 10000);
|
|
137
|
+
|
|
138
|
+
await printPromise;
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 图片打印
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import Taro from '@tarojs/taro';
|
|
145
|
+
|
|
146
|
+
// 从 Canvas 获取图片数据
|
|
147
|
+
const canvas = Taro.createCanvasContext('myCanvas');
|
|
148
|
+
// ... 绘制内容 ...
|
|
149
|
+
|
|
150
|
+
Taro.canvasGetImageData({
|
|
151
|
+
canvasId: 'myCanvas',
|
|
152
|
+
x: 0,
|
|
153
|
+
y: 0,
|
|
154
|
+
width: 200,
|
|
155
|
+
height: 100,
|
|
156
|
+
success: res => {
|
|
157
|
+
const imageData = new Uint8Array(res.data);
|
|
158
|
+
|
|
159
|
+
printer
|
|
160
|
+
.image(imageData, res.width, res.height)
|
|
161
|
+
.feed(2)
|
|
162
|
+
.print();
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 弱网适配
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// 配置重试和分片参数
|
|
171
|
+
printer.setOptions({
|
|
172
|
+
chunkSize: 20, // 每次发送20字节
|
|
173
|
+
delay: 30, // 分片间延迟30ms
|
|
174
|
+
retries: 5, // 失败重试5次
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await printer.text('测试内容').print();
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 📚 文档
|
|
181
|
+
|
|
182
|
+
完整文档请访问:**[https://agions.github.io/taro-bluetooth-print/](https://agions.github.io/taro-bluetooth-print/)**
|
|
183
|
+
|
|
184
|
+
- [安装指南](https://agions.github.io/taro-bluetooth-print/guide/getting-started)
|
|
185
|
+
- [功能特性](https://agions.github.io/taro-bluetooth-print/guide/features)
|
|
186
|
+
- [核心概念](https://agions.github.io/taro-bluetooth-print/guide/core-concepts)
|
|
187
|
+
- [高级用法](https://agions.github.io/taro-bluetooth-print/guide/advanced)
|
|
188
|
+
- [API 参考](https://agions.github.io/taro-bluetooth-print/api)
|
|
189
|
+
- [故障排除](https://agions.github.io/taro-bluetooth-print/guide/troubleshooting)
|
|
190
|
+
|
|
191
|
+
## 🎯 核心 API
|
|
192
|
+
|
|
193
|
+
### BluetoothPrinter
|
|
194
|
+
|
|
195
|
+
| 方法 | 说明 | 返回值 |
|
|
196
|
+
|------|------|--------|
|
|
197
|
+
| `connect(deviceId)` | 连接蓝牙设备 | `Promise<this>` |
|
|
198
|
+
| `disconnect()` | 断开连接 | `Promise<void>` |
|
|
199
|
+
| `text(content, encoding?)` | 添加文本 | `this` |
|
|
200
|
+
| `feed(lines?)` | 换行 | `this` |
|
|
201
|
+
| `image(data, width, height)` | 打印图片 | `this` |
|
|
202
|
+
| `qr(content, options?)` | 打印二维码 | `this` |
|
|
203
|
+
| `cut()` | 切纸 | `this` |
|
|
204
|
+
| `setOptions(options)` | 设置适配器参数 | `this` |
|
|
205
|
+
| `print()` | 发送打印 | `Promise<void>` |
|
|
206
|
+
| `pause()` | 暂停打印 | `void` |
|
|
207
|
+
| `resume()` | 恢复打印 | `Promise<void>` |
|
|
208
|
+
| `cancel()` | 取消打印 | `void` |
|
|
209
|
+
| `remaining()` | 获取剩余字节数 | `number` |
|
|
210
|
+
|
|
211
|
+
### 事件
|
|
212
|
+
|
|
213
|
+
| 事件名 | 数据类型 | 说明 |
|
|
214
|
+
|--------|---------|------|
|
|
215
|
+
| `state-change` | `PrinterState` | 连接状态变化 |
|
|
216
|
+
| `progress` | `{ sent, total }` | 打印进度 |
|
|
217
|
+
| `error` | `BluetoothPrintError` | 错误事件 |
|
|
218
|
+
| `connected` | `string` (deviceId) | 已连接 |
|
|
219
|
+
| `disconnected` | `string` (deviceId) | 已断开 |
|
|
220
|
+
| `print-complete` | `void` | 打印完成 |
|
|
221
|
+
|
|
222
|
+
## 🔧 配置选项
|
|
52
223
|
|
|
53
224
|
```typescript
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
225
|
+
interface IAdapterOptions {
|
|
226
|
+
chunkSize?: number; // 分片大小(默认: 20字节)
|
|
227
|
+
delay?: number; // 分片延迟(默认: 20ms)
|
|
228
|
+
retries?: number; // 重试次数(默认: 3)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface IQrOptions {
|
|
232
|
+
model?: 1 | 2; // 二维码模型(默认: 2)
|
|
233
|
+
size?: number; // 模块大小 1-16(默认: 6)
|
|
234
|
+
errorCorrection?: 'L'|'M'|'Q'|'H'; // 纠错级别(默认: 'M')
|
|
235
|
+
}
|
|
65
236
|
```
|
|
66
237
|
|
|
67
|
-
##
|
|
238
|
+
## 🌐 平台支持
|
|
239
|
+
|
|
240
|
+
| 平台 | 支持情况 | 说明 |
|
|
241
|
+
|------|---------|------|
|
|
242
|
+
| 微信小程序 | ✅ | 完全支持 |
|
|
243
|
+
| H5 | ✅ | 需要浏览器支持 Web Bluetooth |
|
|
244
|
+
| React Native | ✅ | 通过 Taro RN |
|
|
245
|
+
| 支付宝小程序 | ⚠️ | 需要适配蓝牙 API |
|
|
246
|
+
| 百度小程序 | ⚠️ | 需要适配蓝牙 API |
|
|
247
|
+
|
|
248
|
+
## 🏗️ 架构设计
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
┌─────────────────────────────────────┐
|
|
252
|
+
│ BluetoothPrinter (Core) │
|
|
253
|
+
│ - 连接管理 │
|
|
254
|
+
│ - 打印队列 │
|
|
255
|
+
│ - 事件系统 │
|
|
256
|
+
│ - 断点续传 │
|
|
257
|
+
└────────┬────────────────┬───────────┘
|
|
258
|
+
│ │
|
|
259
|
+
┌────▼─────┐ ┌────▼──────┐
|
|
260
|
+
│ Adapter │ │ Driver │
|
|
261
|
+
│ 层 │ │ 层 │
|
|
262
|
+
└──────────┘ └───────────┘
|
|
263
|
+
│ │
|
|
264
|
+
┌────▼─────┐ ┌────▼──────┐
|
|
265
|
+
│Taro │ │ESC/POS │
|
|
266
|
+
│Adapter │ │Driver │
|
|
267
|
+
└──────────┘ └───────────┘
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- **Core 层**: 核心业务逻辑
|
|
271
|
+
- **Adapter 层**: 平台适配(Taro、Web Bluetooth 等)
|
|
272
|
+
- **Driver 层**: 打印机协议(ESC/POS、TSPL 等)
|
|
273
|
+
|
|
274
|
+
## 🤝 贡献
|
|
275
|
+
|
|
276
|
+
欢迎贡献!请查看 [贡献指南](./CONTRIBUTING.md)。
|
|
277
|
+
|
|
278
|
+
### 开发设置
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# 克隆仓库
|
|
282
|
+
git clone https://github.com/agions/taro-bluetooth-print.git
|
|
283
|
+
cd taro-bluetooth-print
|
|
284
|
+
|
|
285
|
+
# 安装依赖
|
|
286
|
+
npm install
|
|
287
|
+
|
|
288
|
+
# 运行测试
|
|
289
|
+
npm test
|
|
290
|
+
|
|
291
|
+
# 构建
|
|
292
|
+
npm run build
|
|
293
|
+
|
|
294
|
+
# 本地文档
|
|
295
|
+
npm run docs:dev
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## 📄 许可证
|
|
299
|
+
|
|
300
|
+
[MIT](./LICENSE) © Agions
|
|
68
301
|
|
|
69
|
-
|
|
302
|
+
## 🙏 致谢
|
|
70
303
|
|
|
71
|
-
|
|
72
|
-
* `disconnect(): Promise<void>`
|
|
73
|
-
* `text(content: string, encoding?: string): this`
|
|
74
|
-
* `feed(lines?: number): this`
|
|
75
|
-
* `cut(): this`
|
|
76
|
-
* `image(data: Uint8Array, width: number, height: number): this`
|
|
77
|
-
* `print(): Promise<void>`
|
|
304
|
+
感谢所有贡献者的支持!
|
|
78
305
|
|
|
79
|
-
|
|
306
|
+
---
|
|
80
307
|
|
|
81
|
-
|
|
308
|
+
<p align="center">
|
|
309
|
+
Made with ❤️ by <a href="https://github.com/agions">Agions</a>
|
|
310
|
+
</p>
|
package/dist/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=(t,e,i)=>new Promise((r,n)=>{var s=t=>{try{a(i.next(t))}catch(e){n(e)}},c=t=>{try{a(i.throw(t))}catch(e){n(e)}},a=t=>t.done?r(t.value):Promise.resolve(t.value).then(s,c);a((i=i.apply(t,e)).next())});Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});var e=(t=>(t.DISCONNECTED="disconnected",t.CONNECTING="connecting",t.CONNECTED="connected",t.DISCONNECTING="disconnecting",t))(e||{});const i=class{static encode(t,e="GBK"){return"UTF-8"===e||"utf-8"===e||console.warn(`Encoding ${e} not fully supported in simplified version, falling back to UTF-8.`),this.encoder.encode(t)}};i.encoder=new TextEncoder;let r=i;class n{static toBitmap(t,e,i){const r=Math.ceil(e/8),n=new Uint8Array(r*i);for(let s=0;s<i;s++)for(let i=0;i<e;i++){const c=4*(s*e+i);if(.299*t[c]+.587*t[c+1]+.114*t[c+2]<128){const t=7-i%8;n[s*r+Math.floor(i/8)]|=1<<t}}return n}}class s{init(){return[new Uint8Array([27,64])]}text(t,e="GBK"){return[r.encode(t,e)]}feed(t=1){return[new Uint8Array([27,100,t])]}cut(){return[new Uint8Array([29,86,0])]}image(t,e,i){const r=n.toBitmap(t,e,i),s=Math.ceil(e/8)%256,c=Math.floor(Math.ceil(e/8)/256),a=i%256,o=Math.floor(i/256);return[new Uint8Array([29,118,48,0,s,c,a,o]),r]}}class c{connect(i){return t(this,null,function*(){this.updateState(e.CONNECTING);try{yield Taro.createBLEConnection({deviceId:i}),this.updateState(e.CONNECTED),Taro.onBLEConnectionStateChange(t=>{t.deviceId!==i||t.connected||this.updateState(e.DISCONNECTED)})}catch(t){throw this.updateState(e.DISCONNECTED),t}})}disconnect(i){return t(this,null,function*(){this.updateState(e.DISCONNECTING);try{yield Taro.closeBLEConnection({deviceId:i}),this.updateState(e.DISCONNECTED)}catch(t){this.updateState(e.DISCONNECTED)}})}write(e,i){return t(this,null,function*(){const t=yield Taro.getBLEDeviceServices({deviceId:e});let r="",n="";for(const i of t.services){const t=(yield Taro.getBLEDeviceCharacteristics({deviceId:e,serviceId:i.uuid})).characteristics.find(t=>t.properties.write||t.properties.writeWithoutResponse);if(t){r=i.uuid,n=t.uuid;break}}if(!r||!n)throw new Error("No writeable characteristic found");const s=new Uint8Array(i);for(let i=0;i<s.length;i+=20){const t=s.slice(i,i+20);yield Taro.writeBLECharacteristicValue({deviceId:e,serviceId:r,characteristicId:n,value:t.buffer}),yield new Promise(t=>setTimeout(t,20))}})}onStateChange(t){this.stateCallback=t}updateState(t){this.stateCallback&&this.stateCallback(t)}}exports.BluetoothPrinter=class{constructor(t,i){var r,n;this.deviceId=null,this.buffer=[],this.state=e.DISCONNECTED,this.adapter=t||new c,this.driver=i||new s,null==(n=(r=this.adapter).onStateChange)||n.call(r,t=>{this.state=t})}connect(e){return t(this,null,function*(){return this.deviceId=e,yield this.adapter.connect(e),this.buffer.push(...this.driver.init()),this})}disconnect(){return t(this,null,function*(){this.deviceId&&(yield this.adapter.disconnect(this.deviceId),this.deviceId=null)})}text(t,e){return this.buffer.push(...this.driver.text(t,e)),this}feed(t=1){return this.buffer.push(...this.driver.feed(t)),this}cut(){return this.buffer.push(...this.driver.cut()),this}image(t,e,i){return this.buffer.push(...this.driver.image(t,e,i)),this}print(){return t(this,null,function*(){if(!this.deviceId)throw new Error("Printer not connected");const t=this.buffer.reduce((t,e)=>t+e.length,0),e=new Uint8Array(t);let i=0;for(const r of this.buffer)e.set(r,i),i+=r.length;yield this.adapter.write(this.deviceId,e.buffer),this.buffer=[]})}},exports.EscPos=s,exports.PrinterState=e,exports.TaroAdapter=c;
|
|
1
|
+
"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,s=(t,i,r)=>i in t?e(t,i,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[i]=r,n=(e,n)=>{for(var o in n||(n={}))i.call(n,o)&&s(e,o,n[o]);if(t)for(var o of t(n))r.call(n,o)&&s(e,o,n[o]);return e},o=(e,t,i)=>new Promise((r,s)=>{var n=e=>{try{c(i.next(e))}catch(t){s(t)}},o=e=>{try{c(i.throw(e))}catch(t){s(t)}},c=e=>e.done?r(e.value):Promise.resolve(e.value).then(n,o);c((i=i.apply(e,t)).next())});Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});var c=(e=>(e.DISCONNECTED="disconnected",e.CONNECTING="connecting",e.CONNECTED="connected",e.DISCONNECTING="disconnecting",e.PRINTING="printing",e.PAUSED="paused",e))(c||{}),a=(e=>(e[e.DEBUG=0]="DEBUG",e[e.INFO=1]="INFO",e[e.WARN=2]="WARN",e[e.ERROR=3]="ERROR",e[e.NONE=4]="NONE",e))(a||{});const l=class e{static setLevel(e){this.level=e}static getLevel(){return this.level}static debug(e,...t){this.level<=0&&console.log(`${this.prefix} [DEBUG]`,e,...t)}static info(e,...t){this.level<=1&&console.log(`${this.prefix} [INFO]`,e,...t)}static warn(e,...t){this.level<=2&&console.warn(`${this.prefix} [WARN]`,e,...t)}static error(e,...t){this.level<=3&&console.error(`${this.prefix} [ERROR]`,e,...t)}static scope(t){const i=`${this.prefix}:${t}`;return{debug:(t,...r)=>{e.level<=0&&console.log(`${i} [DEBUG]`,t,...r)},info:(t,...r)=>{e.level<=1&&console.log(`${i} [INFO]`,t,...r)},warn:(t,...r)=>{e.level<=2&&console.warn(`${i} [WARN]`,t,...r)},error:(t,...r)=>{e.level<=3&&console.error(`${i} [ERROR]`,t,...r)}}}};l.level=2,l.prefix="[TaroBTPrint]";let h=l;const d=h.scope("Encoding"),u=class{static encode(e,t="GBK"){switch(t.toUpperCase().replace("-","")){case"UTF8":case"UTF-8":return d.debug(`Encoding text with UTF-8: "${e.substring(0,30)}..."`),this.utf8Encoder.encode(e);case"GBK":case"GB2312":return d.warn("GBK encoding not yet implemented, falling back to UTF-8. This may cause display issues with some printers."),this.utf8Encoder.encode(e);default:return d.warn(`Unsupported encoding: ${t}, falling back to UTF-8`),this.utf8Encoder.encode(e)}}static isSupported(e){const t=e.toUpperCase().replace("-","");return"UTF8"===t||"UTF-8"===t}};u.utf8Encoder=new TextEncoder;let g=u;class f{static toBitmap(e,t,i){const r=new Uint8Array(e),s=Math.ceil(t/8),n=new Uint8Array(s*i);for(let o=0;o<i;o++)for(let e=0;e<t;e++){const c=4*(o*t+e),a=Math.round(.299*r[c]+.587*r[c+1]+.114*r[c+2]),l=a<128?0:255;if(0===l){const t=7-e%8;n[o*s+Math.floor(e/8)]|=1<<t}const h=a-l;e+1<t&&f.addError(r,t,e+1,o,7*h/16),e-1>=0&&o+1<i&&f.addError(r,t,e-1,o+1,3*h/16),o+1<i&&f.addError(r,t,e,o+1,5*h/16),e+1<t&&o+1<i&&f.addError(r,t,e+1,o+1,1*h/16)}return n}static addError(e,t,i,r,s){const n=4*(r*t+i),o=t=>{const i=e[t]+s;e[t]=Math.max(0,Math.min(255,i))};o(n),o(n+1),o(n+2)}}class E{constructor(){this.logger=h.scope("EscPos")}init(){return this.logger.debug("Generating init command"),[new Uint8Array([27,64])]}text(e,t="GBK"){this.logger.debug(`Generating text command: "${e.substring(0,30)}..." (${t})`);return[g.encode(e,t)]}feed(e=1){return this.logger.debug(`Generating feed command: ${e} lines`),[new Uint8Array([27,100,e])]}cut(){return this.logger.debug("Generating cut command"),[new Uint8Array([29,86,0])]}image(e,t,i){this.logger.debug(`Generating image command: ${t}x${i}`);const r=f.toBitmap(e,t,i),s=Math.ceil(t/8)%256,n=Math.floor(Math.ceil(t/8)/256),o=i%256,c=Math.floor(i/256),a=new Uint8Array([29,118,48,0,s,n,o,c]);return this.logger.debug(`Image header: mode=0, width_bytes=${s+256*n}, height=${o+256*c}`),[a,r]}qr(e,t){var i,r,s,n;const o=null!=(i=null==t?void 0:t.model)?i:2,c=null!=(r=null==t?void 0:t.size)?r:6,a=null!=(s=null==t?void 0:t.errorCorrection)?s:"M";this.logger.debug(`Generating QR code: model=${o}, size=${c}, ec=${a}`);const l=[];l.push(new Uint8Array([29,40,107,4,0,49,65,1===o?49:50,0])),l.push(new Uint8Array([29,40,107,3,0,49,67,c]));const h=null!=(n={L:48,M:49,Q:50,H:51}[a])?n:49;l.push(new Uint8Array([29,40,107,3,0,49,69,h]));const d=g.encode(e,"GBK"),u=d.length+3,f=u%256,E=Math.floor(u/256);return l.push(new Uint8Array([29,40,107,f,E,49,80,48])),l.push(d),l.push(new Uint8Array([29,40,107,3,0,49,81,48])),this.logger.debug(`QR code commands generated: ${l.length} buffers`),l}}var I=(e=>(e.CONNECTION_FAILED="CONNECTION_FAILED",e.CONNECTION_TIMEOUT="CONNECTION_TIMEOUT",e.DEVICE_NOT_FOUND="DEVICE_NOT_FOUND",e.DEVICE_DISCONNECTED="DEVICE_DISCONNECTED",e.SERVICE_NOT_FOUND="SERVICE_NOT_FOUND",e.CHARACTERISTIC_NOT_FOUND="CHARACTERISTIC_NOT_FOUND",e.SERVICE_DISCOVERY_FAILED="SERVICE_DISCOVERY_FAILED",e.WRITE_FAILED="WRITE_FAILED",e.WRITE_TIMEOUT="WRITE_TIMEOUT",e.PRINT_JOB_IN_PROGRESS="PRINT_JOB_IN_PROGRESS",e.PRINT_JOB_CANCELLED="PRINT_JOB_CANCELLED",e.PRINT_JOB_FAILED="PRINT_JOB_FAILED",e.INVALID_CONFIGURATION="INVALID_CONFIGURATION",e.ENCODING_NOT_SUPPORTED="ENCODING_NOT_SUPPORTED",e.INVALID_IMAGE_DATA="INVALID_IMAGE_DATA",e.INVALID_QR_DATA="INVALID_QR_DATA",e))(I||{});class p extends Error{constructor(e,t,i){super(t),this.code=e,this.originalError=i,this.name="BluetoothPrintError",Error.captureStackTrace&&Error.captureStackTrace(this,p)}toString(){let e=`${this.name} [${this.code}]: ${this.message}`;return this.originalError&&(e+=`\nCaused by: ${this.originalError.message}`),e}}class N{constructor(){this.serviceCache=new Map,this.logger=h.scope("TaroAdapter")}connect(e){return o(this,null,function*(){this.updateState(c.CONNECTING),this.logger.debug("Connecting to device:",e);try{yield Taro.createBLEConnection({deviceId:e}),this.logger.info("BLE connection established"),yield this.discoverServices(e),this.updateState(c.CONNECTED),this.logger.info("Device connected successfully"),Taro.onBLEConnectionStateChange(t=>{t.deviceId!==e||t.connected||(this.logger.warn("Device disconnected unexpectedly"),this.updateState(c.DISCONNECTED),this.serviceCache.delete(e))})}catch(t){throw this.updateState(c.DISCONNECTED),this.logger.error("Connection failed:",t),new p(I.CONNECTION_FAILED,`Failed to connect to device ${e}`,t)}})}disconnect(e){return o(this,null,function*(){this.updateState(c.DISCONNECTING),this.logger.debug("Disconnecting from device:",e);try{yield Taro.closeBLEConnection({deviceId:e}),this.serviceCache.delete(e),this.updateState(c.DISCONNECTED),this.logger.info("Device disconnected successfully")}catch(t){this.logger.warn("Disconnect error (ignored):",t),this.serviceCache.delete(e),this.updateState(c.DISCONNECTED)}})}write(e,t,i){return o(this,null,function*(){var r,s,n;const o=this.serviceCache.get(e);if(!o)throw new p(I.SERVICE_NOT_FOUND,"Device not connected or services not discovered. Call connect() first.");const c=null!=(r=null==i?void 0:i.chunkSize)?r:20,a=null!=(s=null==i?void 0:i.delay)?s:20,l=null!=(n=null==i?void 0:i.retries)?n:3,h=new Uint8Array(t),d=Math.ceil(h.length/c);this.logger.debug(`Writing ${h.length} bytes in ${d} chunks`);for(let t=0;t<h.length;t+=c){const i=h.slice(t,t+c),r=Math.floor(t/c)+1;let s=0;for(;s<=l;)try{yield Taro.writeBLECharacteristicValue({deviceId:e,serviceId:o.serviceId,characteristicId:o.characteristicId,value:i.buffer}),this.logger.debug(`Chunk ${r}/${d} written successfully`);break}catch(u){if(s++,s>l)throw this.logger.error(`Chunk ${r} failed after ${l} retries`),new p(I.WRITE_FAILED,`Failed to write chunk ${r}/${d}`,u);this.logger.warn(`Chunk ${r} write failed, retry ${s}/${l}`),yield new Promise(e=>setTimeout(e,2*a))}t+c<h.length&&(yield new Promise(e=>setTimeout(e,a)))}this.logger.info(`Successfully wrote ${h.length} bytes`)})}onStateChange(e){this.stateCallback=e}discoverServices(e){return o(this,null,function*(){this.logger.debug("Discovering services for device:",e);try{const t=yield Taro.getBLEDeviceServices({deviceId:e});for(const i of t.services){const t=(yield Taro.getBLEDeviceCharacteristics({deviceId:e,serviceId:i.uuid})).characteristics.find(e=>e.properties.write||e.properties.writeWithoutResponse);if(t)return this.serviceCache.set(e,{serviceId:i.uuid,characteristicId:t.uuid}),void this.logger.info("Found writeable characteristic:",{service:i.uuid,characteristic:t.uuid})}throw new p(I.CHARACTERISTIC_NOT_FOUND,"No writeable characteristic found. Make sure the device is a supported printer.")}catch(t){if(t instanceof p)throw t;throw new p(I.SERVICE_DISCOVERY_FAILED,"Failed to discover device services",t)}})}updateState(e){this.stateCallback&&this.stateCallback(e)}}class C{constructor(){this.listeners=new Map}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>this.off(e,t)}once(e,t){const i=r=>{t(r),this.off(e,i)};return this.on(e,i)}off(e,t){const i=this.listeners.get(e);i&&(i.delete(t),0===i.size&&this.listeners.delete(e))}emit(e,...t){const i=this.listeners.get(e);if(i){const r=t[0];i.forEach(t=>{try{t(r)}catch(i){console.error(`Error in event handler for "${String(e)}":`,i)}})}}removeAllListeners(){this.listeners.clear()}listenerCount(e){var t,i;return null!=(i=null==(t=this.listeners.get(e))?void 0:t.size)?i:0}}const b={adapter:{chunkSize:20,delay:20,retries:3,timeout:1e4},driver:{encoding:"GBK",paperWidth:58},logging:{level:a.WARN}};exports.BluetoothPrintError=p,exports.BluetoothPrinter=class extends C{constructor(e,t){var i,r;super(),this.deviceId=null,this.buffer=[],this.adapterOptions={},this.isPaused=!1,this.jobBuffer=null,this.jobOffset=0,this.logger=h.scope("BluetoothPrinter"),this.state=c.DISCONNECTED,this.adapter=e||new N,this.driver=t||new E,null==(r=(i=this.adapter).onStateChange)||r.call(i,e=>{this.state=e,this.emit("state-change",e),this.logger.debug("State changed:",e)})}connect(e){return o(this,null,function*(){this.logger.info("Connecting to device:",e);try{return this.deviceId=e,yield this.adapter.connect(e),this.buffer.push(...this.driver.init()),this.emit("connected",e),this.logger.info("Connected successfully"),this}catch(t){this.deviceId=null;const e=t instanceof p?t:new p(I.CONNECTION_FAILED,"Connection failed",t);throw this.emit("error",e),e}})}disconnect(){return o(this,null,function*(){if(!this.deviceId)return void this.logger.warn("Disconnect called but no device connected");const e=this.deviceId;this.logger.info("Disconnecting from device:",e);try{yield this.adapter.disconnect(e),this.deviceId=null,this.buffer=[],this.jobBuffer=null,this.jobOffset=0,this.emit("disconnected",e),this.logger.info("Disconnected successfully")}catch(t){const e=new p(I.DEVICE_DISCONNECTED,"Disconnect failed",t);throw this.emit("error",e),e}})}text(e,t){return this.logger.debug("Adding text:",e.substring(0,50)),this.buffer.push(...this.driver.text(e,t)),this}feed(e=1){return this.logger.debug("Adding feed:",e),this.buffer.push(...this.driver.feed(e)),this}cut(){return this.logger.debug("Adding cut command"),this.buffer.push(...this.driver.cut()),this}image(e,t,i){return this.logger.debug(`Adding image: ${t}x${i}`),this.buffer.push(...this.driver.image(e,t,i)),this}qr(e,t){return this.logger.debug("Adding QR code:",e.substring(0,50)),this.buffer.push(...this.driver.qr(e,t)),this}setOptions(e){return this.adapterOptions=n(n({},this.adapterOptions),e),this.logger.debug("Adapter options updated:",this.adapterOptions),this}pause(){this.isPaused=!0,this.state=c.PAUSED,this.emit("state-change",c.PAUSED),this.logger.info("Print job paused")}resume(){return o(this,null,function*(){if(this.isPaused&&this.jobBuffer){this.isPaused=!1,this.state=c.PRINTING,this.emit("state-change",c.PRINTING),this.logger.info("Print job resumed");try{yield this.processJob()}catch(e){const t=new p(I.PRINT_JOB_FAILED,"Failed to resume print job",e);throw this.emit("error",t),t}}else this.logger.warn("Resume called but job not paused or no job buffer")})}cancel(){this.isPaused=!1,this.jobBuffer=null,this.jobOffset=0,this.buffer=[],this.state=this.deviceId?c.CONNECTED:c.DISCONNECTED,this.emit("state-change",this.state),this.logger.info("Print job cancelled")}remaining(){return this.jobBuffer?this.jobBuffer.length-this.jobOffset:this.buffer.reduce((e,t)=>e+t.length,0)}print(){return o(this,null,function*(){if(!this.deviceId)throw new p(I.CONNECTION_FAILED,"Printer not connected. Call connect() first.");if(this.jobBuffer)throw new p(I.PRINT_JOB_IN_PROGRESS,"A print job is already in progress. Wait for completion or cancel it.");const e=this.buffer.reduce((e,t)=>e+t.length,0);this.logger.info(`Starting print job: ${e} bytes`);const t=new Uint8Array(e);let i=0;for(const s of this.buffer)t.set(s,i),i+=s.length;this.jobBuffer=t,this.jobOffset=0,this.buffer=[],this.isPaused=!1;try{yield this.processJob(),this.emit("print-complete"),this.logger.info("Print job completed successfully")}catch(r){const e=r instanceof p?r:new p(I.PRINT_JOB_FAILED,"Print job failed",r);throw this.emit("error",e),e}})}processJob(){return o(this,null,function*(){if(!this.jobBuffer||!this.deviceId)return;const e=this.jobBuffer.length;for(;this.jobOffset<this.jobBuffer.length;){if(this.isPaused)return void this.logger.debug("Job paused at offset:",this.jobOffset);const t=Math.min(this.jobOffset+512,this.jobBuffer.length),i=this.jobBuffer.slice(this.jobOffset,t);yield this.adapter.write(this.deviceId,i.buffer,this.adapterOptions),this.jobOffset=t,this.emit("progress",{sent:this.jobOffset,total:e})}this.jobBuffer=null,this.jobOffset=0})}},exports.DEFAULT_CONFIG=b,exports.Encoding=g,exports.ErrorCode=I,exports.EscPos=E,exports.EventEmitter=C,exports.ImageProcessing=f,exports.LogLevel=a,exports.Logger=h,exports.PrinterState=c,exports.TaroAdapter=N,exports.mergeConfig=function(e,t){return{adapter:n(n(n({},b.adapter),e.adapter),t.adapter),driver:n(n(n({},b.driver),e.driver),t.driver),logging:n(n(n({},b.logging),e.logging),t.logging)}};
|