import { leFuService } from '../../lefu/index' import type { ScannedDevice } from '../../lefu/index' import type { RawDevice } from '../../lefu/types' /** 渲染用的设备项(RawDevice 含复杂对象,不进 setData) */ interface DeviceItem { deviceId: string name: string connected: boolean } type DeviceStatus = 'idle' | 'searching' | 'found' | 'empty' /** 扫描到的原始设备缓存 */ let scannedDevices: ScannedDevice[] = [] /** 扫描 / 断开回调的取消订阅句柄 */ let unsubDevicesList: (() => void) | null = null let unsubDisconnected: (() => void) | null = null let unsubDeviceConnect: (() => void) | null = null /** 连接流程的清理句柄 */ let connectCleanup: { timer: number | null unsubState: (() => void) | null unsubConnect: (() => void) | null } | null = null /** 一次性防重入标记 */ let modalLocked = false let autoConnectTried = false Page({ data: { status: 'idle' as DeviceStatus, deviceList: [] as DeviceItem[], }, onShow() { // 从其他页面返回时同步连接状态 if (leFuService.isConnected) { this._markConnected() } else { this._markAllDisconnected() } }, onHide() { leFuService.stopScan() // 搜索中切后台:停止扫描后重置状态,避免回来显示「搜索中」假状态 if (this.data.status === 'searching') { this.setData({ status: 'idle' }) } }, onUnload() { leFuService.stopScan() this._clearSubscriptions() this._clearConnectCleanup() }, /** 点击「开始 / 重新搜索」 */ async onStartSearch() { if (modalLocked) return scannedDevices = [] autoConnectTried = false this.setData({ status: 'searching', deviceList: [] }) await this._requestPermissionAndScan() }, /** 点击设备卡片:已连接跳配网,未连接走连接流程 */ onTapCard(e: WechatMiniprogram.TouchEvent) { const connected = e.currentTarget.dataset.connected as boolean if (connected) { wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' }) return } this.onConnect(e) }, /** 手动连接指定设备 */ onConnect(e: WechatMiniprogram.TouchEvent) { const index = e.currentTarget.dataset.index as number this._connectByIndex(index, false) }, /** 断开当前连接 */ onDisconnect() { this._markAllDisconnected() leFuService.disconnect() }, onOpenHelp() { wx.showToast({ title: '请确保设备已开机且在附近', icon: 'none' }) }, /** 将所有卡片置为未连接 */ _markAllDisconnected() { this.setData({ deviceList: this.data.deviceList.map(item => ({ ...item, connected: false })), }) }, /** 获取当前已连接设备的 deviceId(未连接返回空字符串) */ _getConnectedId(): string { if (!leFuService.isConnected) return '' const cached = wx.getStorageSync('connectDeviceInfo') as Record | null return cached?.deviceId || '' }, /** 连接成功后把对应设备卡片标记为已连接(覆盖手动连接与自动重连) */ _markConnected() { const id = this._getConnectedId() if (!id) return this.setData({ deviceList: this.data.deviceList.map(item => ({ ...item, connected: item.deviceId === id })), }) }, _clearSubscriptions() { unsubDevicesList?.() unsubDisconnected?.() unsubDeviceConnect?.() unsubDevicesList = null unsubDisconnected = null unsubDeviceConnect = null }, _clearConnectCleanup() { if (!connectCleanup) return if (connectCleanup.timer !== null) clearTimeout(connectCleanup.timer) connectCleanup.unsubState?.() connectCleanup.unsubConnect?.() connectCleanup = null }, /** 申请位置权限后打开蓝牙适配器(蓝牙权限由 openBluetoothAdapter 自动触发) */ async _requestPermissionAndScan() { try { const { authSetting } = await wx.getSetting() const setting = authSetting as Record if (!setting['scope.userLocation']) { await wx.authorize({ scope: 'scope.userLocation' }) } } catch { this._showLocationGuide() return } await this._openAdapterAndScan() }, async _openAdapterAndScan() { try { await wx.openBluetoothAdapter() } catch (err: any) { console.error('[connectedDevice] openBluetoothAdapter 失败:', err) this.setData({ status: 'idle' }) this._handleAdapterError() return } this._startScan() }, /** 区分蓝牙/定位服务开关,给出准确提示 */ _handleAdapterError() { try { const setting = wx.getSystemSetting() if (!setting.bluetoothEnabled) { this._showBluetoothOff() return } if (!setting.locationEnabled) { this._showLocationOff() return } } catch { // 基础库不支持 wx.getSystemSetting 时,退化为通用提示 } wx.showToast({ title: '蓝牙初始化失败,请重试', icon: 'none' }) }, _startScan() { this._clearSubscriptions() unsubDevicesList = leFuService.onDevicesList((devices) => { scannedDevices = devices if (!devices.length) { this.setData({ status: 'empty', deviceList: [] }) return } const connectedId = this._getConnectedId() this.setData({ status: 'found', deviceList: devices.map(d => ({ deviceId: d.raw.deviceId || '', name: d.name, connected: !!connectedId && d.raw.deviceId === connectedId, })), }) this._tryAutoConnect() }) unsubDisconnected = leFuService.onDisconnected(() => { this._markAllDisconnected() }) unsubDeviceConnect = leFuService.onDeviceConnect(() => { this._markConnected() }) leFuService.startScan() }, /** 命中缓存设备时自动连接一次 */ _tryAutoConnect() { if (autoConnectTried) return // 已连接时不自动重连,避免触发断开重连 if (leFuService.isConnected) return const cached = wx.getStorageSync('connectDeviceInfo') as RawDevice | null if (!cached?.deviceId) return const index = scannedDevices.findIndex(d => d.raw.deviceId === cached.deviceId) if (index < 0) return autoConnectTried = true this._connectByIndex(index, true) }, /** 统一连接流程(手动 / 自动共用),手动连接成功后跳配网 */ _connectByIndex(index: number, isAuto: boolean) { const scanned = scannedDevices[index] if (!scanned) return // 连接中:防止重复点击导致连接流程被重复触发 if (connectCleanup) return wx.showLoading({ title: '连接中...', mask: true }) leFuService.stopScan() leFuService.connect(scanned.raw) this._clearConnectCleanup() connectCleanup = { timer: null, unsubState: null, unsubConnect: null } const finish = () => { if (connectCleanup?.timer !== null) clearTimeout(connectCleanup.timer) connectCleanup?.unsubState?.() connectCleanup?.unsubConnect?.() connectCleanup = null wx.hideLoading() } connectCleanup.timer = setTimeout(() => { finish() wx.showToast({ title: '连接超时', icon: 'none' }) }, 15000) connectCleanup.unsubState = leFuService.onConnectState((state) => { if (state === leFuService.BLUE_STATE.CONNECTFAILED) { finish() wx.showToast({ title: '连接失败,请重试', icon: 'none' }) } else if (state === leFuService.BLUE_STATE.UNAVAILABLE) { // 连接过程中蓝牙被关闭:立即结束 loading,避免卡到超时 finish() wx.showToast({ title: '蓝牙已断开,请重新开启', icon: 'none' }) } }) connectCleanup.unsubConnect = leFuService.onDeviceConnect(() => { finish() wx.setStorageSync('connectDeviceInfo', { ...scanned.raw, scaleDeviceName: scanned.raw.name }) if (!isAuto) { wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' }) } }) }, _showBluetoothOff() { wx.showModal({ title: '蓝牙未开启', content: '请先开启手机蓝牙后重试', showCancel: false, confirmText: '知道了', success: () => this.setData({ status: 'idle' }), }) }, _showLocationOff() { wx.showModal({ title: '定位服务未开启', content: '安卓手机搜索蓝牙设备需开启定位服务,请前往系统设置开启后重试。', showCancel: false, confirmText: '知道了', }) }, _showLocationGuide() { modalLocked = true wx.showModal({ title: '需要授权', content: '小程序需要位置权限才能搜索蓝牙设备,请前往设置开启。', confirmText: '去开启', cancelText: '取消', success: (res) => { modalLocked = false this.setData({ status: 'idle' }) if (res.confirm) { wx.openSetting({ success: () => this.onStartSearch(), fail: () => this.setData({ status: 'idle' }), }) } }, fail: () => { modalLocked = false this.setData({ status: 'idle' }) }, }) }, })