diff --git a/美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue b/美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue index aea8f5d..4928b79 100644 --- a/美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue +++ b/美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue @@ -163,7 +163,7 @@ 2. On Android: enable Location (required for Bluetooth scan) 3. Place the printer within 10 m and tap Scan again 4. Devices with no name show as "Unknown Device"—you can still connect - 5. D320FAX (d320fax_295c): use Bluetooth mode, tap Scan—shows paired + nearby devices, no filtering + 5. GP-D320FX (d320fx_xxxx): use Bluetooth mode, tap Scan—shows paired + nearby devices, no filtering 6. Restart the printer or app if not visible @@ -260,8 +260,21 @@ async function refreshNativeDebug () { function hasPreferredClassicDeviceInList () { return devices.value.some((item: any) => { const name = String(item?.name || '').toLowerCase() - const type = String(item?.type || '').toLowerCase() - return name.includes('virtual bt printer') || type === 'classic' || type === 'dual' + const driverKey = String(item?.driverKey || '').toLowerCase() + return name.includes('virtual bt printer') || driverKey === 'd320fax' + }) +} + +function upsertDevice (device: any) { + const described = describeDiscoveredPrinter(device) + const existing = devices.value.find(item => item.deviceId === described.deviceId) + if (!existing) { + discoveredIds.add(described.deviceId) + devices.value.push(described) + return + } + Object.assign(existing, described, { + RSSI: described.RSSI != null ? described.RSSI : existing.RSSI, }) } @@ -343,16 +356,13 @@ const stopDiscovery = () => { const onDeviceFound = (res: any) => { const foundDevices: any[] = res.devices || [] for (const d of foundDevices) { - if (discoveredIds.has(d.deviceId)) continue const name = (d.localName || d.name || '').trim() - const displayName = name || 'Unknown Device' - discoveredIds.add(d.deviceId) - devices.value.push(describeDiscoveredPrinter({ + upsertDevice({ deviceId: d.deviceId, - name: displayName, + name: name || 'Unknown Device', RSSI: d.RSSI, type: 'ble', - })) + }) } devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100)) } @@ -363,15 +373,13 @@ function mergeCachedBleDevices () { success: (res: any) => { const list = res.devices || [] for (const d of list) { - if (discoveredIds.has(d.deviceId)) continue const name = (d.localName || d.name || '').trim() - discoveredIds.add(d.deviceId) - devices.value.push(describeDiscoveredPrinter({ + upsertDevice({ deviceId: d.deviceId, name: name || 'Unknown Device', RSSI: d.RSSI, type: 'ble', - })) + }) } if (list.length > 0) devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100)) }, @@ -387,13 +395,11 @@ function addPairedDevices () { debugInfo.value.pairedCount = (paired || []).length debugInfo.value.foundVirtualPrinter = (paired || []).some((item: any) => String(item?.name || '').toLowerCase().includes('virtual bt printer')) for (const p of paired) { - if (discoveredIds.has(p.deviceId)) continue - discoveredIds.add(p.deviceId) - devices.value.push(describeDiscoveredPrinter({ + upsertDevice({ deviceId: p.deviceId, name: p.name || 'Unknown Device', type: p.type || 'classic', - })) + }) } if (paired.length > 0) { debugInfo.value.lastClassicEvent = 'paired devices loaded' @@ -416,13 +422,11 @@ function startClassicScan () { classic.startClassicDiscovery( (dev: { name: string; deviceId: string; type: string }) => { debugInfo.value.lastClassicEvent = 'device found' - if (discoveredIds.has(dev.deviceId)) return - discoveredIds.add(dev.deviceId) - devices.value.push(describeDiscoveredPrinter({ + upsertDevice({ deviceId: dev.deviceId, name: dev.name || 'Unknown Device', type: (dev.type as BtDevice['type']) || 'classic', - })) + }) devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100)) }, () => { @@ -542,7 +546,7 @@ const handleTestPrint = async () => { if (msg === 'BUILTIN_PLUGIN_NOT_FOUND') { uni.showModal({ title: 'Use Bluetooth Mode', - 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.', + content: 'For GP-D320FX, switch to Bluetooth mode and tap Scan to connect (for example d320fx_xxxx). Built-in mode needs custom app packaging.', showCancel: false, success: () => { switchType('bluetooth') }, }) diff --git a/美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue b/美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue index d83ea4a..d99d5e5 100644 --- a/美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue +++ b/美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue @@ -218,8 +218,8 @@ const normalizeDeviceName = (device: any) => { const hasPreferredClassicDevice = () => { return pairedDevices.value.some((item: any) => { const name = String(item?.name || '').toLowerCase() - const type = String(item?.type || '').toLowerCase() - return name.includes('virtual bt printer') || type === 'classic' || type === 'dual' + const driverKey = String(item?.driverKey || '').toLowerCase() + return name.includes('virtual bt printer') || driverKey === 'd320fax' }) } diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/d320fax.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/d320fax.ts index 793fd4c..207fd2b 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/d320fax.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/d320fax.ts @@ -3,7 +3,6 @@ import type { PrinterCandidate, PrinterDriver } from '../types/printer' const KEYWORDS = [ 'd320fax', - 'd320fx', 'virtual bt printer', 'gp-d320fax', ] @@ -20,7 +19,7 @@ function score (device: PrinterCandidate): number { export const d320faxDriver: PrinterDriver = { key: 'd320fax', brand: 'Gprinter', - model: 'D320FAX/D320FX', + model: 'D320FAX', displayName: 'Gprinter D320FAX', protocol: 'tsc', preferredConnection: 'classic', diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/gpD320fx.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/gpD320fx.ts new file mode 100644 index 0000000..bd3b311 --- /dev/null +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/drivers/gpD320fx.ts @@ -0,0 +1,42 @@ +import { buildTscLabelData, buildTscTestPrintData } from '../protocols/tscProtocol' +import type { PrinterCandidate, PrinterDriver } from '../types/printer' + +const KEYWORDS = [ + 'd320fx', + 'gp-d320fx', +] + +function score (device: PrinterCandidate): number { + const text = `${device.name || ''} ${device.deviceId || ''}`.toLowerCase() + let total = 0 + KEYWORDS.forEach((keyword) => { + if (text.includes(keyword)) total += 80 + }) + if (text.includes('gprinter')) total += 10 + return total +} + +export const gpD320fxDriver: PrinterDriver = { + key: 'gp-d320fx', + brand: 'Gprinter', + model: 'GP-D320FX', + displayName: 'Gprinter GP-D320FX', + protocol: 'tsc', + preferredConnection: 'ble', + preferredBleMtu: 512, + imageMaxWidthDots: 800, + imageDpi: 203, + keywords: KEYWORDS, + matches (device) { + return score(device) + }, + resolveConnectionType (device) { + return device.type === 'classic' ? 'classic' : 'ble' + }, + buildTestPrintData () { + return buildTscTestPrintData() + }, + buildLabelData (payload) { + return buildTscLabelData(payload) + }, +} diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/manager/driverRegistry.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/manager/driverRegistry.ts index 3e27fd0..f9e7600 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/manager/driverRegistry.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/manager/driverRegistry.ts @@ -1,10 +1,12 @@ import { d320faxDriver } from '../drivers/d320fax' import { genericTscDriver } from '../drivers/genericTsc' +import { gpD320fxDriver } from '../drivers/gpD320fx' import { gpR3Driver } from '../drivers/gpR3' import type { PrinterCandidate, PrinterDriver, ResolvedPrinterCandidate } from '../types/printer' const printerDrivers: PrinterDriver[] = [ gpR3Driver, + gpD320fxDriver, d320faxDriver, genericTscDriver, ] diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts index 981e4a1..2ba6218 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts @@ -41,7 +41,38 @@ function getPrinterTypeDisplayName (type: '' | 'bluetooth' | 'builtin'): string function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDriver): Promise { return new Promise((resolve, reject) => { // #ifdef APP-PLUS - if (isNativeFastPrinterAvailable()) { + const shouldUseGenericClassicOnly = driver.key === 'gp-d320fx' + + const connectClassicSocketFallback = () => { + try { + if (!classicBluetooth || typeof classicBluetooth.connDevice !== 'function') { + reject(new Error('Classic Bluetooth fallback is not available.')) + return + } + classicBluetooth.connDevice(device.deviceId, (ok: boolean) => { + if (ok) { + setBluetoothConnection({ + deviceId: device.deviceId, + deviceName: device.name || 'Bluetooth Printer', + deviceType: 'classic', + transportMode: 'generic', + driverKey: driver.key, + mtu: driver.preferredBleMtu || 20, + }) + resolve() + return + } + const message = typeof classicBluetooth.getLastError === 'function' + ? classicBluetooth.getLastError() + : '' + reject(new Error(message || 'Classic Bluetooth connection failed.')) + }) + } catch (error: any) { + reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.'))) + } + } + + if (!shouldUseGenericClassicOnly && isNativeFastPrinterAvailable()) { connectNativeFastPrinterPlugin({ deviceId: device.deviceId, deviceName: device.name || 'Bluetooth Printer', @@ -50,15 +81,24 @@ function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDrive deviceId: device.deviceId, deviceName: device.name || 'Bluetooth Printer', deviceType: 'classic', + transportMode: 'native-plugin', driverKey: driver.key, mtu: driver.preferredBleMtu || 20, }) resolve() }).catch((error: any) => { + if (driver.key === 'd320fax') { + connectClassicSocketFallback() + return + } reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.'))) }) return } + if (driver.key === 'd320fax' || shouldUseGenericClassicOnly) { + connectClassicSocketFallback() + return + } reject(new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')) // #endif // #ifndef APP-PLUS @@ -103,19 +143,44 @@ function findBleWriteCharacteristic (deviceId: string): Promise<{ serviceId: str }) } +function requestBleMtu (deviceId: string, preferredMtu: number): Promise { + return new Promise((resolve) => { + const targetMtu = Math.max(20, Math.min(512, Math.round(preferredMtu || 20))) + if (targetMtu <= 20 || typeof (uni as any).setBLEMTU !== 'function') { + resolve(20) + return + } + let settled = false + const done = (value: number) => { + if (settled) return + settled = true + clearTimeout(timer) + resolve(Math.max(20, Math.round(value || 20))) + } + const timer = setTimeout(() => done(20), 3000) + ;(uni as any).setBLEMTU({ + deviceId, + mtu: targetMtu, + success: (res: any) => done(Number(res?.mtu) || targetMtu), + fail: () => done(20), + }) + }) +} + function connectBlePrinter (device: PrinterCandidate, driver: PrinterDriver): Promise { const finalizeExistingBleConnection = async () => { const write = await findBleWriteCharacteristic(device.deviceId) if (!write) { throw new Error('No writable characteristic found. This device may not support printing.') } + const negotiatedMtu = await requestBleMtu(device.deviceId, driver.preferredBleMtu || 20) setBluetoothConnection({ deviceId: device.deviceId, deviceName: device.name || 'Bluetooth Printer', serviceId: write.serviceId, characteristicId: write.characteristicId, deviceType: 'ble', - mtu: driver.preferredBleMtu || 20, + mtu: negotiatedMtu, driverKey: driver.key, }) } @@ -145,6 +210,14 @@ function connectBlePrinter (device: PrinterCandidate, driver: PrinterDriver): Pr export async function connectBluetoothPrinter (device: PrinterCandidate): Promise { const driver = resolvePrinterDriver(device) + if (driver.key === 'gp-d320fx') { + try { + await connectBlePrinter(device, driver) + } catch (_) { + await connectClassicBluetooth(device, driver) + } + return driver + } const resolvedType = driver.resolveConnectionType(device) if (resolvedType === 'classic') { await connectClassicBluetooth(device, driver) @@ -218,19 +291,25 @@ function canUseNativeFastTemplatePrint (driver: PrinterDriver): boolean { const connection = getBluetoothConnection() return driver.protocol === 'tsc' && connection?.deviceType === 'classic' + && connection?.transportMode === 'native-plugin' && isNativeFastPrinterAvailable() } function getNativeClassicConnection () { const connection = getBluetoothConnection() - if (!connection || connection.deviceType !== 'classic') return null + if (!connection || connection.deviceType !== 'classic' || connection.transportMode !== 'native-plugin') return null return connection } export async function testPrintCurrentPrinter (onProgress?: (percent: number) => void): Promise { const driver = getCurrentPrinterDriver() const connection = getBluetoothConnection() - if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) { + if ( + driver.protocol === 'tsc' + && connection?.deviceType === 'classic' + && connection?.transportMode === 'native-plugin' + && !isNativeFastPrinterAvailable() + ) { throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.') } if (canUseNativeFastTemplatePrint(driver)) { @@ -319,7 +398,12 @@ export async function printSystemTemplateForCurrentPrinter ( ): Promise { const driver = getCurrentPrinterDriver() const connection = getBluetoothConnection() - if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) { + if ( + driver.protocol === 'tsc' + && connection?.deviceType === 'classic' + && connection?.transportMode === 'native-plugin' + && !isNativeFastPrinterAvailable() + ) { throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.') } if (canUseNativeFastTemplatePrint(driver)) { @@ -341,6 +425,7 @@ export async function printSystemTemplateForCurrentPrinter ( const structuredTemplate = adaptSystemLabelTemplate(template, data, { dpi: driver.imageDpi || 203, printQty: options.printQty || 1, + disableBitmapText: driver.key === 'gp-d320fx', }) const bytes = driver.protocol === 'esc' ? buildEscPosTemplateData(structuredTemplate) @@ -360,7 +445,7 @@ export function disconnectCurrentPrinter (): Promise { if (type === 'bluetooth' && connection?.deviceType === 'classic') { // #ifdef APP-PLUS - if (isNativeFastPrinterAvailable()) { + if (connection.transportMode === 'native-plugin' && isNativeFastPrinterAvailable()) { disconnectNativeFastPrinterPlugin().catch((e: any) => { console.error('Disconnect native fast printer failed', e) }).finally(() => { diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts index c27d915..b566b52 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts @@ -10,6 +10,14 @@ const DESIGN_DPI = 96 const DEFAULT_THRESHOLD = 180 const TEXT_PADDING_DOTS = 6 +function normalizePrinterLikeText (str: string): string { + return String(str || '') + .normalize('NFKC') + .replace(/[\u2018\u2019]/g, '\'') + .replace(/[\u201C\u201D]/g, '"') + .replace(/[\u2013\u2014]/g, '-') +} + type BitmapPatchItem = { type: 'bitmap' x: number @@ -144,10 +152,11 @@ function splitTextLines (text: string, paint: any, maxWidth: number): string[] { export function shouldRasterizeTextElement (text: string, type: string): boolean { const normalizedType = String(type || '').toUpperCase() - if (!text) return false - if (normalizedType === 'TEXT_PRICE') return true - if (/[€£¥¥$éÉáàâäãåæçèêëìíîïñòóôöõøùúûüýÿœšž]/.test(text)) return true - return /[^\x20-\x7E]/.test(text) + const normalizedText = normalizePrinterLikeText(text) + if (!normalizedText) return false + if (normalizedType === 'TEXT_PRICE' && /[€£¥¥]/.test(normalizedText)) return true + if (/[€£¥¥éÉáàâäãåæçèêëìíîïñòóôöõøùúûüýÿœšž]/.test(normalizedText)) return true + return /[^\x20-\x7E]/.test(normalizedText) } export function createTextBitmapPatch (params: { diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts index 8937397..e7334df 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts @@ -11,6 +11,7 @@ const STORAGE_BT_DEVICE_NAME = 'btDeviceName' const STORAGE_BT_SERVICE_ID = 'btServiceId' const STORAGE_BT_CHARACTERISTIC_ID = 'btCharacteristicId' const STORAGE_BT_DEVICE_TYPE = 'btDeviceType' // 'ble' | 'classic' +const STORAGE_BT_TRANSPORT_MODE = 'btTransportMode' // 'native-plugin' | 'generic' const STORAGE_BLE_MTU = 'bleMTU' const STORAGE_BUILTIN_PORT = 'builtinPort' const STORAGE_PRINTER_DRIVER_KEY = 'printerDriverKey' @@ -29,6 +30,7 @@ export const PrinterStorageKeys = { btServiceId: STORAGE_BT_SERVICE_ID, btCharacteristicId: STORAGE_BT_CHARACTERISTIC_ID, btDeviceType: STORAGE_BT_DEVICE_TYPE, + btTransportMode: STORAGE_BT_TRANSPORT_MODE, bleMTU: STORAGE_BLE_MTU, driverKey: STORAGE_PRINTER_DRIVER_KEY, } as const @@ -43,6 +45,7 @@ export function setBluetoothConnection (info: { serviceId?: string characteristicId?: string deviceType?: BtDeviceType + transportMode?: 'native-plugin' | 'generic' mtu?: number driverKey?: string }) { @@ -52,6 +55,10 @@ export function setBluetoothConnection (info: { uni.setStorageSync(STORAGE_BT_SERVICE_ID, info.serviceId || '') uni.setStorageSync(STORAGE_BT_CHARACTERISTIC_ID, info.characteristicId || '') uni.setStorageSync(STORAGE_BT_DEVICE_TYPE, info.deviceType || 'ble') + uni.setStorageSync( + STORAGE_BT_TRANSPORT_MODE, + info.transportMode || (info.deviceType === 'classic' ? 'native-plugin' : 'generic') + ) uni.setStorageSync(STORAGE_BLE_MTU, info.mtu != null ? info.mtu : BLE_MTU_DEFAULT) uni.setStorageSync(STORAGE_PRINTER_DRIVER_KEY, info.driverKey || '') } @@ -68,6 +75,7 @@ export function clearPrinter () { uni.removeStorageSync(STORAGE_BT_SERVICE_ID) uni.removeStorageSync(STORAGE_BT_CHARACTERISTIC_ID) uni.removeStorageSync(STORAGE_BT_DEVICE_TYPE) + uni.removeStorageSync(STORAGE_BT_TRANSPORT_MODE) uni.removeStorageSync(STORAGE_BLE_MTU) uni.removeStorageSync(STORAGE_BUILTIN_PORT) uni.removeStorageSync(STORAGE_PRINTER_DRIVER_KEY) @@ -93,10 +101,12 @@ export function getBluetoothConnection (): { serviceId: string characteristicId: string deviceType: BtDeviceType + transportMode: 'native-plugin' | 'generic' mtu: number } | null { const deviceId = uni.getStorageSync(STORAGE_BT_DEVICE_ID) const deviceType = (uni.getStorageSync(STORAGE_BT_DEVICE_TYPE) as BtDeviceType) || 'ble' + const transportMode = (uni.getStorageSync(STORAGE_BT_TRANSPORT_MODE) as 'native-plugin' | 'generic') || 'generic' if (!deviceId) return null if (deviceType === 'classic') { return { @@ -105,6 +115,7 @@ export function getBluetoothConnection (): { serviceId: '', characteristicId: '', deviceType: 'classic', + transportMode, mtu: BLE_MTU_DEFAULT, } } @@ -117,6 +128,7 @@ export function getBluetoothConnection (): { serviceId, characteristicId, deviceType: 'ble', + transportMode, mtu: Number(uni.getStorageSync(STORAGE_BLE_MTU)) || BLE_MTU_DEFAULT, } } @@ -209,15 +221,33 @@ function sendViaBle ( return Promise.reject(new Error('Bluetooth printer not connected.')) } const { deviceId, serviceId, characteristicId, mtu } = conn + const payloadSize = Math.max(20, Math.round((mtu || BLE_MTU_DEFAULT) > 23 ? (mtu || BLE_MTU_DEFAULT) - 3 : (mtu || BLE_MTU_DEFAULT))) const chunks: number[][] = [] - for (let i = 0; i < data.length; i += mtu) { - chunks.push(data.slice(i, i + mtu)) + for (let i = 0; i < data.length; i += payloadSize) { + chunks.push(data.slice(i, i + payloadSize)) } const total = chunks.length let sent = 0 + let completed = false + let timeoutId: ReturnType | null = setTimeout(() => {}, 0) + const writeDelayMs = payloadSize >= 180 ? 0 : (payloadSize > 20 ? 1 : 8) + + const resetTimeout = (reject: (reason?: any) => void) => { + if (timeoutId) clearTimeout(timeoutId) + timeoutId = setTimeout(() => { + if (completed) return + completed = true + reject(new Error('BLE write timeout')) + }, 15000) + } function sendNext (): Promise { + if (completed) { + return Promise.reject(new Error('BLE write timeout')) + } if (sent >= total) { + completed = true + if (timeoutId) clearTimeout(timeoutId) if (onProgress) onProgress(100) return Promise.resolve() } @@ -228,17 +258,28 @@ function sendViaBle ( view.setUint8(j, chunk[j] & 0xff) } return new Promise((resolve, reject) => { + resetTimeout(reject) uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: buffer, success: () => { + if (completed) return sent++ if (onProgress) onProgress(Math.round((sent / total) * 100)) - setTimeout(() => sendNext().then(resolve).catch(reject), 10) + if (writeDelayMs <= 0) { + sendNext().then(resolve).catch(reject) + return + } + setTimeout(() => sendNext().then(resolve).catch(reject), writeDelayMs) + }, + fail: (err: any) => { + if (completed) return + completed = true + if (timeoutId) clearTimeout(timeoutId) + reject(new Error(err.errMsg || 'BLE write failed')) }, - fail: (err: any) => reject(new Error(err.errMsg || 'BLE write failed')), }) }) } @@ -256,9 +297,22 @@ function sendViaClassic ( return Promise.reject(new Error('Classic Bluetooth printer not connected.')) } return new Promise((resolve, reject) => { + let settled = false + const finish = (fn: () => void) => { + if (settled) return + settled = true + clearTimeout(timeoutId) + fn() + } + const timeoutId = setTimeout(() => { + finish(() => { + reject(buildClassicBluetoothError('Classic Bluetooth send timeout', conn.deviceId)) + }) + }, 15000) + try { if (!classicBluetooth) { - reject(new Error('Classic Bluetooth not available')) + finish(() => reject(new Error('Classic Bluetooth not available'))) return } const debugState = typeof classicBluetooth.getDebugState === 'function' @@ -272,7 +326,7 @@ function sendViaClassic ( const errorMessage = typeof classicBluetooth.getLastError === 'function' ? classicBluetooth.getLastError() : '' - reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth connection is not ready', conn.deviceId)) + finish(() => reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth connection is not ready', conn.deviceId))) return } @@ -283,31 +337,35 @@ function sendViaClassic ( if (typeof classicBluetooth.sendByteDataAsync === 'function') { classicBluetooth.sendByteDataAsync(sendData, (ok: boolean, errorMessage?: string) => { - if (onProgress) onProgress(100) - if (ok) { - resolve() - return - } - reject(buildClassicBluetoothError( - errorMessage || classicBluetooth.getLastError?.() || 'Classic Bluetooth send failed', - conn.deviceId - )) + finish(() => { + if (onProgress) onProgress(100) + if (ok) { + resolve() + return + } + reject(buildClassicBluetoothError( + errorMessage || classicBluetooth.getLastError?.() || 'Classic Bluetooth send failed', + conn.deviceId + )) + }) }) return } const ok = classicBluetooth.sendByteData(sendData) - if (onProgress) onProgress(100) - if (ok) { - resolve() - return - } - const errorMessage = typeof classicBluetooth.getLastError === 'function' - ? classicBluetooth.getLastError() - : '' - reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth send failed', conn.deviceId)) + finish(() => { + if (onProgress) onProgress(100) + if (ok) { + resolve() + return + } + const errorMessage = typeof classicBluetooth.getLastError === 'function' + ? classicBluetooth.getLastError() + : '' + reject(buildClassicBluetoothError(errorMessage || 'Classic Bluetooth send failed', conn.deviceId)) + }) } catch (e: any) { - reject(buildClassicBluetoothError(e?.message || String(e || 'Classic Bluetooth send exception'), conn.deviceId)) + finish(() => reject(buildClassicBluetoothError(e?.message || String(e || 'Classic Bluetooth send exception'), conn.deviceId))) } }) // #endif diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts index 3af524c..0fc3000 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts @@ -297,7 +297,10 @@ function buildTscTemplate ( template: SystemLabelTemplate, data: LabelTemplateData, dpi: number, - printQty: number + printQty: number, + options: { + disableBitmapText?: boolean + } = {} ): StructuredTscTemplate { const widthMm = roundNumber(toMillimeter(template.width, template.unit || 'inch')) const heightMm = roundNumber(toMillimeter(template.height, template.unit || 'inch')) @@ -324,7 +327,7 @@ function buildTscTemplate ( const scale = resolveTextScale(getConfigNumber(config, ['fontSize'], 14), dpi) const align = resolveElementAlign(element, pageWidth) - if (shouldRasterizeTextElement(text, type)) { + if (!options.disableBitmapText && shouldRasterizeTextElement(text, type)) { const bitmapPatch = createTextBitmapPatch({ element, text, @@ -510,13 +513,16 @@ export function adaptSystemLabelTemplate ( options: { dpi?: number printQty?: number + disableBitmapText?: boolean } = {} ): StructuredLabelTemplate { const dpi = options.dpi || 203 const printQty = Math.max(1, Math.round(options.printQty || 1)) return { key: template.id || template.name || 'system-label-template', - tsc: buildTscTemplate(template, data, dpi, printQty), + tsc: buildTscTemplate(template, data, dpi, printQty, { + disableBitmapText: options.disableBitmapText, + }), esc: buildEscTemplate(template, data, printQty), } } diff --git a/美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts b/美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts index 4b21651..63642f1 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts @@ -220,7 +220,7 @@ export function buildTscTemplateLabel (template: StructuredTscTemplate): number[ if (item.type === 'bitmap') { const bytesPerRow = item.image.width / 8 const bitmapBytes = pixelsToTscBitmapBytes(item.image) - add(`BITMAP ${item.x},${item.y},${bytesPerRow},${item.image.height},0,`) + addCommandBytes(out, `BITMAP ${item.x},${item.y},${bytesPerRow},${item.image.height},0,`) for (let i = 0; i < bitmapBytes.length; i++) out.push(bitmapBytes[i]) out.push(0x0d, 0x0a) return @@ -275,7 +275,7 @@ export function buildTscImageLabel ( add('DENSITY 14') add('SPEED 5') add('CLS') - add(`BITMAP ${x},${y},${bytesPerRow},${image.height},0,`) + addCommandBytes(out, `BITMAP ${x},${y},${bytesPerRow},${image.height},0,`) for (let i = 0; i < bitmapBytes.length; i++) out.push(bitmapBytes[i]) out.push(0x0d, 0x0a) add(`PRINT 1,${printQty}`)