Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
415 lines
16 KiB
TypeScript
415 lines
16 KiB
TypeScript
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(() => {})
|
||
},
|
||
})
|
||
}
|
||
})
|