diff --git a/美国版/Food Labeling Management App UniApp/src/locales/en.ts b/美国版/Food Labeling Management App UniApp/src/locales/en.ts index 46c10b7..7bde161 100644 --- a/美国版/Food Labeling Management App UniApp/src/locales/en.ts +++ b/美国版/Food Labeling Management App UniApp/src/locales/en.ts @@ -53,6 +53,11 @@ export default { connectFail: 'Connection failed', bleNotAvailable: 'Bluetooth not available', noDevices: 'No devices found', + pairedDevices: 'Paired Devices', + noPairedDevices: 'No paired Bluetooth devices', + classic: 'Classic', + ble: 'BLE', + dual: 'Dual', printer1: { name: 'Kitchen Printer #1', location: 'Main Kitchen' }, printer2: { name: 'Kitchen Printer #2', location: 'Main Kitchen' }, printer3: { name: 'Prep Area Printer', location: 'Prep Station' }, diff --git a/美国版/Food Labeling Management App UniApp/src/locales/zh.ts b/美国版/Food Labeling Management App UniApp/src/locales/zh.ts index 98cb9a9..b4ac717 100644 --- a/美国版/Food Labeling Management App UniApp/src/locales/zh.ts +++ b/美国版/Food Labeling Management App UniApp/src/locales/zh.ts @@ -53,6 +53,11 @@ export default { connectFail: '连接失败', bleNotAvailable: '蓝牙未开启或不可用', noDevices: '未发现设备', + pairedDevices: '已配对设备', + noPairedDevices: '没有已配对的蓝牙设备', + classic: '经典', + ble: '低功耗', + dual: '双模', printer1: { name: '厨房打印机 #1', location: '主厨房' }, printer2: { name: '厨房打印机 #2', location: '主厨房' }, printer3: { name: '准备区打印机', location: '准备站' }, 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 457ffed..83d0b28 100644 --- a/美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue +++ b/美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue @@ -234,7 +234,7 @@ const initBluetooth = (): Promise => { }) } -const startDiscovery = () => { +const startBleScan = () => { uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success: () => { @@ -242,29 +242,9 @@ const startDiscovery = () => { errorMsg.value = '' }, fail: (err: any) => { - isScanning.value = false - errorMsg.value = 'Scan failed: ' + (err.errMsg || 'Unknown error') + console.error('BLE startBluetoothDevicesDiscovery fail:', err) }, }) - // 同时启动经典蓝牙扫描(发现 d320fax_295c 等未配对设备,不过滤任何设备) - // #ifdef APP-PLUS - const classic = getClassicBluetooth() - if (classic && classic.startClassicDiscovery) { - classic.startClassicDiscovery( - (dev: { name: string; deviceId: string; type: string }) => { - if (discoveredIds.has(dev.deviceId)) return - discoveredIds.add(dev.deviceId) - devices.value.push({ - deviceId: dev.deviceId, - name: dev.name || 'Unknown', - type: (dev.type as BtDevice['type']) || 'classic', - }) - devices.value.sort((a, b) => (b.RSSI || -100) - (a.RSSI || -100)) - }, - () => { /* 经典蓝牙扫描完成 */ }, - ) - } - // #endif } const stopDiscovery = () => { @@ -383,15 +363,41 @@ const handleScan = async () => { errorMsg.value = '' devices.value = [] discoveredIds.clear() + + addPairedDevices() + + // #ifdef APP-PLUS + const classic = getClassicBluetooth() + if (classic && classic.startClassicDiscovery) { + try { + classic.startClassicDiscovery( + (dev: { name: string; deviceId: string; type: string }) => { + if (discoveredIds.has(dev.deviceId)) return + discoveredIds.add(dev.deviceId) + devices.value.push({ + deviceId: dev.deviceId, + name: dev.name || 'Unknown', + type: (dev.type as BtDevice['type']) || 'classic', + }) + }, + () => {}, + ) + isScanning.value = true + } catch (e) { + console.error('Classic discovery failed', e) + } + } + // #endif + + if (devices.value.length > 0) { + uni.showToast({ title: `Found ${devices.value.length} device(s)`, icon: 'none' }) + } + try { if (!btAdapterReady.value) { await initBluetooth() } - addPairedDevices() mergeCachedBleDevices() - if (devices.value.length > 0) { - uni.showToast({ title: `Found ${devices.value.length} device(s)`, icon: 'none' }) - } await new Promise((resolve) => { uni.getLocation({ type: 'wgs84', @@ -399,13 +405,15 @@ const handleScan = async () => { fail: () => resolve(), }) }) - startDiscovery() + startBleScan() uni.showToast({ title: 'Scanning...', icon: 'none' }) setTimeout(() => { if (isScanning.value) stopDiscovery() }, 20000) } catch (_) { - // error set in initBluetooth + if (!isScanning.value && devices.value.length === 0) { + errorMsg.value = 'Bluetooth scan failed. Check if Bluetooth is enabled.' + } } } @@ -563,7 +571,17 @@ onMounted(() => { onUnmounted(() => { if (isScanning.value) stopDiscovery() - uni.offBluetoothDeviceFound() + try { + if (typeof uni.offBluetoothDeviceFound === 'function') { + uni.offBluetoothDeviceFound() + } + } catch (_) {} + try { + if (typeof uni.offBluetoothAdapterStateChange === 'function') { + uni.offBluetoothAdapterStateChange() + } + } catch (_) {} + uni.closeBluetoothAdapter({ complete: () => {} }) }) diff --git a/美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue b/美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue index 26c7666..d5d7093 100644 --- a/美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue +++ b/美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue @@ -265,8 +265,8 @@ const handlePrint = async () => { const msg = (e && e.message) ? e.message : 'Print failed' if (msg === 'BUILTIN_PLUGIN_NOT_FOUND' || (msg && msg.indexOf('Built-in printer') !== -1)) { uni.showModal({ - title: 'Use Bluetooth Mode', - content: 'For GP-D320FAX, go to Printer settings, switch to Bluetooth mode and tap Scan to connect (e.g. d320fax_295c or Virtual BT Printer). Built-in mode needs custom app packaging.', + title: 'Built-in Print Not Available', + content: 'This device does not support TCP built-in printing. Please switch to Bluetooth mode: go to Printer Settings, tap Scan, and connect to your printer (look for a device name like "Virtual BT Printer" or your printer model). You may need to pair it first in Android Bluetooth settings.', confirmText: 'Printer Settings', cancelText: 'Cancel', success: (res) => { 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 d8a1133..15f1a4f 100644 --- a/美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue +++ b/美国版/Food Labeling Management App UniApp/src/pages/more/printers.vue @@ -45,6 +45,29 @@ + + + + {{ t('printers.pairedDevices') }} + + + + + + {{ d.name || 'Unknown Device' }} + {{ d.deviceId }} + + + {{ getTypeLabel(d.type) }} + + + + + + {{ t('printers.noPairedDevices') }} + + + {{ t('printers.nearby') }} @@ -62,7 +85,10 @@ {{ d.name || 'Unknown Device' }} {{ d.deviceId }} - + + {{ getTypeLabel(d.type) }} + + @@ -90,51 +116,101 @@ import { } from '../../utils/print/printerConnection' import { buildTestTscLabel } from '../../utils/print/tscLabelBuilder' +// #ifdef APP-PLUS +const classicBluetooth = (require('../../utils/print/bluetoothTool.js') as any).default +// #endif + const { t } = useI18n() const isMenuOpen = ref(false) -// 状态管理 const currentType = ref(getPrinterType()) const currentBt = ref(getBluetoothConnection()) const isScanning = ref(false) const devices = ref([]) +const pairedDevices = ref([]) const isConnecting = ref(false) +let bleListenerRegistered = false -// Refresh current state const refreshStatus = () => { currentType.value = getPrinterType() currentBt.value = getBluetoothConnection() } -// 蓝牙搜索 +const getTypeLabel = (type?: string) => { + switch (type) { + case 'classic': return t('printers.classic') + case 'ble': return t('printers.ble') + case 'dual': return t('printers.dual') + default: return t('printers.ble') + } +} + +const addDeviceDedup = (device: any) => { + const existing = devices.value.find(d => d.deviceId === device.deviceId) + if (!existing) { + devices.value.push(device) + } +} + +// #ifdef APP-PLUS +const loadPairedDevices = () => { + try { + const list = classicBluetooth.getPairedDevices() + pairedDevices.value = list || [] + } catch (e) { + console.error('Failed to load paired devices', e) + pairedDevices.value = [] + } +} +// #endif + const startScan = () => { if (isScanning.value) return - + devices.value = [] isScanning.value = true - + + // #ifdef APP-PLUS + loadPairedDevices() + try { + classicBluetooth.startClassicDiscovery( + (device: any) => { + if (device.name) { + addDeviceDedup(device) + } + }, + () => {} + ) + } catch (e) { + console.error('Classic discovery failed', e) + } + // #endif + uni.openBluetoothAdapter({ success: () => { + if (!bleListenerRegistered) { + bleListenerRegistered = true + uni.onBluetoothDeviceFound((res) => { + res.devices.forEach(device => { + if (device.name) { + addDeviceDedup({ ...device, type: 'ble' }) + } + }) + }) + } uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, - success: () => { - uni.onBluetoothDeviceFound((res) => { - res.devices.forEach(device => { - if (device.name && !devices.value.find(d => d.deviceId === device.deviceId)) { - devices.value.push(device) - } - }) - }) - }, + success: () => {}, fail: (err) => { - isScanning.value = false - uni.showToast({ title: t('printers.bleNotAvailable'), icon: 'none' }) + console.error('BLE startBluetoothDevicesDiscovery fail:', err) } }) }, fail: (err) => { - isScanning.value = false - uni.showToast({ title: t('printers.bleNotAvailable'), icon: 'none' }) + console.error('openBluetoothAdapter fail:', err) + if (devices.value.length === 0 && pairedDevices.value.length === 0) { + uni.showToast({ title: t('printers.bleNotAvailable'), icon: 'none' }) + } } }) } @@ -142,37 +218,73 @@ const startScan = () => { const stopScan = () => { isScanning.value = false uni.stopBluetoothDevicesDiscovery({ - success: () => console.log('Stop scan success'), - fail: (err) => console.error('Stop scan fail', err) + success: () => console.log('Stop BLE scan success'), + fail: (err) => console.error('Stop BLE scan fail', err) }) + // #ifdef APP-PLUS + try { + classicBluetooth.cancelClassicDiscovery() + } catch (e) { + console.error('Cancel classic discovery failed', e) + } + // #endif } -// 连接蓝牙设备 const connectBt = (device: any) => { if (isConnecting.value) return isConnecting.value = true stopScan() - + uni.showLoading({ title: t('printers.connecting') }) - + + const deviceType = device.type || 'ble' + + if (deviceType === 'classic' || deviceType === 'dual') { + // #ifdef APP-PLUS + classicBluetooth.connDevice(device.deviceId, (success: boolean) => { + isConnecting.value = false + uni.hideLoading() + if (success) { + setBluetoothConnection({ + deviceId: device.deviceId, + deviceName: device.name || 'Bluetooth Printer', + deviceType: 'classic' + }) + currentType.value = 'bluetooth' + currentBt.value = getBluetoothConnection() + uni.showToast({ title: t('printers.connectSuccess') }) + } else { + uni.showToast({ title: t('printers.connectFail'), icon: 'none' }) + } + }) + // #endif + // #ifndef APP-PLUS + isConnecting.value = false + uni.hideLoading() + uni.showToast({ title: t('printers.connectFail'), icon: 'none' }) + // #endif + } else { + connectBle(device) + } +} + +const connectBle = (device: any) => { uni.createBLEConnection({ deviceId: device.deviceId, success: () => { uni.getBLEDeviceServices({ deviceId: device.deviceId, success: (res) => { - // 这里简化处理:寻找第一个包含写权限的特征值 - // 实际项目中可能需要根据特定 UUID 匹配,官方示例中是遍历寻找 findWriteCharacteristic(device.deviceId, res.services, device.name) }, - fail: (err) => { + fail: () => { isConnecting.value = false uni.hideLoading() uni.showToast({ title: t('printers.connectFail'), icon: 'none' }) } }) }, - fail: (err) => { + fail: () => { isConnecting.value = false uni.hideLoading() uni.showToast({ title: t('printers.connectFail'), icon: 'none' }) @@ -183,7 +295,7 @@ const connectBt = (device: any) => { const findWriteCharacteristic = (deviceId: string, services: any[], deviceName: string) => { let found = false let serviceIdx = 0 - + const nextService = () => { if (serviceIdx >= services.length || found) { if (!found) { @@ -193,7 +305,7 @@ const findWriteCharacteristic = (deviceId: string, services: any[], deviceName: } return } - + const service = services[serviceIdx++] uni.getBLEDeviceCharacteristics({ deviceId, @@ -221,26 +333,32 @@ const findWriteCharacteristic = (deviceId: string, services: any[], deviceName: fail: () => nextService() }) } - + nextService() } -// 连接内置打印机 const connectBuiltin = () => { setBuiltinPrinter() currentType.value = 'builtin' uni.showToast({ title: t('printers.connectSuccess') }) } -// 断开连接 const disconnect = () => { + // #ifdef APP-PLUS + if (currentBt.value?.deviceType === 'classic') { + try { + classicBluetooth.disConnDevice() + } catch (e) { + console.error('Disconnect classic bluetooth failed', e) + } + } + // #endif clearPrinter() currentType.value = '' currentBt.value = null uni.showToast({ title: t('printers.disconnected') }) } -// 测试打印 const doTestPrint = async () => { try { const data = buildTestTscLabel() @@ -250,7 +368,17 @@ const doTestPrint = async () => { uni.showToast({ title: t('printers.testPrintSuccess') }) } catch (e: any) { uni.hideLoading() - uni.showToast({ title: t('printers.testPrintFail') + ': ' + e.message, icon: 'none' }) + const msg = (e && e.message) ? e.message : 'Print failed' + if (msg.indexOf('Built-in printer') !== -1 || msg === 'BUILTIN_PLUGIN_NOT_FOUND') { + uni.showModal({ + title: 'Print Failed', + content: 'Built-in TCP printing is not available on this device. Please switch to Bluetooth mode and scan for your printer. Check Android Bluetooth settings to pair with "Virtual BT Printer" or your printer model first.', + confirmText: 'OK', + showCancel: false, + }) + } else { + uni.showToast({ title: t('printers.testPrintFail') + ': ' + msg, icon: 'none' }) + } } } @@ -265,10 +393,22 @@ const goBack = () => { onMounted(() => { refreshStatus() + // #ifdef APP-PLUS + loadPairedDevices() + // #endif }) onUnmounted(() => { if (isScanning.value) stopScan() + if (bleListenerRegistered) { + bleListenerRegistered = false + try { + if (typeof uni.offBluetoothDeviceFound === 'function') { + uni.offBluetoothDeviceFound() + } + } catch (_) {} + } + uni.closeBluetoothAdapter({ complete: () => {} }) }) @@ -316,4 +456,10 @@ onUnmounted(() => { .empty-state { padding: 80rpx 0; text-align: center; } .empty-text { font-size: 28rpx; color: #9ca3af; } + +.device-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 12rpx; flex-shrink: 0; } +.device-type-tag { display: inline-block; padding: 4rpx 16rpx; font-size: 20rpx; border-radius: 8rpx; font-weight: 500; } +.tag-ble { background: #eff6ff; color: #3b82f6; } +.tag-classic { background: #fef3c7; color: #d97706; } +.tag-dual { background: #ecfdf5; color: #059669; } 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 91af55a..286c09b 100644 --- a/美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts +++ b/美国版/Food Labeling Management App UniApp/src/utils/print/printerConnection.ts @@ -9,6 +9,9 @@ const STORAGE_BT_SERVICE_ID = 'btServiceId' const STORAGE_BT_CHARACTERISTIC_ID = 'btCharacteristicId' const STORAGE_BT_DEVICE_TYPE = 'btDeviceType' // 'ble' | 'classic' const STORAGE_BLE_MTU = 'bleMTU' +const STORAGE_BUILTIN_PORT = 'builtinPort' + +const BUILTIN_PROBE_PORTS = [9100, 4000, 9000, 6000] export type PrinterType = 'bluetooth' | 'builtin' export type BtDeviceType = 'ble' | 'classic' @@ -56,6 +59,7 @@ export function clearPrinter () { uni.removeStorageSync(STORAGE_BT_CHARACTERISTIC_ID) uni.removeStorageSync(STORAGE_BT_DEVICE_TYPE) uni.removeStorageSync(STORAGE_BLE_MTU) + uni.removeStorageSync(STORAGE_BUILTIN_PORT) } const BLE_MTU_DEFAULT = 20 @@ -219,24 +223,46 @@ function sendViaBuiltin (data: number[]): Promise { const hexStr = Array.from(uint8) .map(b => ('0' + (b & 0xff).toString(16)).slice(-2)) .join('') - return new Promise((resolve, reject) => { - moeTcp.connect({ ip: '127.0.0.1', port: 9100 }, (res: string) => { - try { - const r = typeof res === 'string' ? JSON.parse(res) : res - if (r.code !== 1) { - reject(new Error(r.msg || 'Built-in printer connection failed')) - return - } - moeTcp.sendHexStr({ message: hexStr }) - setTimeout(() => { + + const savedPort = parseInt(uni.getStorageSync(STORAGE_BUILTIN_PORT)) || 0 + const ports = savedPort > 0 + ? [savedPort, ...BUILTIN_PROBE_PORTS.filter(p => p !== savedPort)] + : [...BUILTIN_PROBE_PORTS] + + function tryPort (idx: number): Promise { + if (idx >= ports.length) { + return Promise.reject(new Error( + 'Built-in printer: all ports failed (tried ' + BUILTIN_PROBE_PORTS.join(', ') + + '). Check if the printer service is running on this device.' + )) + } + const port = ports[idx] + return new Promise((resolve, reject) => { + console.log('[builtin] trying 127.0.0.1:' + port) + moeTcp.connect({ ip: '127.0.0.1', port }, (res: string) => { + try { + const r = typeof res === 'string' ? JSON.parse(res) : res + if (r.code !== 1) { + console.log('[builtin] port ' + port + ' failed: ' + (r.msg || '')) + try { moeTcp.disconnect() } catch (_) {} + tryPort(idx + 1).then(resolve).catch(reject) + return + } + console.log('[builtin] connected on port ' + port) + uni.setStorageSync(STORAGE_BUILTIN_PORT, String(port)) + moeTcp.sendHexStr({ message: hexStr }) + setTimeout(() => { + try { moeTcp.disconnect() } catch (_) {} + resolve() + }, 300) + } catch (e) { try { moeTcp.disconnect() } catch (_) {} - resolve() - }, 300) - } catch (e) { - reject(e) - } + tryPort(idx + 1).then(resolve).catch(reject) + } + }) }) - }) + } + return tryPort(0) } catch (e) { return Promise.reject(e) }