424 lines
14 KiB
TypeScript
424 lines
14 KiB
TypeScript
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<IAppOption>().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
|
||
/** 进入页面时是否已连接(返回时恢复被扫描打断的连接) */
|
||
let enteredConnected = false
|
||
|
||
Page({
|
||
data: {
|
||
status: 'idle' as DeviceStatus,
|
||
deviceList: [] as DeviceItem[],
|
||
},
|
||
|
||
onLoad() {
|
||
enteredConnected = leFuService.isConnected
|
||
},
|
||
|
||
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()
|
||
// 进入前已连接、扫描打断连接后返回:恢复原连接
|
||
if (enteredConnected) {
|
||
leFuService.autoConnect()
|
||
}
|
||
},
|
||
|
||
/** 点击「开始 / 重新搜索」 */
|
||
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(err)
|
||
})
|
||
},
|
||
|
||
/** bindDevice 失败后的清理重置:断开连接、清缓存、重置页面状态 */
|
||
_resetAfterBindFail(err?: any) {
|
||
leFuService.disconnect()
|
||
wx.removeStorageSync('connectDeviceInfo')
|
||
scannedDevices = []
|
||
this.setData({ status: 'idle', deviceList: [] })
|
||
wx.showToast({ title: err?.msg || err?.message || '绑定失败,请重试', 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.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<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, 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' })
|
||
},
|
||
})
|
||
},
|
||
})
|