Files
bodyWeight/miniprogram/pages/connectedDevice/connectedDevice.ts
T
17792275749andClaude Opus 4.7 f53f11e6e9 feat(lefu): 迁移乐福体脂秤 SDK,接入设备扫描连接与 WiFi 配网
- 新增 lefu 服务层(扫描/连接/断开/保活重连/测量回调/WiFi 配网),事件回调支持多监听
- connectedDevice 接入真实蓝牙扫描连接,修复断线重连 UI 未同步、蓝牙/定位开关误判
- connectedWifi 接入真实 WiFi 配网,补连接状态校验与退出配网模式
- 新增 5 张设备图标并调整应用入口

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-20 15:57:16 +08:00

288 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { leFuService } from '../../lefu/index'
import type { ScannedDevice } from '../../lefu/index'
import type { RawDevice } from '../../lefu/types'
/** 渲染用的设备项(RawDevice 含复杂对象,不进 setData */
interface DeviceItem {
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._markAllDisconnected()
}
},
onHide() {
leFuService.stopScan()
},
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 })),
})
},
/** 连接成功后把对应设备卡片标记为已连接(覆盖手动连接与自动重连) */
_markConnected() {
const cached = wx.getStorageSync('connectDeviceInfo') as Record<string, any> | null
const name = leFuService.deviceInfo?.modelNumber
|| cached?.scaleDeviceName
|| cached?.name
|| cached?.deviceName
if (!name) return
this.setData({
deviceList: this.data.deviceList.map(item => ({ ...item, connected: item.name === name })),
})
},
_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<string, boolean | undefined>
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 connectedName = this.data.deviceList.find(d => d.connected)?.name
this.setData({
status: 'found',
deviceList: devices.map(d => ({ name: d.name, connected: d.name === connectedName })),
})
this._tryAutoConnect()
})
unsubDisconnected = leFuService.onDisconnected(() => {
this._markAllDisconnected()
})
unsubDeviceConnect = leFuService.onDeviceConnect(() => {
this._markConnected()
})
leFuService.startScan()
},
/** 命中缓存设备时自动连接一次 */
_tryAutoConnect() {
if (autoConnectTried) 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
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' })
}
})
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' })
},
})
},
})