Files
bodyWeight/miniprogram/pages/connectedDevice/connectedDevice.ts
T

346 lines
11 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 {
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<string, any> | 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
},
/** 检查系统蓝牙开关 */
_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<boolean> {
try {
const { authSetting } = await wx.getSetting()
const setting = authSetting as Record<string, boolean | undefined>
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 }
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: '知道了',
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' })
},
})
},
})