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

415 lines
16 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 { get, del, post } from '../../utils/request/index'
import { leFuService } from '../../lefu/index'
import type { UserInfo } from '../../api/index'
/** 体重设备(对应 /weighingScale/list/device 返回的 result 元素) */
interface ScaleDevice {
/** 记录 ID */
id: string
/** 设备 SN */
scaleDeviceId: string
/** 连接设备信息(JSON 字符串,含 name、deviceId */
connectDeviceInfo: string
/** 用户数量 */
userNum: number
/** 状态 */
status: false
}
/** 获取体重设备列表(运维模式走 ops/listDevice,普通模式走 v6/list/device */
const getDeviceList = () =>
get<ScaleDevice[]>(
getApp<IAppOption>().globalData.operationsEngineer
? 'weighingScale/ops/listDevice'
: 'weighingScale/v6/list/device',
)
/** 取消配对:id 不传则解除所有设备绑定(运维模式走 ops/deleteUser,普通模式走 v6/unbind */
const unbindDevice = (id?: string) => {
const url = getApp<IAppOption>().globalData.operationsEngineer
? 'weighingScale/ops/deleteUser'
: 'weighingScale/v6/unbind'
return del(`${url}${id ? `?id=${id}` : ''}`, {}, { loading: false })
}
/** 小程序蓝牙补传体重数据 */
const uploadHistoryData = (data: Record<string, any>) =>
post('weighingScale/v6/bluetoothUpload', data, { loading: false })
/** SN 转 MAC:每两位加一个冒号(如 CEE9052031AC → CE:E9:05:20:31:AC */
const formatMac = (sn: string) => sn.match(/.{2}/g)?.join(':') ?? ''
/** 展示用设备项 */
interface DeviceItem {
id: string
name: string
status: boolean
deviceId: string
sn: string
userNum: number
connectDeviceInfo: string
}
let unsubDevicesList: (() => void) | null = null
let unsubConnect: (() => void) | null = null
let unsubDisconnected: (() => void) | null = null
let unsubDeviceConnect: (() => void) | null = null
let unsubConnectState: (() => void) | null = null
let scanTimer: number | null = null
let connectTimer: number | null = null
Page({
data: {
operationsEngineer: false,
devices: [] as DeviceItem[],
loadingShow: false,
loadingTitle: '',
loadingContent: '请稍等...'
},
onShow() {
this.setData({ operationsEngineer: getApp<IAppOption>().globalData.operationsEngineer ?? false })
this.loadDevices()
this._subscribeDisconnected()
this._subscribeDeviceConnect()
},
onHide() {
leFuService.stopScan()
unsubDisconnected?.()
unsubDisconnected = null
unsubDeviceConnect?.()
unsubDeviceConnect = null
},
onUnload() {
leFuService.stopScan()
unsubDisconnected?.()
unsubDisconnected = null
unsubDeviceConnect?.()
unsubDeviceConnect = null
unsubDevicesList?.()
unsubDevicesList = null
unsubConnect?.()
unsubConnect = null
unsubConnectState?.()
unsubConnectState = null
if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null }
if (connectTimer !== null) { clearTimeout(connectTimer); connectTimer = null }
},
/** 订阅断开事件:设备长时间未操作或意外断开时,同步页面状态为未连接 */
_subscribeDisconnected() {
unsubDisconnected?.()
unsubDisconnected = leFuService.onDisconnected(() => {
this.setData({
devices: this.data.devices.map(d => ({ ...d, status: false })),
})
})
},
/** 订阅连接成功事件:回前台自动重连等场景下,同步页面状态为已连接 */
_subscribeDeviceConnect() {
unsubDeviceConnect?.()
unsubDeviceConnect = leFuService.onDeviceConnect(() => {
const deviceId = leFuService.lastRawDevice?.deviceId ?? ''
if (deviceId) {
this._updateStatus(deviceId, true)
}
})
},
/** 拉取体重设备列表 */
async loadDevices() {
const raw = wx.getStorageSync('userInfo') as UserInfo | null
if (!raw?.userId) return
try {
const res = await getDeviceList()
this.setData({
devices: res.result.map(item => {
let info: Record<string, any> = {}
try {
info = JSON.parse(item.connectDeviceInfo || '{}')
} catch {}
return {
id: item.id,
name: info.name ?? '',
status: leFuService.isConnected && leFuService.lastRawDevice?.deviceId === info.deviceId,
deviceId: info.deviceId ?? '',
sn: item.scaleDeviceId,
userNum: item.userNum ?? 0,
connectDeviceInfo: item.connectDeviceInfo ?? '',
}
}),
})
} catch {
// 请求失败已由 request 层统一 toast
}
},
/** 取消配对:解除指定设备绑定 */
onTapUnpair(e: WechatMiniprogram.TouchEvent) {
const item = e.currentTarget.dataset.item as DeviceItem
wx.showModal({
title: '取消配对',
content: '是否确认取消配对当前设备?',
cancelText: '取消',
confirmText: '确定',
confirmColor: '#F24439',
success: (res) => {
if (!res.confirm) return
unbindDevice(item.id)
.then(() => {
// 取消配对的是当前连接的设备:断开连接并清缓存
if (leFuService.isConnected && leFuService.lastRawDevice?.deviceId === item.deviceId) {
leFuService.disconnect()
}
const cached = wx.getStorageSync('connectDeviceInfo') as Record<string, any> | null
if (cached?.deviceId === item.deviceId) {
wx.removeStorageSync('connectDeviceInfo')
}
wx.showToast({ title: '已取消配对', icon: 'success' })
this.loadDevices()
})
.catch(() => {})
},
})
},
/** 点击设备状态:已连接则断开,未连接则扫描连接 */
onTapStatus(e: WechatMiniprogram.TouchEvent) {
const item = e.currentTarget.dataset.item as DeviceItem
if (item.status) {
leFuService.disconnect()
this._updateStatus(item.deviceId, false)
return
}
this._connectDevice(item)
},
/** 扫描蓝牙,找到 deviceId 匹配的设备后连接 */
_connectDevice(item: DeviceItem) {
const targetDeviceId = item.deviceId
if (!targetDeviceId) return
if (leFuService.isConnected) {
leFuService.disconnect()
// 断开后全部置未连接,等新设备连上再标回
this.setData({
devices: this.data.devices.map(d => ({ ...d, status: false })),
})
}
// 清理上一次连接流程的订阅与定时器,避免重复订阅累积
unsubDevicesList?.()
unsubDevicesList = null
unsubConnect?.()
unsubConnect = null
unsubConnectState?.()
unsubConnectState = null
if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null }
if (connectTimer !== null) { clearTimeout(connectTimer); connectTimer = null }
wx.showLoading({ title: '连接中...', mask: true })
wx.openBluetoothAdapter({
success: () => {
scanTimer = setTimeout(() => {
unsubDevicesList?.()
unsubDevicesList = null
wx.hideLoading()
wx.showToast({ title: '未找到设备', icon: 'none' })
}, 15000)
unsubDevicesList = leFuService.onDevicesList((devices) => {
const matched = devices.find(d => d.raw.deviceId === targetDeviceId)
if (!matched) return
if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null }
unsubDevicesList?.()
unsubDevicesList = null
leFuService.stopScan()
// 连接超时兜底:15 秒未连上则结束 loading
connectTimer = setTimeout(() => {
connectTimer = null
unsubConnect?.()
unsubConnect = null
unsubConnectState?.()
unsubConnectState = null
wx.hideLoading()
wx.showToast({ title: '连接失败,请重试', icon: 'none' })
}, 15000)
// 连接失败:立即结束 loading
unsubConnectState = leFuService.onConnectState((state) => {
if (state !== leFuService.BLUE_STATE.CONNECTFAILED) return
if (connectTimer !== null) { clearTimeout(connectTimer); connectTimer = null }
unsubConnectState?.()
unsubConnectState = null
unsubConnect?.()
unsubConnect = null
wx.hideLoading()
wx.showToast({ title: '连接失败,请重试', icon: 'none' })
})
leFuService.connect(matched.raw)
unsubConnect = leFuService.onDeviceConnect(() => {
if (connectTimer !== null) { clearTimeout(connectTimer); connectTimer = null }
unsubConnect?.()
unsubConnect = null
unsubConnectState?.()
unsubConnectState = null
wx.hideLoading()
wx.setStorageSync('connectDeviceInfo', matched.raw)
this._updateStatus(targetDeviceId, true)
})
})
leFuService.startScan()
},
fail: () => {
wx.hideLoading()
wx.showToast({ title: '蓝牙未开启', icon: 'none' })
},
})
},
_updateStatus(deviceId: string, status: boolean) {
this.setData({
devices: this.data.devices.map(item =>
item.deviceId === deviceId ? { ...item, status } : item
),
})
},
onCloseLoading() {
this.setData({ loadingShow: false })
},
/** 用户管理 */
onTapUser(e: WechatMiniprogram.TouchEvent) {
const item = e.currentTarget.dataset.item as DeviceItem
wx.navigateTo({
url: `/pages/userManagement/userManagement?sn=${encodeURIComponent(item.sn)}&deviceId=${encodeURIComponent(item.deviceId)}&name=${encodeURIComponent(item.name)}&status=${item.status}&connectDeviceInfo=${encodeURIComponent(item.connectDeviceInfo)}`,
})
},
/** 跳转设备 WiFi 配网页 */
onTapWifi() {
wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' })
},
/** 数据补传:读取设备端用户,有用户则拉取历史数据并上传 */
onTapDataRetransfer() {
this.setData({ loadingShow: true, loadingTitle: '正在获取设备用户' })
leFuService.fetchDeviceUserIds()
.then((list) => {
if (!list || !list.length) {
this.setData({ loadingShow: false })
wx.showToast({ title: '没有用户', icon: 'none' })
return
}
const userId = list[0]
this.setData({ loadingTitle: '正在读取设备本地未上传的数据' })
leFuService.fetchHistoryData(userId, (dataList) => {
this.setData({ loadingTitle: '正在提交数据' })
this._uploadHistory(userId, dataList, () => {
this.setData({ loadingShow: false })
wx.showToast({ title: '数据补传完成', icon: 'success' })
})
})
})
.catch(() => {
this.setData({ loadingShow: false })
wx.showToast({ title: '请先连接设备', icon: 'none' })
})
},
/** 处理历史数据并上传到后台 */
_uploadHistory(mainUserId: string, dataList: any[], done?: () => void) {
if (!dataList || !dataList.length) {
done?.()
return
}
const deviceInfo = leFuService.deviceInfo
const sn = deviceInfo?.serialNumber ?? ''
const type = deviceInfo?.modelNumber ?? ''
const mac = formatMac(sn)
const bat = deviceInfo?.devicePower != null ? String(deviceInfo.devicePower / 100) : ''
const list = dataList.map((data) => ({
sn,
type,
mac,
bat,
userid: data.memberId || mainUserId,
dateStr: data.dateStr ?? '',
weight: (data.weight ?? 0) / 100,
heartRate: data.heartRate ?? 0,
impedance: data.impedance ?? 0,
// memberId: data.memberId ?? '',
isEnd: data.isEnd ?? false,
z20KhzLeftArmEnCode: data.z20KhzLeftArmEnCode ?? 0,
z20KhzRightArmEnCode: data.z20KhzRightArmEnCode ?? 0,
z20KhzLeftLegEnCode: data.z20KhzLeftLegEnCode ?? 0,
z20KhzRightLegEnCode: data.z20KhzRightLegEnCode ?? 0,
z20KhzTrunkEnCode: data.z20KhzTrunkEnCode ?? 0,
z100KhzLeftArmEnCode: data.z100KhzLeftArmEnCode ?? 0,
z100KhzRightArmEnCode: data.z100KhzRightArmEnCode ?? 0,
z100KhzLeftLegEnCode: data.z100KhzLeftLegEnCode ?? 0,
z100KhzRightLegEnCode: data.z100KhzRightLegEnCode ?? 0,
z100KhzTrunkEnCode: data.z100KhzTrunkEnCode ?? 0,
}))
uploadHistoryData({ list })
.then(() => {
console.log('[数据补传] 上传成功')
done?.()
})
.catch(() => {
console.error('[数据补传] 上传失败')
done?.()
})
},
onTapAdd() {
wx.navigateTo({ url: '/pages/connectedDevice/connectedDevice' })
},
/** 长按 banner 打开运维模式 */
onLongPressBanner() {
getApp<IAppOption>().globalData.operationsEngineer = true
this.setData({ operationsEngineer: true })
this.loadDevices()
},
/** 关闭运维模式 */
onCloseOps() {
getApp<IAppOption>().globalData.operationsEngineer = false
this.setData({ operationsEngineer: false })
this.loadDevices()
},
/** 取消全部配对:解除所有设备绑定 */
onUnpairAll() {
wx.showModal({
title: '取消全部配对',
content: '是否确认取消全部设备的配对?',
cancelText: '取消',
confirmText: '确定',
confirmColor: '#F24439',
success: (res) => {
if (!res.confirm) return
unbindDevice()
.then(() => {
// 取消全部配对:断开当前连接并清缓存
if (leFuService.isConnected) {
leFuService.disconnect()
}
wx.removeStorageSync('connectDeviceInfo')
wx.showToast({ title: '已取消全部配对', icon: 'success' })
this.loadDevices()
})
.catch(() => {})
},
})
}
})