Commit ca4ab0f779b01bd40535f2a27f1c1493e720fd84

Authored by 杨鑫
2 parents 364df3ef 817a562b

Merge branch 'main' of http://39.98.150.180/wangming/Food-Labeling-Management-Platform

美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue
... ... @@ -163,7 +163,7 @@
163 163 <text class="tips-item">2. On Android: enable Location (required for Bluetooth scan)</text>
164 164 <text class="tips-item">3. Place the printer within 10 m and tap Scan again</text>
165 165 <text class="tips-item">4. Devices with no name show as "Unknown Device"—you can still connect</text>
166   - <text class="tips-item">5. D320FAX (d320fax_295c): use Bluetooth mode, tap Scan—shows paired + nearby devices, no filtering</text>
  166 + <text class="tips-item">5. GP-D320FX (d320fx_xxxx): use Bluetooth mode, tap Scan—shows paired + nearby devices, no filtering</text>
167 167 <text class="tips-item tips-item-last">6. Restart the printer or app if not visible</text>
168 168 </view>
169 169 </view>
... ... @@ -260,8 +260,21 @@ async function refreshNativeDebug () {
260 260 function hasPreferredClassicDeviceInList () {
261 261 return devices.value.some((item: any) => {
262 262 const name = String(item?.name || '').toLowerCase()
263   - const type = String(item?.type || '').toLowerCase()
264   - return name.includes('virtual bt printer') || type === 'classic' || type === 'dual'
  263 + const driverKey = String(item?.driverKey || '').toLowerCase()
  264 + return name.includes('virtual bt printer') || driverKey === 'd320fax'
  265 + })
  266 +}
  267 +
  268 +function upsertDevice (device: any) {
  269 + const described = describeDiscoveredPrinter(device)
  270 + const existing = devices.value.find(item => item.deviceId === described.deviceId)
  271 + if (!existing) {
  272 + discoveredIds.add(described.deviceId)
  273 + devices.value.push(described)
  274 + return
  275 + }
  276 + Object.assign(existing, described, {
  277 + RSSI: described.RSSI != null ? described.RSSI : existing.RSSI,
265 278 })
266 279 }
267 280  
... ... @@ -343,16 +356,13 @@ const stopDiscovery = () =&gt; {
343 356 const onDeviceFound = (res: any) => {
344 357 const foundDevices: any[] = res.devices || []
345 358 for (const d of foundDevices) {
346   - if (discoveredIds.has(d.deviceId)) continue
347 359 const name = (d.localName || d.name || '').trim()
348   - const displayName = name || 'Unknown Device'
349   - discoveredIds.add(d.deviceId)
350   - devices.value.push(describeDiscoveredPrinter({
  360 + upsertDevice({
351 361 deviceId: d.deviceId,
352   - name: displayName,
  362 + name: name || 'Unknown Device',
353 363 RSSI: d.RSSI,
354 364 type: 'ble',
355   - }))
  365 + })
356 366 }
357 367 devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
358 368 }
... ... @@ -363,15 +373,13 @@ function mergeCachedBleDevices () {
363 373 success: (res: any) => {
364 374 const list = res.devices || []
365 375 for (const d of list) {
366   - if (discoveredIds.has(d.deviceId)) continue
367 376 const name = (d.localName || d.name || '').trim()
368   - discoveredIds.add(d.deviceId)
369   - devices.value.push(describeDiscoveredPrinter({
  377 + upsertDevice({
370 378 deviceId: d.deviceId,
371 379 name: name || 'Unknown Device',
372 380 RSSI: d.RSSI,
373 381 type: 'ble',
374   - }))
  382 + })
375 383 }
376 384 if (list.length > 0) devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
377 385 },
... ... @@ -387,13 +395,11 @@ function addPairedDevices () {
387 395 debugInfo.value.pairedCount = (paired || []).length
388 396 debugInfo.value.foundVirtualPrinter = (paired || []).some((item: any) => String(item?.name || '').toLowerCase().includes('virtual bt printer'))
389 397 for (const p of paired) {
390   - if (discoveredIds.has(p.deviceId)) continue
391   - discoveredIds.add(p.deviceId)
392   - devices.value.push(describeDiscoveredPrinter({
  398 + upsertDevice({
393 399 deviceId: p.deviceId,
394 400 name: p.name || 'Unknown Device',
395 401 type: p.type || 'classic',
396   - }))
  402 + })
397 403 }
398 404 if (paired.length > 0) {
399 405 debugInfo.value.lastClassicEvent = 'paired devices loaded'
... ... @@ -416,13 +422,11 @@ function startClassicScan () {
416 422 classic.startClassicDiscovery(
417 423 (dev: { name: string; deviceId: string; type: string }) => {
418 424 debugInfo.value.lastClassicEvent = 'device found'
419   - if (discoveredIds.has(dev.deviceId)) return
420   - discoveredIds.add(dev.deviceId)
421   - devices.value.push(describeDiscoveredPrinter({
  425 + upsertDevice({
422 426 deviceId: dev.deviceId,
423 427 name: dev.name || 'Unknown Device',
424 428 type: (dev.type as BtDevice['type']) || 'classic',
425   - }))
  429 + })
426 430 devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100))
427 431 },
428 432 () => {
... ... @@ -542,7 +546,7 @@ const handleTestPrint = async () =&gt; {
542 546 if (msg === 'BUILTIN_PLUGIN_NOT_FOUND') {
543 547 uni.showModal({
544 548 title: 'Use Bluetooth Mode',
545   - content: 'For GP-D320FAX, switch to Bluetooth mode and tap Scan to connect (e.g. d320fax_295c or Virtual BT Printer). Built-in mode needs custom app packaging.',
  549 + content: 'For GP-D320FX, switch to Bluetooth mode and tap Scan to connect (for example d320fx_xxxx). Built-in mode needs custom app packaging.',
546 550 showCancel: false,
547 551 success: () => { switchType('bluetooth') },
548 552 })
... ...
美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue
... ... @@ -218,8 +218,8 @@ const normalizeDeviceName = (device: any) =&gt; {
218 218 const hasPreferredClassicDevice = () => {
219 219 return pairedDevices.value.some((item: any) => {
220 220 const name = String(item?.name || '').toLowerCase()
221   - const type = String(item?.type || '').toLowerCase()
222   - return name.includes('virtual bt printer') || type === 'classic' || type === 'dual'
  221 + const driverKey = String(item?.driverKey || '').toLowerCase()
  222 + return name.includes('virtual bt printer') || driverKey === 'd320fax'
223 223 })
224 224 }
225 225  
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/drivers/d320fax.ts
... ... @@ -3,7 +3,6 @@ import type { PrinterCandidate, PrinterDriver } from &#39;../types/printer&#39;
3 3  
4 4 const KEYWORDS = [
5 5 'd320fax',
6   - 'd320fx',
7 6 'virtual bt printer',
8 7 'gp-d320fax',
9 8 ]
... ... @@ -20,7 +19,7 @@ function score (device: PrinterCandidate): number {
20 19 export const d320faxDriver: PrinterDriver = {
21 20 key: 'd320fax',
22 21 brand: 'Gprinter',
23   - model: 'D320FAX/D320FX',
  22 + model: 'D320FAX',
24 23 displayName: 'Gprinter D320FAX',
25 24 protocol: 'tsc',
26 25 preferredConnection: 'classic',
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/drivers/gpD320fx.ts 0 → 100644
  1 +import { buildTscLabelData, buildTscTestPrintData } from '../protocols/tscProtocol'
  2 +import type { PrinterCandidate, PrinterDriver } from '../types/printer'
  3 +
  4 +const KEYWORDS = [
  5 + 'd320fx',
  6 + 'gp-d320fx',
  7 +]
  8 +
  9 +function score (device: PrinterCandidate): number {
  10 + const text = `${device.name || ''} ${device.deviceId || ''}`.toLowerCase()
  11 + let total = 0
  12 + KEYWORDS.forEach((keyword) => {
  13 + if (text.includes(keyword)) total += 80
  14 + })
  15 + if (text.includes('gprinter')) total += 10
  16 + return total
  17 +}
  18 +
  19 +export const gpD320fxDriver: PrinterDriver = {
  20 + key: 'gp-d320fx',
  21 + brand: 'Gprinter',
  22 + model: 'GP-D320FX',
  23 + displayName: 'Gprinter GP-D320FX',
  24 + protocol: 'tsc',
  25 + preferredConnection: 'ble',
  26 + preferredBleMtu: 512,
  27 + imageMaxWidthDots: 800,
  28 + imageDpi: 203,
  29 + keywords: KEYWORDS,
  30 + matches (device) {
  31 + return score(device)
  32 + },
  33 + resolveConnectionType (device) {
  34 + return device.type === 'classic' ? 'classic' : 'ble'
  35 + },
  36 + buildTestPrintData () {
  37 + return buildTscTestPrintData()
  38 + },
  39 + buildLabelData (payload) {
  40 + return buildTscLabelData(payload)
  41 + },
  42 +}
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/manager/driverRegistry.ts
1 1 import { d320faxDriver } from '../drivers/d320fax'
2 2 import { genericTscDriver } from '../drivers/genericTsc'
  3 +import { gpD320fxDriver } from '../drivers/gpD320fx'
3 4 import { gpR3Driver } from '../drivers/gpR3'
4 5 import type { PrinterCandidate, PrinterDriver, ResolvedPrinterCandidate } from '../types/printer'
5 6  
6 7 const printerDrivers: PrinterDriver[] = [
7 8 gpR3Driver,
  9 + gpD320fxDriver,
8 10 d320faxDriver,
9 11 genericTscDriver,
10 12 ]
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts
... ... @@ -41,7 +41,38 @@ function getPrinterTypeDisplayName (type: &#39;&#39; | &#39;bluetooth&#39; | &#39;builtin&#39;): string
41 41 function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDriver): Promise<void> {
42 42 return new Promise((resolve, reject) => {
43 43 // #ifdef APP-PLUS
44   - if (isNativeFastPrinterAvailable()) {
  44 + const shouldUseGenericClassicOnly = driver.key === 'gp-d320fx'
  45 +
  46 + const connectClassicSocketFallback = () => {
  47 + try {
  48 + if (!classicBluetooth || typeof classicBluetooth.connDevice !== 'function') {
  49 + reject(new Error('Classic Bluetooth fallback is not available.'))
  50 + return
  51 + }
  52 + classicBluetooth.connDevice(device.deviceId, (ok: boolean) => {
  53 + if (ok) {
  54 + setBluetoothConnection({
  55 + deviceId: device.deviceId,
  56 + deviceName: device.name || 'Bluetooth Printer',
  57 + deviceType: 'classic',
  58 + transportMode: 'generic',
  59 + driverKey: driver.key,
  60 + mtu: driver.preferredBleMtu || 20,
  61 + })
  62 + resolve()
  63 + return
  64 + }
  65 + const message = typeof classicBluetooth.getLastError === 'function'
  66 + ? classicBluetooth.getLastError()
  67 + : ''
  68 + reject(new Error(message || 'Classic Bluetooth connection failed.'))
  69 + })
  70 + } catch (error: any) {
  71 + reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.')))
  72 + }
  73 + }
  74 +
  75 + if (!shouldUseGenericClassicOnly && isNativeFastPrinterAvailable()) {
45 76 connectNativeFastPrinterPlugin({
46 77 deviceId: device.deviceId,
47 78 deviceName: device.name || 'Bluetooth Printer',
... ... @@ -50,15 +81,24 @@ function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDrive
50 81 deviceId: device.deviceId,
51 82 deviceName: device.name || 'Bluetooth Printer',
52 83 deviceType: 'classic',
  84 + transportMode: 'native-plugin',
53 85 driverKey: driver.key,
54 86 mtu: driver.preferredBleMtu || 20,
55 87 })
56 88 resolve()
57 89 }).catch((error: any) => {
  90 + if (driver.key === 'd320fax') {
  91 + connectClassicSocketFallback()
  92 + return
  93 + }
58 94 reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.')))
59 95 })
60 96 return
61 97 }
  98 + if (driver.key === 'd320fax' || shouldUseGenericClassicOnly) {
  99 + connectClassicSocketFallback()
  100 + return
  101 + }
62 102 reject(new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.'))
63 103 // #endif
64 104 // #ifndef APP-PLUS
... ... @@ -103,19 +143,44 @@ function findBleWriteCharacteristic (deviceId: string): Promise&lt;{ serviceId: str
103 143 })
104 144 }
105 145  
  146 +function requestBleMtu (deviceId: string, preferredMtu: number): Promise<number> {
  147 + return new Promise((resolve) => {
  148 + const targetMtu = Math.max(20, Math.min(512, Math.round(preferredMtu || 20)))
  149 + if (targetMtu <= 20 || typeof (uni as any).setBLEMTU !== 'function') {
  150 + resolve(20)
  151 + return
  152 + }
  153 + let settled = false
  154 + const done = (value: number) => {
  155 + if (settled) return
  156 + settled = true
  157 + clearTimeout(timer)
  158 + resolve(Math.max(20, Math.round(value || 20)))
  159 + }
  160 + const timer = setTimeout(() => done(20), 3000)
  161 + ;(uni as any).setBLEMTU({
  162 + deviceId,
  163 + mtu: targetMtu,
  164 + success: (res: any) => done(Number(res?.mtu) || targetMtu),
  165 + fail: () => done(20),
  166 + })
  167 + })
  168 +}
  169 +
106 170 function connectBlePrinter (device: PrinterCandidate, driver: PrinterDriver): Promise<void> {
107 171 const finalizeExistingBleConnection = async () => {
108 172 const write = await findBleWriteCharacteristic(device.deviceId)
109 173 if (!write) {
110 174 throw new Error('No writable characteristic found. This device may not support printing.')
111 175 }
  176 + const negotiatedMtu = await requestBleMtu(device.deviceId, driver.preferredBleMtu || 20)
112 177 setBluetoothConnection({
113 178 deviceId: device.deviceId,
114 179 deviceName: device.name || 'Bluetooth Printer',
115 180 serviceId: write.serviceId,
116 181 characteristicId: write.characteristicId,
117 182 deviceType: 'ble',
118   - mtu: driver.preferredBleMtu || 20,
  183 + mtu: negotiatedMtu,
119 184 driverKey: driver.key,
120 185 })
121 186 }
... ... @@ -145,6 +210,14 @@ function connectBlePrinter (device: PrinterCandidate, driver: PrinterDriver): Pr
145 210  
146 211 export async function connectBluetoothPrinter (device: PrinterCandidate): Promise<PrinterDriver> {
147 212 const driver = resolvePrinterDriver(device)
  213 + if (driver.key === 'gp-d320fx') {
  214 + try {
  215 + await connectBlePrinter(device, driver)
  216 + } catch (_) {
  217 + await connectClassicBluetooth(device, driver)
  218 + }
  219 + return driver
  220 + }
148 221 const resolvedType = driver.resolveConnectionType(device)
149 222 if (resolvedType === 'classic') {
150 223 await connectClassicBluetooth(device, driver)
... ... @@ -218,19 +291,25 @@ function canUseNativeFastTemplatePrint (driver: PrinterDriver): boolean {
218 291 const connection = getBluetoothConnection()
219 292 return driver.protocol === 'tsc'
220 293 && connection?.deviceType === 'classic'
  294 + && connection?.transportMode === 'native-plugin'
221 295 && isNativeFastPrinterAvailable()
222 296 }
223 297  
224 298 function getNativeClassicConnection () {
225 299 const connection = getBluetoothConnection()
226   - if (!connection || connection.deviceType !== 'classic') return null
  300 + if (!connection || connection.deviceType !== 'classic' || connection.transportMode !== 'native-plugin') return null
227 301 return connection
228 302 }
229 303  
230 304 export async function testPrintCurrentPrinter (onProgress?: (percent: number) => void): Promise<PrinterDriver> {
231 305 const driver = getCurrentPrinterDriver()
232 306 const connection = getBluetoothConnection()
233   - if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) {
  307 + if (
  308 + driver.protocol === 'tsc'
  309 + && connection?.deviceType === 'classic'
  310 + && connection?.transportMode === 'native-plugin'
  311 + && !isNativeFastPrinterAvailable()
  312 + ) {
234 313 throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')
235 314 }
236 315 if (canUseNativeFastTemplatePrint(driver)) {
... ... @@ -319,7 +398,12 @@ export async function printSystemTemplateForCurrentPrinter (
319 398 ): Promise<PrinterDriver> {
320 399 const driver = getCurrentPrinterDriver()
321 400 const connection = getBluetoothConnection()
322   - if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) {
  401 + if (
  402 + driver.protocol === 'tsc'
  403 + && connection?.deviceType === 'classic'
  404 + && connection?.transportMode === 'native-plugin'
  405 + && !isNativeFastPrinterAvailable()
  406 + ) {
323 407 throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')
324 408 }
325 409 if (canUseNativeFastTemplatePrint(driver)) {
... ... @@ -341,6 +425,7 @@ export async function printSystemTemplateForCurrentPrinter (
341 425 const structuredTemplate = adaptSystemLabelTemplate(template, data, {
342 426 dpi: driver.imageDpi || 203,
343 427 printQty: options.printQty || 1,
  428 + disableBitmapText: driver.key === 'gp-d320fx',
344 429 })
345 430 const bytes = driver.protocol === 'esc'
346 431 ? buildEscPosTemplateData(structuredTemplate)
... ... @@ -360,7 +445,7 @@ export function disconnectCurrentPrinter (): Promise&lt;void&gt; {
360 445  
361 446 if (type === 'bluetooth' && connection?.deviceType === 'classic') {
362 447 // #ifdef APP-PLUS
363   - if (isNativeFastPrinterAvailable()) {
  448 + if (connection.transportMode === 'native-plugin' && isNativeFastPrinterAvailable()) {
364 449 disconnectNativeFastPrinterPlugin().catch((e: any) => {
365 450 console.error('Disconnect native fast printer failed', e)
366 451 }).finally(() => {
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts
... ... @@ -10,6 +10,14 @@ const DESIGN_DPI = 96
10 10 const DEFAULT_THRESHOLD = 180
11 11 const TEXT_PADDING_DOTS = 6
12 12  
  13 +function normalizePrinterLikeText (str: string): string {
  14 + return String(str || '')
  15 + .normalize('NFKC')
  16 + .replace(/[\u2018\u2019]/g, '\'')
  17 + .replace(/[\u201C\u201D]/g, '"')
  18 + .replace(/[\u2013\u2014]/g, '-')
  19 +}
  20 +
13 21 type BitmapPatchItem = {
14 22 type: 'bitmap'
15 23 x: number
... ... @@ -144,10 +152,11 @@ function splitTextLines (text: string, paint: any, maxWidth: number): string[] {
144 152  
145 153 export function shouldRasterizeTextElement (text: string, type: string): boolean {
146 154 const normalizedType = String(type || '').toUpperCase()
147   - if (!text) return false
148   - if (normalizedType === 'TEXT_PRICE') return true
149   - if (/[€£¥¥$éÉáàâäãåæçèêëìíîïñòóôöõøùúûüýÿœšž]/.test(text)) return true
150   - return /[^\x20-\x7E]/.test(text)
  155 + const normalizedText = normalizePrinterLikeText(text)
  156 + if (!normalizedText) return false
  157 + if (normalizedType === 'TEXT_PRICE' && /[€£¥¥]/.test(normalizedText)) return true
  158 + if (/[€£¥¥éÉáàâäãåæçèêëìíîïñòóôöõøùúûüýÿœšž]/.test(normalizedText)) return true
  159 + return /[^\x20-\x7E]/.test(normalizedText)
151 160 }
152 161  
153 162 export function createTextBitmapPatch (params: {
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts
... ... @@ -11,6 +11,7 @@ const STORAGE_BT_DEVICE_NAME = &#39;btDeviceName&#39;
11 11 const STORAGE_BT_SERVICE_ID = 'btServiceId'
12 12 const STORAGE_BT_CHARACTERISTIC_ID = 'btCharacteristicId'
13 13 const STORAGE_BT_DEVICE_TYPE = 'btDeviceType' // 'ble' | 'classic'
  14 +const STORAGE_BT_TRANSPORT_MODE = 'btTransportMode' // 'native-plugin' | 'generic'
14 15 const STORAGE_BLE_MTU = 'bleMTU'
15 16 const STORAGE_BUILTIN_PORT = 'builtinPort'
16 17 const STORAGE_PRINTER_DRIVER_KEY = 'printerDriverKey'
... ... @@ -29,6 +30,7 @@ export const PrinterStorageKeys = {
29 30 btServiceId: STORAGE_BT_SERVICE_ID,
30 31 btCharacteristicId: STORAGE_BT_CHARACTERISTIC_ID,
31 32 btDeviceType: STORAGE_BT_DEVICE_TYPE,
  33 + btTransportMode: STORAGE_BT_TRANSPORT_MODE,
32 34 bleMTU: STORAGE_BLE_MTU,
33 35 driverKey: STORAGE_PRINTER_DRIVER_KEY,
34 36 } as const
... ... @@ -43,6 +45,7 @@ export function setBluetoothConnection (info: {
43 45 serviceId?: string
44 46 characteristicId?: string
45 47 deviceType?: BtDeviceType
  48 + transportMode?: 'native-plugin' | 'generic'
46 49 mtu?: number
47 50 driverKey?: string
48 51 }) {
... ... @@ -52,6 +55,10 @@ export function setBluetoothConnection (info: {
52 55 uni.setStorageSync(STORAGE_BT_SERVICE_ID, info.serviceId || '')
53 56 uni.setStorageSync(STORAGE_BT_CHARACTERISTIC_ID, info.characteristicId || '')
54 57 uni.setStorageSync(STORAGE_BT_DEVICE_TYPE, info.deviceType || 'ble')
  58 + uni.setStorageSync(
  59 + STORAGE_BT_TRANSPORT_MODE,
  60 + info.transportMode || (info.deviceType === 'classic' ? 'native-plugin' : 'generic')
  61 + )
55 62 uni.setStorageSync(STORAGE_BLE_MTU, info.mtu != null ? info.mtu : BLE_MTU_DEFAULT)
56 63 uni.setStorageSync(STORAGE_PRINTER_DRIVER_KEY, info.driverKey || '')
57 64 }
... ... @@ -68,6 +75,7 @@ export function clearPrinter () {
68 75 uni.removeStorageSync(STORAGE_BT_SERVICE_ID)
69 76 uni.removeStorageSync(STORAGE_BT_CHARACTERISTIC_ID)
70 77 uni.removeStorageSync(STORAGE_BT_DEVICE_TYPE)
  78 + uni.removeStorageSync(STORAGE_BT_TRANSPORT_MODE)
71 79 uni.removeStorageSync(STORAGE_BLE_MTU)
72 80 uni.removeStorageSync(STORAGE_BUILTIN_PORT)
73 81 uni.removeStorageSync(STORAGE_PRINTER_DRIVER_KEY)
... ... @@ -93,10 +101,12 @@ export function getBluetoothConnection (): {
93 101 serviceId: string
94 102 characteristicId: string
95 103 deviceType: BtDeviceType
  104 + transportMode: 'native-plugin' | 'generic'
96 105 mtu: number
97 106 } | null {
98 107 const deviceId = uni.getStorageSync(STORAGE_BT_DEVICE_ID)
99 108 const deviceType = (uni.getStorageSync(STORAGE_BT_DEVICE_TYPE) as BtDeviceType) || 'ble'
  109 + const transportMode = (uni.getStorageSync(STORAGE_BT_TRANSPORT_MODE) as 'native-plugin' | 'generic') || 'generic'
100 110 if (!deviceId) return null
101 111 if (deviceType === 'classic') {
102 112 return {
... ... @@ -105,6 +115,7 @@ export function getBluetoothConnection (): {
105 115 serviceId: '',
106 116 characteristicId: '',
107 117 deviceType: 'classic',
  118 + transportMode,
108 119 mtu: BLE_MTU_DEFAULT,
109 120 }
110 121 }
... ... @@ -117,6 +128,7 @@ export function getBluetoothConnection (): {
117 128 serviceId,
118 129 characteristicId,
119 130 deviceType: 'ble',
  131 + transportMode,
120 132 mtu: Number(uni.getStorageSync(STORAGE_BLE_MTU)) || BLE_MTU_DEFAULT,
121 133 }
122 134 }
... ... @@ -209,15 +221,33 @@ function sendViaBle (
209 221 return Promise.reject(new Error('Bluetooth printer not connected.'))
210 222 }
211 223 const { deviceId, serviceId, characteristicId, mtu } = conn
  224 + const payloadSize = Math.max(20, Math.round((mtu || BLE_MTU_DEFAULT) > 23 ? (mtu || BLE_MTU_DEFAULT) - 3 : (mtu || BLE_MTU_DEFAULT)))
212 225 const chunks: number[][] = []
213   - for (let i = 0; i < data.length; i += mtu) {
214   - chunks.push(data.slice(i, i + mtu))
  226 + for (let i = 0; i < data.length; i += payloadSize) {
  227 + chunks.push(data.slice(i, i + payloadSize))
215 228 }
216 229 const total = chunks.length
217 230 let sent = 0
  231 + let completed = false
  232 + let timeoutId: ReturnType<typeof setTimeout> | null = setTimeout(() => {}, 0)
  233 + const writeDelayMs = payloadSize >= 180 ? 0 : (payloadSize > 20 ? 1 : 8)
  234 +
  235 + const resetTimeout = (reject: (reason?: any) => void) => {
  236 + if (timeoutId) clearTimeout(timeoutId)
  237 + timeoutId = setTimeout(() => {
  238 + if (completed) return
  239 + completed = true
  240 + reject(new Error('BLE write timeout'))
  241 + }, 15000)
  242 + }
218 243  
219 244 function sendNext (): Promise<void> {
  245 + if (completed) {
  246 + return Promise.reject(new Error('BLE write timeout'))
  247 + }
220 248 if (sent >= total) {
  249 + completed = true
  250 + if (timeoutId) clearTimeout(timeoutId)
221 251 if (onProgress) onProgress(100)
222 252 return Promise.resolve()
223 253 }
... ... @@ -228,17 +258,28 @@ function sendViaBle (
228 258 view.setUint8(j, chunk[j] & 0xff)
229 259 }
230 260 return new Promise((resolve, reject) => {
  261 + resetTimeout(reject)
231 262 uni.writeBLECharacteristicValue({
232 263 deviceId,
233 264 serviceId,
234 265 characteristicId,
235 266 value: buffer,
236 267 success: () => {
  268 + if (completed) return
237 269 sent++
238 270 if (onProgress) onProgress(Math.round((sent / total) * 100))
239   - setTimeout(() => sendNext().then(resolve).catch(reject), 10)
  271 + if (writeDelayMs <= 0) {
  272 + sendNext().then(resolve).catch(reject)
  273 + return
  274 + }
  275 + setTimeout(() => sendNext().then(resolve).catch(reject), writeDelayMs)
  276 + },
  277 + fail: (err: any) => {
  278 + if (completed) return
  279 + completed = true
  280 + if (timeoutId) clearTimeout(timeoutId)
  281 + reject(new Error(err.errMsg || 'BLE write failed'))
240 282 },
241   - fail: (err: any) => reject(new Error(err.errMsg || 'BLE write failed')),
242 283 })
243 284 })
244 285 }
... ... @@ -256,9 +297,22 @@ function sendViaClassic (
256 297 return Promise.reject(new Error('Classic Bluetooth printer not connected.'))
257 298 }
258 299 return new Promise((resolve, reject) => {
  300 + let settled = false
  301 + const finish = (fn: () => void) => {
  302 + if (settled) return
  303 + settled = true
  304 + clearTimeout(timeoutId)
  305 + fn()
  306 + }
  307 + const timeoutId = setTimeout(() => {
  308 + finish(() => {
  309 + reject(buildClassicBluetoothError('Classic Bluetooth send timeout', conn.deviceId))
  310 + })
  311 + }, 15000)
  312 +
259 313 try {
260 314 if (!classicBluetooth) {
261   - reject(new Error('Classic Bluetooth not available'))
  315 + finish(() => reject(new Error('Classic Bluetooth not available')))
262 316 return
263 317 }
264 318 const debugState = typeof classicBluetooth.getDebugState === 'function'
... ... @@ -272,7 +326,7 @@ function sendViaClassic (
272 326 const errorMessage = typeof classicBluetooth.getLastError === 'function'
273 327 ? classicBluetooth.getLastError()
274 328 : ''
275   - reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth connection is not ready', conn.deviceId))
  329 + finish(() => reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth connection is not ready', conn.deviceId)))
276 330 return
277 331 }
278 332  
... ... @@ -283,31 +337,35 @@ function sendViaClassic (
283 337  
284 338 if (typeof classicBluetooth.sendByteDataAsync === 'function') {
285 339 classicBluetooth.sendByteDataAsync(sendData, (ok: boolean, errorMessage?: string) => {
286   - if (onProgress) onProgress(100)
287   - if (ok) {
288   - resolve()
289   - return
290   - }
291   - reject(buildClassicBluetoothError(
292   - errorMessage || classicBluetooth.getLastError?.() || 'Classic Bluetooth send failed',
293   - conn.deviceId
294   - ))
  340 + finish(() => {
  341 + if (onProgress) onProgress(100)
  342 + if (ok) {
  343 + resolve()
  344 + return
  345 + }
  346 + reject(buildClassicBluetoothError(
  347 + errorMessage || classicBluetooth.getLastError?.() || 'Classic Bluetooth send failed',
  348 + conn.deviceId
  349 + ))
  350 + })
295 351 })
296 352 return
297 353 }
298 354  
299 355 const ok = classicBluetooth.sendByteData(sendData)
300   - if (onProgress) onProgress(100)
301   - if (ok) {
302   - resolve()
303   - return
304   - }
305   - const errorMessage = typeof classicBluetooth.getLastError === 'function'
306   - ? classicBluetooth.getLastError()
307   - : ''
308   - reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth send failed', conn.deviceId))
  356 + finish(() => {
  357 + if (onProgress) onProgress(100)
  358 + if (ok) {
  359 + resolve()
  360 + return
  361 + }
  362 + const errorMessage = typeof classicBluetooth.getLastError === 'function'
  363 + ? classicBluetooth.getLastError()
  364 + : ''
  365 + reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth send failed', conn.deviceId))
  366 + })
309 367 } catch (e: any) {
310   - reject(buildClassicBluetoothError(e?.message || String(e || 'Classic Bluetooth send exception'), conn.deviceId))
  368 + finish(() => reject(buildClassicBluetoothError(e?.message || String(e || 'Classic Bluetooth send exception'), conn.deviceId)))
311 369 }
312 370 })
313 371 // #endif
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts
... ... @@ -297,7 +297,10 @@ function buildTscTemplate (
297 297 template: SystemLabelTemplate,
298 298 data: LabelTemplateData,
299 299 dpi: number,
300   - printQty: number
  300 + printQty: number,
  301 + options: {
  302 + disableBitmapText?: boolean
  303 + } = {}
301 304 ): StructuredTscTemplate {
302 305 const widthMm = roundNumber(toMillimeter(template.width, template.unit || 'inch'))
303 306 const heightMm = roundNumber(toMillimeter(template.height, template.unit || 'inch'))
... ... @@ -324,7 +327,7 @@ function buildTscTemplate (
324 327 const scale = resolveTextScale(getConfigNumber(config, ['fontSize'], 14), dpi)
325 328 const align = resolveElementAlign(element, pageWidth)
326 329  
327   - if (shouldRasterizeTextElement(text, type)) {
  330 + if (!options.disableBitmapText && shouldRasterizeTextElement(text, type)) {
328 331 const bitmapPatch = createTextBitmapPatch({
329 332 element,
330 333 text,
... ... @@ -510,13 +513,16 @@ export function adaptSystemLabelTemplate (
510 513 options: {
511 514 dpi?: number
512 515 printQty?: number
  516 + disableBitmapText?: boolean
513 517 } = {}
514 518 ): StructuredLabelTemplate {
515 519 const dpi = options.dpi || 203
516 520 const printQty = Math.max(1, Math.round(options.printQty || 1))
517 521 return {
518 522 key: template.id || template.name || 'system-label-template',
519   - tsc: buildTscTemplate(template, data, dpi, printQty),
  523 + tsc: buildTscTemplate(template, data, dpi, printQty, {
  524 + disableBitmapText: options.disableBitmapText,
  525 + }),
520 526 esc: buildEscTemplate(template, data, printQty),
521 527 }
522 528 }
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts
... ... @@ -220,7 +220,7 @@ export function buildTscTemplateLabel (template: StructuredTscTemplate): number[
220 220 if (item.type === 'bitmap') {
221 221 const bytesPerRow = item.image.width / 8
222 222 const bitmapBytes = pixelsToTscBitmapBytes(item.image)
223   - add(`BITMAP ${item.x},${item.y},${bytesPerRow},${item.image.height},0,`)
  223 + addCommandBytes(out, `BITMAP ${item.x},${item.y},${bytesPerRow},${item.image.height},0,`)
224 224 for (let i = 0; i < bitmapBytes.length; i++) out.push(bitmapBytes[i])
225 225 out.push(0x0d, 0x0a)
226 226 return
... ... @@ -275,7 +275,7 @@ export function buildTscImageLabel (
275 275 add('DENSITY 14')
276 276 add('SPEED 5')
277 277 add('CLS')
278   - add(`BITMAP ${x},${y},${bytesPerRow},${image.height},0,`)
  278 + addCommandBytes(out, `BITMAP ${x},${y},${bytesPerRow},${image.height},0,`)
279 279 for (let i = 0; i < bitmapBytes.length; i++) out.push(bitmapBytes[i])
280 280 out.push(0x0d, 0x0a)
281 281 add(`PRINT 1,${printQty}`)
... ...