@m430/capacitor-label-printer 0.1.0

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.
Files changed (150) hide show
  1. package/API.md +218 -0
  2. package/LICENSE +21 -0
  3. package/M430CapacitorLabelPrinter.podspec +21 -0
  4. package/README.md +190 -0
  5. package/android/build.gradle +46 -0
  6. package/android/libs/.gitkeep +1 -0
  7. package/android/libs/fat-generic-tspl-bluetooth-classic-0.1.16-GA.jar +0 -0
  8. package/android/src/main/AndroidManifest.xml +12 -0
  9. package/android/src/main/java/com/m430/capacitor/labelprinter/.gitkeep +1 -0
  10. package/android/src/main/java/com/m430/capacitor/labelprinter/AndroidPrinterManager.java +132 -0
  11. package/android/src/main/java/com/m430/capacitor/labelprinter/AndroidStatusMapper.java +54 -0
  12. package/android/src/main/java/com/m430/capacitor/labelprinter/LabelPrinterPlugin.java +273 -0
  13. package/android/src/main/java/com/m430/capacitor/labelprinter/VendorAndroidDeviceCatalog.java +81 -0
  14. package/android/src/main/java/com/m430/capacitor/labelprinter/VendorAndroidPrinterSession.java +189 -0
  15. package/capacitor-label-printer.podspec +21 -0
  16. package/dist/docs.json +443 -0
  17. package/dist/esm/cpcl/builder.d.ts +10 -0
  18. package/dist/esm/cpcl/builder.js +35 -0
  19. package/dist/esm/definitions.d.ts +107 -0
  20. package/dist/esm/definitions.js +1 -0
  21. package/dist/esm/errors.d.ts +1 -0
  22. package/dist/esm/errors.js +1 -0
  23. package/dist/esm/index.d.ts +9 -0
  24. package/dist/esm/index.js +11 -0
  25. package/dist/esm/tspl/builder.d.ts +13 -0
  26. package/dist/esm/tspl/builder.js +45 -0
  27. package/dist/esm/tspl/helpers.d.ts +2 -0
  28. package/dist/esm/tspl/helpers.js +6 -0
  29. package/dist/esm/types.d.ts +12 -0
  30. package/dist/esm/types.js +1 -0
  31. package/dist/esm/web.d.ts +21 -0
  32. package/dist/esm/web.js +34 -0
  33. package/dist/plugin.cjs.js +143 -0
  34. package/dist/plugin.cjs.js.map +1 -0
  35. package/dist/plugin.js +136 -0
  36. package/dist/plugin.js.map +1 -0
  37. package/ios/Plugin/.gitkeep +1 -0
  38. package/ios/Plugin/IOSPrinterManager.swift +230 -0
  39. package/ios/Plugin/IOSStatusMapper.swift +57 -0
  40. package/ios/Plugin/LabelPrinterPlugin.swift +141 -0
  41. package/ios/VendorFrameworks/.gitkeep +1 -0
  42. package/ios/VendorFrameworks/adapter.framework/Headers/BasedOtherConnectedDevice.h +17 -0
  43. package/ios/VendorFrameworks/adapter.framework/Headers/ConnectedDevice.h +213 -0
  44. package/ios/VendorFrameworks/adapter.framework/Headers/IPRTBlueToothBaseDef.h +22 -0
  45. package/ios/VendorFrameworks/adapter.framework/Headers/KeepStateConnectedDevice.h +18 -0
  46. package/ios/VendorFrameworks/adapter.framework/Headers/QueueConfig.h +18 -0
  47. package/ios/VendorFrameworks/adapter.framework/Headers/ReadOptions.h +22 -0
  48. package/ios/VendorFrameworks/adapter.framework/Headers/WroteReporter.h +23 -0
  49. package/ios/VendorFrameworks/adapter.framework/Headers/adapter.h +18 -0
  50. package/ios/VendorFrameworks/adapter.framework/Info.plist +0 -0
  51. package/ios/VendorFrameworks/adapter.framework/Modules/module.modulemap +6 -0
  52. package/ios/VendorFrameworks/adapter.framework/adapter +0 -0
  53. package/ios/VendorFrameworks/appleble.framework/Frameworks/adapter.framework/Info.plist +0 -0
  54. package/ios/VendorFrameworks/appleble.framework/Frameworks/adapter.framework/_CodeSignature/CodeResources +101 -0
  55. package/ios/VendorFrameworks/appleble.framework/Frameworks/adapter.framework/adapter +0 -0
  56. package/ios/VendorFrameworks/appleble.framework/Frameworks/ibridge.framework/Frameworks/adapter.framework/Info.plist +0 -0
  57. package/ios/VendorFrameworks/appleble.framework/Frameworks/ibridge.framework/Frameworks/adapter.framework/adapter +0 -0
  58. package/ios/VendorFrameworks/appleble.framework/Frameworks/ibridge.framework/Info.plist +0 -0
  59. package/ios/VendorFrameworks/appleble.framework/Frameworks/ibridge.framework/_CodeSignature/CodeResources +132 -0
  60. package/ios/VendorFrameworks/appleble.framework/Frameworks/ibridge.framework/ibridge +0 -0
  61. package/ios/VendorFrameworks/appleble.framework/Headers/AppleBle.h +25 -0
  62. package/ios/VendorFrameworks/appleble.framework/Info.plist +0 -0
  63. package/ios/VendorFrameworks/appleble.framework/Modules/module.modulemap +6 -0
  64. package/ios/VendorFrameworks/appleble.framework/_CodeSignature/CodeResources +252 -0
  65. package/ios/VendorFrameworks/appleble.framework/appleble +0 -0
  66. package/ios/VendorFrameworks/father.framework/Frameworks/adapter.framework/Info.plist +0 -0
  67. package/ios/VendorFrameworks/father.framework/Frameworks/adapter.framework/adapter +0 -0
  68. package/ios/VendorFrameworks/father.framework/Headers/Appendat.h +28 -0
  69. package/ios/VendorFrameworks/father.framework/Headers/Arg.h +22 -0
  70. package/ios/VendorFrameworks/father.framework/Headers/BasicBatteryVolumeArg.h +16 -0
  71. package/ios/VendorFrameworks/father.framework/Headers/BinaryCommand.h +17 -0
  72. package/ios/VendorFrameworks/father.framework/Headers/CPCLCommand.h +10 -0
  73. package/ios/VendorFrameworks/father.framework/Headers/CallbackData.h +26 -0
  74. package/ios/VendorFrameworks/father.framework/Headers/Checker.h +16 -0
  75. package/ios/VendorFrameworks/father.framework/Headers/Command.h +25 -0
  76. package/ios/VendorFrameworks/father.framework/Headers/CommandClause.h +33 -0
  77. package/ios/VendorFrameworks/father.framework/Headers/Commander.h +31 -0
  78. package/ios/VendorFrameworks/father.framework/Headers/DataWriteOperation.h +42 -0
  79. package/ios/VendorFrameworks/father.framework/Headers/DefaultCommand.h +16 -0
  80. package/ios/VendorFrameworks/father.framework/Headers/EasyArg.h +16 -0
  81. package/ios/VendorFrameworks/father.framework/Headers/FastBinary.h +20 -0
  82. package/ios/VendorFrameworks/father.framework/Headers/HexOutput.h +19 -0
  83. package/ios/VendorFrameworks/father.framework/Headers/IDataWriteCallback.h +25 -0
  84. package/ios/VendorFrameworks/father.framework/Headers/Lifecycle.h +21 -0
  85. package/ios/VendorFrameworks/father.framework/Headers/NewLineArg.h +15 -0
  86. package/ios/VendorFrameworks/father.framework/Headers/OnlyBinaryHeaderArg.h +15 -0
  87. package/ios/VendorFrameworks/father.framework/Headers/OnlyTextHeaderArg.h +16 -0
  88. package/ios/VendorFrameworks/father.framework/Headers/PImageTool.h +22 -0
  89. package/ios/VendorFrameworks/father.framework/Headers/PReplaceKit.h +16 -0
  90. package/ios/VendorFrameworks/father.framework/Headers/PSDK.h +60 -0
  91. package/ios/VendorFrameworks/father.framework/Headers/PVariableKit.h +18 -0
  92. package/ios/VendorFrameworks/father.framework/Headers/PsdkConst.h +33 -0
  93. package/ios/VendorFrameworks/father.framework/Headers/Raw.h +22 -0
  94. package/ios/VendorFrameworks/father.framework/Headers/SimpleCheck.h +16 -0
  95. package/ios/VendorFrameworks/father.framework/Headers/SingleCommand.h +32 -0
  96. package/ios/VendorFrameworks/father.framework/Headers/TSPLCommand.h +10 -0
  97. package/ios/VendorFrameworks/father.framework/Headers/WriteControl.h +12 -0
  98. package/ios/VendorFrameworks/father.framework/Headers/WriteOptions.h +28 -0
  99. package/ios/VendorFrameworks/father.framework/Headers/father.h +21 -0
  100. package/ios/VendorFrameworks/father.framework/Info.plist +0 -0
  101. package/ios/VendorFrameworks/father.framework/Modules/module.modulemap +6 -0
  102. package/ios/VendorFrameworks/father.framework/father +0 -0
  103. package/ios/VendorFrameworks/tspl.framework/Frameworks/father.framework/Frameworks/adapter.framework/Info.plist +0 -0
  104. package/ios/VendorFrameworks/tspl.framework/Frameworks/father.framework/Frameworks/adapter.framework/adapter +0 -0
  105. package/ios/VendorFrameworks/tspl.framework/Frameworks/father.framework/Info.plist +0 -0
  106. package/ios/VendorFrameworks/tspl.framework/Frameworks/father.framework/father +0 -0
  107. package/ios/VendorFrameworks/tspl.framework/Headers/BasicTSPL.h +146 -0
  108. package/ios/VendorFrameworks/tspl.framework/Headers/BasicTSPLArg.h +17 -0
  109. package/ios/VendorFrameworks/tspl.framework/Headers/GenericTSPL.h +16 -0
  110. package/ios/VendorFrameworks/tspl.framework/Headers/TBar.h +30 -0
  111. package/ios/VendorFrameworks/tspl.framework/Headers/TBarCode.h +54 -0
  112. package/ios/VendorFrameworks/tspl.framework/Headers/TBatteryVolume.h +19 -0
  113. package/ios/VendorFrameworks/tspl.framework/Headers/TBox.h +32 -0
  114. package/ios/VendorFrameworks/tspl.framework/Headers/TCircle.h +26 -0
  115. package/ios/VendorFrameworks/tspl.framework/Headers/TCleanBmpFlash.h +20 -0
  116. package/ios/VendorFrameworks/tspl.framework/Headers/TClear.h +20 -0
  117. package/ios/VendorFrameworks/tspl.framework/Headers/TCut.h +21 -0
  118. package/ios/VendorFrameworks/tspl.framework/Headers/TDensity.h +21 -0
  119. package/ios/VendorFrameworks/tspl.framework/Headers/TDirection.h +51 -0
  120. package/ios/VendorFrameworks/tspl.framework/Headers/TDmatrix.h +29 -0
  121. package/ios/VendorFrameworks/tspl.framework/Headers/TDownloadBmpFlash.h +16 -0
  122. package/ios/VendorFrameworks/tspl.framework/Headers/TGap.h +21 -0
  123. package/ios/VendorFrameworks/tspl.framework/Headers/TImage.h +36 -0
  124. package/ios/VendorFrameworks/tspl.framework/Headers/TLine.h +34 -0
  125. package/ios/VendorFrameworks/tspl.framework/Headers/TMddle.h +16 -0
  126. package/ios/VendorFrameworks/tspl.framework/Headers/TPage.h +22 -0
  127. package/ios/VendorFrameworks/tspl.framework/Headers/TPrint.h +21 -0
  128. package/ios/VendorFrameworks/tspl.framework/Headers/TQRCode.h +35 -0
  129. package/ios/VendorFrameworks/tspl.framework/Headers/TReadState.h +16 -0
  130. package/ios/VendorFrameworks/tspl.framework/Headers/TReverse.h +27 -0
  131. package/ios/VendorFrameworks/tspl.framework/Headers/TSN.h +19 -0
  132. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLCodeType.h +53 -0
  133. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLCorrectLevel.h +21 -0
  134. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLFont.h +83 -0
  135. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLImageMode.h +30 -0
  136. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLLineMode.h +37 -0
  137. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLLineType.h +21 -0
  138. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLOutDirection.h +27 -0
  139. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLRotation.h +36 -0
  140. package/ios/VendorFrameworks/tspl.framework/Headers/TSPLShowType.h +36 -0
  141. package/ios/VendorFrameworks/tspl.framework/Headers/TSPL_.h +18 -0
  142. package/ios/VendorFrameworks/tspl.framework/Headers/TSpeed.h +20 -0
  143. package/ios/VendorFrameworks/tspl.framework/Headers/TText.h +38 -0
  144. package/ios/VendorFrameworks/tspl.framework/Headers/TTextBox.h +44 -0
  145. package/ios/VendorFrameworks/tspl.framework/Headers/TVersion.h +19 -0
  146. package/ios/VendorFrameworks/tspl.framework/Headers/tspl.h +45 -0
  147. package/ios/VendorFrameworks/tspl.framework/Info.plist +0 -0
  148. package/ios/VendorFrameworks/tspl.framework/Modules/module.modulemap +6 -0
  149. package/ios/VendorFrameworks/tspl.framework/tspl +0 -0
  150. package/package.json +95 -0
