import { leFuService } from '../../lefu/index' import type { ScannedDevice } from '../../lefu/index' import type { RawDevice } from '../../lefu/types' import { post } from '../../utils/request/index' import type { UserInfo } from '../../api/index' /** 绑定设备 SN 到用户信息(运维模式走 ops/addDevice,普通模式走 v6/bindDevice) */ const bindDevice = (data: { sn: string; idCard: string; connectDeviceInfo?: string }) => post( getApp().globalData.operationsEngineer ? 'weighingScale/ops/addDevice' : 'weighingScale/v6/bindDevice', data, { loading: false }, ) /** 渲染用的设备项(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 unsubInfo: (() => 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.openAppAuthorizeSetting() }, /** 调用接口把设备 SN 绑定到已保存的用户信息,成功后回调 */ _bindDevice(sn: string, onSuccess?: () => void) { const userInfo = wx.getStorageSync('userInfo') as UserInfo | null const idCard = userInfo?.idCard const connectDeviceInfo = leFuService.lastRawDevice ? JSON.stringify(leFuService.lastRawDevice) : '' if (!idCard) { onSuccess?.() return } bindDevice({ sn, idCard, connectDeviceInfo }) .then((res) => { console.log('[connectedDevice] bindDevice 成功:', res) onSuccess?.() }) .catch((err) => { console.error('[connectedDevice] bindDevice 失败:', err) this._resetAfterBindFail() }) }, /** bindDevice 失败后的清理重置:断开连接、清缓存、重置页面状态 */ _resetAfterBindFail() { leFuService.disconnect() wx.removeStorageSync('connectDeviceInfo') scannedDevices = [] this.setData({ status: 'idle', deviceList: [] }) 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.unsubInfo?.() connectCleanup = null }, /** 检查系统蓝牙开关 */ _checkBluetoothEnabled(): boolean { try { if (wx.getSystemSetting().bluetoothEnabled === false) { this._showBluetoothOff() return false } return true } catch { // 基础库不支持 wx.getSystemSetting 时跳过,交由 openBluetoothAdapter 报错兜底 return true } }, /** 检查系统定位开关 */ _checkLocationService(): boolean { try { if (wx.getSystemSetting().locationEnabled === false) { this._showLocationOff() return false } return true } catch { return true } }, /** 申请微信“附近设备/位置”授权 */ async _ensureLocationAuth(): Promise { try { const { authSetting } = await wx.getSetting() const setting = authSetting as Record if (!setting['scope.userLocation']) { await wx.authorize({ scope: 'scope.userLocation' }) } return true } catch { this._showLocationGuide() return false } }, /** 编排:蓝牙开关 → 定位开关 → 微信授权 → 打开蓝牙并扫描 */ async _requestPermissionAndScan() { if (!this._checkBluetoothEnabled()) return if (!this._checkLocationService()) return if (!(await this._ensureLocationAuth())) 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, unsubInfo: null } const finish = () => { if (connectCleanup?.timer !== null) clearTimeout(connectCleanup.timer) connectCleanup?.unsubState?.() connectCleanup?.unsubConnect?.() connectCleanup?.unsubInfo?.() connectCleanup = null wx.hideLoading() } // 连接成功 + SN 到位两个信号都满足才调接口,各自事件独立触发、顺序不定 let connected = false let sn: string | undefined const tryBind = () => { if (!connected || !sn) return finish() // 手动/自动连接:绑定设备成功后缓存设备并跳配网 this._bindDevice(sn, () => { wx.setStorageSync('connectDeviceInfo', { ...scanned.raw, scaleDeviceName: scanned.raw.name }) wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' }) }) } connectCleanup.timer = setTimeout(() => { finish() if (connected) { // 已连上但一直没拿到 SN:断开并重置,避免设备残留占用连接 leFuService.disconnect() wx.removeStorageSync('connectDeviceInfo') scannedDevices = [] this.setData({ status: 'idle', deviceList: [] }) wx.showToast({ title: '设备信息异常,请重试', icon: 'none' }) } else { wx.showToast({ title: '连接超时', icon: 'none' }) } }, 15000) connectCleanup.unsubState = leFuService.onConnectState((state) => { if (state === leFuService.BLUE_STATE.CONNECTFAILED) { finish() wx.showToast({ title: '连接失败,请重试', icon: 'none' }) } // UNAVAILABLE 不在此处理:connect 过程中 clearProtocol 会触发一次 UNAVAILABLE 抖动, // 后面紧跟 CONNECTSUCCESS,误判会打断连接流程并误报「蓝牙关闭」。 // 真正「连接后关闭蓝牙」由 onDisconnected 回调处理。 }) connectCleanup.unsubConnect = leFuService.onDeviceConnect(() => { connected = true sn = leFuService.deviceInfo?.serialNumber tryBind() }) connectCleanup.unsubInfo = leFuService.onDeviceInfo((info) => { sn = info?.serialNumber tryBind() }) }, _showBluetoothOff() { wx.showModal({ title: '蓝牙未开启', content: '请先开启手机蓝牙后重试', showCancel: false, confirmText: '知道了', success: () => this.setData({ status: 'idle' }), }) }, _showLocationOff() { wx.showModal({ title: '定位服务未开启', content: '安卓手机搜索蓝牙设备需开启定位服务,请前往系统设置开启后重试。', showCancel: false, confirmText: '知道了', success: () => this.setData({ status: 'idle' }), }) }, _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' }) }, }) }, })