package/API.md ADDED
@@ -0,0 +1,218 @@
1
+ # API
2
+
3
+ `@m430/capacitor-label-printer` 当前公开的是一组偏底层的打印能力,业务层建议自行封装打印模板、重试策略和任务队列。
4
+
5
+ ## 方法
6
+
7
+ ### `isSupported()`
8
+
9
+ 判断当前运行环境是否支持原生标签打印。
10
+
11
+ 返回:
12
+
13
+ ```ts
14
+ Promise<{ supported: boolean }>
15
+ ```
16
+
17
+ ### `checkPermissions()`
18
+
19
+ 查询当前蓝牙权限状态。
20
+
21
+ 返回:
22
+
23
+ ```ts
24
+ Promise<PrinterPermissionResult>
25
+ ```
26
+
27
+ ### `ensurePermissions()`
28
+
29
+ 确保蓝牙访问权限已就绪。
30
+
31
+ - Android:会主动触发“附近设备”权限申请
32
+ - iOS:首版会返回结构化权限状态,真实系统授权仍由蓝牙扫描/连接行为触发
33
+
34
+ 返回:
35
+
36
+ ```ts
37
+ Promise<PrinterPermissionResult>
38
+ ```
39
+
40
+ ### `discoverDevices(options?)`
41
+
42
+ 发现可用于连接的打印机列表。
43
+
44
+ 参数:
45
+
46
+ ```ts
47
+ interface DiscoverDevicesOptions {
48
+ timeout?: number;
49
+ namePrefixes?: string[];
50
+ }
51
+ ```
52
+
53
+ 返回:
54
+
55
+ ```ts
56
+ Promise<{ devices: PrinterDevice[] }>
57
+ ```
58
+
59
+ 说明:
60
+
61
+ - Android 上如果权限不足,插件会在内部先尝试申请权限
62
+ - 权限仍不足时会抛出 `PERMISSION_DENIED`
63
+
64
+ ### `connect(options)`
65
+
66
+ 连接指定打印机。
67
+
68
+ 参数:
69
+
70
+ ```ts
71
+ interface ConnectOptions {
72
+ deviceId: string;
73
+ }
74
+ ```
75
+
76
+ 返回:
77
+
78
+ ```ts
79
+ Promise<void>
80
+ ```
81
+
82
+ ### `disconnect()`
83
+
84
+ 断开当前打印机连接。
85
+
86
+ 返回:
87
+
88
+ ```ts
89
+ Promise<void>
90
+ ```
91
+
92
+ ### `getConnectionState()`
93
+
94
+ 查询当前连接状态。
95
+
96
+ 返回:
97
+
98
+ ```ts
99
+ Promise<{ state: PrinterConnectionState }>
100
+ ```
101
+
102
+ 其中 `PrinterConnectionState` 为:
103
+
104
+ ```ts
105
+ type PrinterConnectionState = 'disconnected' | 'connecting' | 'connected';
106
+ ```
107
+
108
+ ### `print(options)`
109
+
110
+ 发送原始打印负载到打印机。
111
+
112
+ 参数:
113
+
114
+ ```ts
115
+ interface PrintOptions {
116
+ payload: string;
117
+ language?: PrinterLanguage;
118
+ copies?: number;
119
+ }
120
+ ```
121
+
122
+ 其中 `PrinterLanguage` 为:
123
+
124
+ ```ts
125
+ type PrinterLanguage = 'tspl' | 'cpcl' | 'raw';
126
+ ```
127
+
128
+ 返回:
129
+
130
+ ```ts
131
+ Promise<void>
132
+ ```
133
+
134
+ ### `getStatus()`
135
+
136
+ 查询当前打印机状态。
137
+
138
+ 返回:
139
+
140
+ ```ts
141
+ Promise<PrinterStatus>
142
+ ```
143
+
144
+ ```ts
145
+ interface PrinterStatus {
146
+ connected: boolean;
147
+ ready?: boolean;
148
+ paperOut?: boolean;
149
+ coverOpen?: boolean;
150
+ overheating?: boolean;
151
+ message?: string;
152
+ raw?: unknown;
153
+ }
154
+ ```
155
+
156
+ ### `openAppSettings()`
157
+
158
+ 跳转到应用系统设置页。
159
+
160
+ 返回:
161
+
162
+ ```ts
163
+ Promise<void>
164
+ ```
165
+
166
+ ## 主要类型
167
+
168
+ ### `PrinterPermissionResult`
169
+
170
+ ```ts
171
+ interface PrinterPermissionResult {
172
+ granted: boolean;
173
+ canPrompt: boolean;
174
+ shouldOpenSettings: boolean;
175
+ permissions: {
176
+ bluetoothConnect?: PermissionState;
177
+ bluetoothScan?: PermissionState;
178
+ bluetooth?: PermissionState;
179
+ };
180
+ }
181
+ ```
182
+
183
+ ```ts
184
+ type PermissionState = 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied';
185
+ ```
186
+
187
+ ### `PrinterDevice`
188
+
189
+ ```ts
190
+ interface PrinterDevice {
191
+ id: string;
192
+ name: string;
193
+ address?: string;
194
+ transport: PrinterTransport;
195
+ bonded?: boolean;
196
+ rssi?: number;
197
+ }
198
+ ```
199
+
200
+ ```ts
201
+ type PrinterTransport = 'classic' | 'ble';
202
+ ```
203
+
204
+ ## 导出的 Builder 与 Helper
205
+
206
+ 除了 `LabelPrinter` 插件对象,包里还导出了:
207
+
208
+ - `CpclBuilder`
209
+ - `TsplBuilder`
210
+ - `mmToDots`
211
+ - `escapeTsplText`
212
+
213
+ 推荐业务层先用 `CpclBuilder` 或 `TsplBuilder` 组装 `payload`,再调用 `print()`。
214
+
215
+ ## 额外说明
216
+
217
+ - Web 端只有 `isSupported()` 和 `getConnectionState()` / `getStatus()` 的兜底返回,其余方法会抛错
218
+ - 自动生成的结构化 API 元数据位于 `dist/docs.json`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andy Zheng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+ repository_url = package.dig('repository', 'url').to_s.sub(/^git\+/, '')
5
+ homepage_url = repository_url.sub(/\.git$/, '')
6
+
7
+ Pod::Spec.new do |s|
8
+ s.name = 'M430CapacitorLabelPrinter'
9
+ s.version = package['version']
10
+ s.summary = package['description']
11
+ s.license = package['license']
12
+ s.homepage = homepage_url
13
+ s.author = package['author']
14
+ s.source = { git: repository_url, tag: s.version.to_s }
15
+ s.source_files = 'ios/Plugin/**/*.{swift,h,m,mm}'
16
+ s.vendored_frameworks = 'ios/VendorFrameworks/*.framework', 'ios/VendorFrameworks/*.xcframework'
17
+ s.preserve_paths = 'ios/VendorFrameworks/**/*'
18
+ s.ios.deployment_target = '13.0'
19
+ s.dependency 'Capacitor'
20
+ s.swift_version = '5.9'
21
+ end
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # @m430/capacitor-label-printer
2
+
3
+ 面向启锐 `QR-365` 及同类标签热敏打印机的 `Capacitor 7` 插件,内置 `CPCL` / `TSPL` 指令发送能力。
4
+
5
+ 这个包的目标不是把业务模板写死在插件里,而是提供一层可复用的原生打印通道,统一暴露设备发现、连接、状态查询和标签打印能力,并随 npm 包分发 Android `jar` 与 iOS `framework`。
6
+
7
+ ## 当前状态
8
+
9
+ - 已完成独立仓库、npm 包、Capacitor 插件骨架和 `example-app`
10
+ - 已完成 Android `jar` 与 iOS `framework` 的随包分发
11
+ - 已完成统一 JS API、`CPCL` / `TSPL` builder、Android/iOS 编译链路验证
12
+ - 当前 `AndroidPrinterManager` 与 `IOSPrinterManager` 仍是首版骨架,真实厂商 SDK 的会话管理、写入和状态回传逻辑需要继续结合真机联调补齐
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ npm install @m430/capacitor-label-printer
18
+ npx cap sync
19
+ ```
20
+
21
+ 如果宿主项目还没有添加原生平台,请先执行:
22
+
23
+ ```bash
24
+ npx cap add android
25
+ npx cap add ios
26
+ ```
27
+
28
+ ## 宿主项目权限
29
+
30
+ ### Android
31
+
32
+ Android 蓝牙权限已经随插件一起分发并参与宿主 Manifest 合并,通常不需要宿主项目再手写:
33
+
34
+ - `BLUETOOTH`、`BLUETOOTH_ADMIN`,并带 `maxSdkVersion=30`
35
+ - `BLUETOOTH_CONNECT`
36
+ - `BLUETOOTH_SCAN`,并带 `neverForLocation`
37
+
38
+ 体验约定:
39
+
40
+ - 首次调用 `discoverDevices()` 时,插件会自动触发“附近设备”权限申请
41
+ - 也可以先主动调用 `ensurePermissions()`,拿到结构化权限状态后再决定是否继续
42
+ - 如果用户永久拒绝权限,可调用 `openAppSettings()` 引导跳转系统设置页
43
+
44
+ ### iOS
45
+
46
+ 宿主项目需要在自己的 `Info.plist` 中补充蓝牙用途说明:
47
+
48
+ ```xml
49
+ <key>NSBluetoothAlwaysUsageDescription</key>
50
+ <string>App 需要连接蓝牙标签打印机以打印物流面单与条码标签</string>
51
+ <key>NSBluetoothPeripheralUsageDescription</key>
52
+ <string>App 需要访问蓝牙设备以完成标签打印</string>
53
+ ```
54
+
55
+ ## 快速使用
56
+
57
+ ```ts
58
+ import { CpclBuilder, LabelPrinter } from '@m430/capacitor-label-printer';
59
+
60
+ async function printDemoLabel() {
61
+ const support = await LabelPrinter.isSupported();
62
+ if (!support.supported) {
63
+ throw new Error('当前平台不支持原生标签打印');
64
+ }
65
+
66
+ const permissionResult = await LabelPrinter.ensurePermissions();
67
+ if (!permissionResult.granted) {
68
+ if (permissionResult.shouldOpenSettings) {
69
+ await LabelPrinter.openAppSettings();
70
+ }
71
+ throw new Error('需要先允许蓝牙附近设备权限');
72
+ }
73
+
74
+ const { devices } = await LabelPrinter.discoverDevices({
75
+ namePrefixes: ['QR', 'QIRUI', 'BEEPRT']
76
+ });
77
+
78
+ if (!devices.length) {
79
+ throw new Error('没有找到可用打印机');
80
+ }
81
+
82
+ await LabelPrinter.connect({ deviceId: devices[0].id });
83
+
84
+ const payload = new CpclBuilder()
85
+ .page(640, 1)
86
+ .pageWidth(576)
87
+ .text(4, 0, 40, 40, 'YT1234567890')
88
+ .barcode128(40, 120, 80, 'YT1234567890')
89
+ .form()
90
+ .print()
91
+ .build();
92
+
93
+ await LabelPrinter.print({
94
+ payload,
95
+ language: 'cpcl',
96
+ copies: 1
97
+ });
98
+
99
+ const status = await LabelPrinter.getStatus();
100
+ console.log(status);
101
+ }
102
+ ```
103
+
104
+ ## API 概览
105
+
106
+ 插件当前公开这些方法:
107
+
108
+ - `isSupported`
109
+ - `checkPermissions`
110
+ - `ensurePermissions`
111
+ - `discoverDevices`
112
+ - `connect`
113
+ - `disconnect`
114
+ - `getConnectionState`
115
+ - `print`
116
+ - `getStatus`
117
+ - `openAppSettings`
118
+
119
+ 完整 API 文档见 [API.md](./API.md)。
120
+
121
+ ## Helper
122
+
123
+ 已内置 `CpclBuilder`、`TsplBuilder` 与基础 helper,适合物流面单、条码标签这类“一张一张打”的场景:
124
+
125
+ - `page`
126
+ - `pageWidth`
127
+ - `sizeMm`
128
+ - `gapMm`
129
+ - `density`
130
+ - `speed`
131
+ - `cls`
132
+ - `text`
133
+ - `barcode128`
134
+ - `qrcode`
135
+ - `printCopies`
136
+
137
+ ## 平台说明
138
+
139
+ ### Android
140
+
141
+ - 当前集成的是厂商 `TSPL classic bluetooth` 方向的 `jar`
142
+ - 插件包内已带上 `android/libs/fat-generic-tspl-bluetooth-classic-0.1.16-GA.jar`
143
+ - 现阶段 `discoverDevices` 主要基于已配对设备过滤,适合作为 `QR-365` 的首版接入基线
144
+ - `discoverDevices()` 与 `connect()` 会在 Android 12+ 自动兜底附近设备权限
145
+
146
+ ### iOS
147
+
148
+ - 当前包内已带上 `ios/VendorFrameworks/` 下的厂商 `framework`
149
+ - `CocoaPods` 集成使用的是 `M430CapacitorLabelPrinter.podspec`
150
+ - 已验证 `npx cap sync ios` 与 `xcodebuild` 编译链路可通过
151
+ - iOS 仍需要宿主在 `Info.plist` 中声明蓝牙用途说明
152
+
153
+ ### Web
154
+
155
+ - 不支持
156
+ - `isSupported()` 返回 `false`
157
+ - 其余原生能力会抛出 `Label printing is not supported on web.`
158
+
159
+ ## 开发与发布校验
160
+
161
+ ```bash
162
+ npm run verify
163
+ npm run verify:ios
164
+ npm run verify:release
165
+ ```
166
+
167
+ 其中:
168
+
169
+ - `verify` 会执行单测、Android Gradle 构建和插件打包
170
+ - `verify:ios` 会执行 `example-app` 的 `cap sync ios` 与 `xcodebuild`
171
+ - `verify:release` 会串起完整发布前检查,并执行 `npm pack --dry-run`
172
+
173
+ ## 已随包分发的原生资源
174
+
175
+ - Android `jar`
176
+ - iOS `framework`
177
+ - iOS `podspec`
178
+
179
+ 宿主项目安装后不需要再单独下载一份厂商 SDK。
180
+
181
+ ## 已知限制
182
+
183
+ - 当前版本以“统一 API + 原生依赖分发 + 构建链路打通”为主
184
+ - Android 与 iOS 的真实打印链路还需要按厂商 SDK 文档继续接入
185
+ - 还没有内置打印队列、自动重连、模板编辑器和图片调试工具
186
+
187
+ ## 仓库
188
+
189
+ - GitHub: `https://github.com/m430/capacitor-label-printer`
190
+ - npm: `@m430/capacitor-label-printer`
@@ -0,0 +1,46 @@
1
+ ext {
2
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
3
+ }
4
+
5
+ buildscript {
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+ dependencies {
11
+ classpath 'com.android.tools.build:gradle:8.7.3'
12
+ }
13
+ }
14
+
15
+ apply plugin: 'com.android.library'
16
+
17
+ android {
18
+ namespace "com.m430.capacitor.labelprinter"
19
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
20
+ defaultConfig {
21
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
22
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
23
+ versionCode 1
24
+ versionName "0.1.0"
25
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
26
+ }
27
+ lintOptions {
28
+ abortOnError false
29
+ }
30
+ compileOptions {
31
+ sourceCompatibility JavaVersion.VERSION_17
32
+ targetCompatibility JavaVersion.VERSION_17
33
+ }
34
+ }
35
+
36
+ repositories {
37
+ google()
38
+ mavenCentral()
39
+ }
40
+
41
+ dependencies {
42
+ implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
43
+ implementation project(':capacitor-android')
44
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
45
+ testImplementation fileTree(dir: 'gradle/test-libs', include: ['*.jar'])
46
+ }
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,12 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission
3
+ android:name="android.permission.BLUETOOTH"
4
+ android:maxSdkVersion="30" />
5
+ <uses-permission
6
+ android:name="android.permission.BLUETOOTH_ADMIN"
7
+ android:maxSdkVersion="30" />
8
+ <uses-permission
9
+ android:name="android.permission.BLUETOOTH_SCAN"
10
+ android:usesPermissionFlags="neverForLocation" />
11
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
12
+ </manifest>
@@ -0,0 +1,132 @@
1
+ package com.m430.capacitor.labelprinter;
2
+
3
+ import com.getcapacitor.JSArray;
4
+ import com.getcapacitor.JSObject;
5
+ import java.io.IOException;
6
+ import java.nio.charset.StandardCharsets;
7
+ import java.util.List;
8
+
9
+ public class AndroidPrinterManager {
10
+ interface DeviceCatalog {
11
+ JSArray discover(List<String> prefixes);
12
+
13
+ PrinterSession openSession(String deviceId);
14
+ }
15
+
16
+ interface PrinterSession {
17
+ void connect() throws IOException;
18
+
19
+ void disconnect();
20
+
21
+ boolean isConnected();
22
+
23
+ String getConnectionState();
24
+
25
+ String getDeviceName();
26
+
27
+ void print(String language, byte[] payload) throws IOException;
28
+
29
+ byte[] queryStatus() throws IOException;
30
+ }
31
+
32
+ private final DeviceCatalog deviceCatalog;
33
+ private final AndroidStatusMapper statusMapper;
34
+ private PrinterSession session;
35
+ private String connectedDeviceId;
36
+
37
+ public AndroidPrinterManager(DeviceCatalog deviceCatalog, AndroidStatusMapper statusMapper) {
38
+ this.deviceCatalog = deviceCatalog;
39
+ this.statusMapper = statusMapper;
40
+ }
41
+
42
+ public JSArray getBondedDevices(List<String> prefixes) {
43
+ return deviceCatalog.discover(prefixes);
44
+ }
45
+
46
+ public void connect(String deviceId) throws IOException {
47
+ if (deviceId == null || deviceId.trim().isEmpty()) {
48
+ throw new IllegalArgumentException("deviceId is required");
49
+ }
50
+
51
+ PrinterSession nextSession = deviceCatalog.openSession(deviceId);
52
+ if (nextSession == null) {
53
+ throw new IllegalArgumentException("printer device not found: " + deviceId);
54
+ }
55
+
56
+ disconnect();
57
+ nextSession.connect();
58
+ session = nextSession;
59
+ connectedDeviceId = deviceId;
60
+ }
61
+
62
+ public void disconnect() {
63
+ if (session != null) {
64
+ session.disconnect();
65
+ }
66
+ session = null;
67
+ connectedDeviceId = null;
68
+ }
69
+
70
+ public void print(String payload, String language, int copies) throws IOException {
71
+ if (payload == null || payload.isEmpty()) {
72
+ throw new IllegalArgumentException("payload is empty");
73
+ }
74
+ PrinterSession activeSession = requireSession();
75
+ activeSession.print(language, payload.repeat(Math.max(copies, 1)).getBytes(StandardCharsets.UTF_8));
76
+ }
77
+
78
+ public JSObject getConnectionState() {
79
+ JSObject state = new JSObject();
80
+ state.put("state", session == null ? "disconnected" : session.getConnectionState());
81
+ return state;
82
+ }
83
+
84
+ public JSObject getStatus() {
85
+ if (session == null || !session.isConnected()) {
86
+ return statusMapper.disconnected("disconnected");
87
+ }
88
+
89
+ JSObject status = statusMapper.toPluginStatus(true, AndroidStatusMapper.STATE_READY, "ready");
90
+ status.put("deviceId", connectedDeviceId);
91
+ status.put("deviceName", session.getDeviceName());
92
+
93
+ try {
94
+ byte[] response = session.queryStatus();
95
+ if (response != null && response.length > 0) {
96
+ String rawText = new String(response, StandardCharsets.UTF_8).trim();
97
+ if (!rawText.isEmpty()) {
98
+ status.put("raw", rawText);
99
+ status.put("message", rawText);
100
+ applyStatusHints(status, rawText);
101
+ }
102
+ }
103
+ } catch (IOException exception) {
104
+ status.put("message", exception.getMessage());
105
+ }
106
+
107
+ return status;
108
+ }
109
+
110
+ private PrinterSession requireSession() {
111
+ if (session == null || !session.isConnected()) {
112
+ throw new IllegalStateException("printer is not connected");
113
+ }
114
+ return session;
115
+ }
116
+
117
+ private void applyStatusHints(JSObject status, String rawText) {
118
+ String normalized = rawText.toUpperCase();
119
+ if (normalized.contains("PAPER")) {
120
+ status.put("ready", false);
121
+ status.put("paperOut", true);
122
+ }
123
+ if (normalized.contains("COVER") || normalized.contains("OPEN")) {
124
+ status.put("ready", false);
125
+ status.put("coverOpen", true);
126
+ }
127
+ if (normalized.contains("HEAT")) {
128
+ status.put("ready", false);
129
+ status.put("overheating", true);
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,54 @@
1
+ package com.m430.capacitor.labelprinter;
2
+
3
+ import com.getcapacitor.JSObject;
4
+ import org.json.JSONException;
5
+
6
+ public class AndroidStatusMapper {
7
+ public static final int STATE_UNKNOWN = -1;
8
+ public static final int STATE_READY = 0;
9
+ public static final int STATE_PAPER_OUT = 1;
10
+ public static final int STATE_COVER_OPEN = 2;
11
+ public static final int STATE_OVERHEATING = 3;
12
+
13
+ public JSObject toPluginStatus(boolean connected, int stateCode, String message) {
14
+ String messageValue = message == null
15
+ ? "null"
16
+ : "\"" + message.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
17
+ String payload = "{"
18
+ + "\"connected\":" + connected + ","
19
+ + "\"ready\":" + (stateCode == STATE_READY) + ","
20
+ + "\"paperOut\":" + (stateCode == STATE_PAPER_OUT) + ","
21
+ + "\"coverOpen\":" + (stateCode == STATE_COVER_OPEN) + ","
22
+ + "\"overheating\":" + (stateCode == STATE_OVERHEATING) + ","
23
+ + "\"message\":" + messageValue + ","
24
+ + "\"raw\":" + stateCode
25
+ + "}";
26
+
27
+ try {
28
+ return new JSObject(payload);
29
+ } catch (JSONException exception) {
30
+ throw new IllegalStateException("Failed to map printer status", exception);
31
+ }
32
+ }
33
+
34
+ public JSObject disconnected(String message) {
35
+ String messageValue = message == null
36
+ ? "null"
37
+ : "\"" + message.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
38
+ String payload = "{"
39
+ + "\"connected\":false,"
40
+ + "\"ready\":false,"
41
+ + "\"paperOut\":false,"
42
+ + "\"coverOpen\":false,"
43
+ + "\"overheating\":false,"
44
+ + "\"message\":" + messageValue + ","
45
+ + "\"raw\":null"
46
+ + "}";
47
+
48
+ try {
49
+ return new JSObject(payload);
50
+ } catch (JSONException exception) {
51
+ throw new IllegalStateException("Failed to map disconnected printer status", exception);
52
+ }
53
+ }
54
+ }