feat(userManagement): 设备成员管理接入成员列表接口与同步标记

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
17792275749
2026-08-24 19:00:36 +08:00
co-authored by Claude Sonnet 4.6
parent f750d15150
commit a80a5cd939
6 changed files with 309 additions and 107 deletions
@@ -1,54 +1,88 @@
import { get, post } from '../../utils/request/index'
import { leFuService } from '../../lefu/index'
import type { SyncUserData } from '../../lefu/index'
import { calcAge } from '../../utils/idCard/index'
/** 连接状态订阅句柄 */
let unsubDeviceConnect: (() => void) | null = null
let unsubDisconnected: (() => void) | null = null
/** 同步完成关闭弹框定时器 */
let syncCloseTimer: number | null = null
/** selectUserBySn/user 返回的单条结构 */
interface SysUserDevice {
id: string
userId: string
realname: string
height: number
birthday: string
sex: number
dataType: number
relationType: number | null
syncStatus?: number
}
/** 展示用成员项 */
interface MemberItem {
id: string
userId: string
name: string
gender: string
age: number
height: number
sync: string
syncClass: string
}
/** 展示用分组 */
interface GroupItem {
title: string
members: MemberItem[]
}
/** 获取设备成员列表 */
const getDeviceMembers = (sn: string, userId: string) =>
get<SysUserDevice[]>('weighingScale/v3/selectUserBySn/user', { sn, userId })
/** 标记用户已同步到设备 */
const markSynced = (data: { sn: string; list: string[] }) =>
post('weighingScale/v6/markSynced', data, { loading: false })
/** equipment 页传入的目标设备 */
let targetDevice: { sn: string; deviceId: string; name: string; connected: boolean; connectDeviceInfo: string } | null = null
Page({
data: {
device: {
name: '智能体重秤',
connected: false,
model: '',
serial: ''
status: false,
deviceId: '',
sn: ''
},
memberCount: 5,
hasData: true,
self: {
name: '张晓敏',
gender: '男',
age: 36,
height: 172,
sync: '已同步',
syncClass: 'done'
},
groups: [
{
title: '员工',
members: [
{ id: 2, name: '李建国', gender: '男', age: 36, height: 182, sync: '已同步', syncClass: 'done' },
{ id: 5, name: '赵明亮', gender: '男', age: 32, height: 172, sync: '已同步', syncClass: 'done' }
]
},
{
title: '家庭用户',
members: [
{ id: 3, name: '吉夏', gender: '女', age: 28, height: 165, sync: '已同步', syncClass: 'done' },
{ id: 4, name: '张雪', gender: '女', age: 32, height: 158, sync: '未同步', syncClass: 'pending' }
]
}
],
memberCount: 0,
hasData: false,
self: null as null | MemberItem,
groups: [] as GroupItem[],
syncShow: false,
syncPercent: 0,
syncStatus: ''
},
onLoad() {
this._updateDeviceInfo()
onLoad(options: Record<string, string>) {
targetDevice = {
sn: decodeURIComponent(options.sn || ''),
deviceId: decodeURIComponent(options.deviceId || ''),
name: decodeURIComponent(options.name || ''),
connected: options.status === 'true',
connectDeviceInfo: decodeURIComponent(options.connectDeviceInfo || ''),
}
this.setData({
'device.name': targetDevice.name || '智能体重秤',
'device.deviceId': targetDevice.deviceId,
'device.sn': targetDevice.sn,
'device.status': targetDevice.connected,
})
this._subscribeDeviceState()
this._loadMembers()
},
onUnload() {
@@ -59,59 +93,150 @@ Page({
if (syncCloseTimer !== null) { clearTimeout(syncCloseTimer); syncCloseTimer = null }
},
/** 从 service 读取设备信息 + 连接状态 */
_updateDeviceInfo() {
const info = leFuService.deviceInfo
this.setData({
'device.name': info?.modelNumber || '智能体重秤',
'device.model': info?.modelNumber || '',
'device.serial': info?.serialNumber || '',
'device.connected': leFuService.isConnected,
})
},
/** 订阅连接状态,实时同步设备卡 */
/** 订阅连接状态,实时同步设备卡(判断是否连接的目标设备) */
_subscribeDeviceState() {
unsubDeviceConnect?.()
unsubDisconnected?.()
unsubDeviceConnect = leFuService.onDeviceConnect(() => {
this._updateDeviceInfo()
this.setData({ 'device.status': leFuService.lastRawDevice?.deviceId === targetDevice?.deviceId })
})
unsubDisconnected = leFuService.onDisconnected(() => {
this.setData({ 'device.connected': false })
this.setData({ 'device.status': false })
})
},
/** 校验设备连接状态:未连接则同步状态 + 提示 */
/** 拉取设备成员列表,映射到本人 + 员工/家庭用户分组 */
_loadMembers() {
if (!targetDevice?.sn) return
const userInfo = wx.getStorageSync('userInfo') as { userId?: string } | null
const userId = userInfo?.userId ?? ''
if (!userId) return
getDeviceMembers(targetDevice.sn, userId)
.then(res => {
const list = res.result
const toMember = (item: SysUserDevice): MemberItem => ({
id: item.id,
userId: item.userId,
name: item.realname,
gender: item.sex === 1 ? '男' : '女',
age: calcAge(item.birthday),
height: item.height,
sync: item.syncStatus === 1 ? '已同步' : '待同步',
syncClass: item.syncStatus === 1 ? 'done' : 'pending',
})
const self = list.find(m => m.relationType === 1)
this.setData({
self: self ? toMember(self) : null,
groups: [
{ title: '员工', members: list.filter(m => m.relationType !== 1 && m.dataType === 0).map(toMember) },
{ title: '家庭用户', members: list.filter(m => m.relationType !== 1 && m.dataType === 1).map(toMember) },
].filter(g => g.members.length),
memberCount: list.length,
hasData: list.length > 0,
})
})
.catch(() => {
wx.showToast({ title: '加载用户列表失败', icon: 'none' })
})
},
/** 校验是否连接的是目标设备:不是则弹框提示连接/取消 */
_ensureConnected(): boolean {
if (leFuService.isConnected) return true
this.setData({ 'device.connected': false })
wx.showToast({ title: '请先连接设备', icon: 'none' })
const connected = leFuService.isConnected && leFuService.lastRawDevice?.deviceId === targetDevice?.deviceId
if (connected) return true
this.setData({ 'device.status': false })
wx.showModal({
title: '未连接设备',
content: '请先连接该设备后再同步用户',
cancelText: '取消同步',
confirmText: '连接设备',
success: (res) => {
if (res.confirm) {
this._connectTargetDevice()
}
},
})
return false
},
/** 同步用户至设备 */
/** 本页面扫描并连接目标设备 */
_connectTargetDevice() {
const targetDeviceId = targetDevice?.deviceId
if (!targetDeviceId) return
if (leFuService.isConnected) leFuService.disconnect()
wx.showLoading({ title: '连接中...', mask: true })
wx.openBluetoothAdapter({
success: () => {
let unsubList: (() => void) | null = null
let unsubConnect: (() => void) | null = null
const timer = setTimeout(() => {
unsubList?.()
unsubList = null
wx.hideLoading()
wx.showToast({ title: '未找到设备', icon: 'none' })
}, 15000)
unsubList = leFuService.onDevicesList((devices) => {
const matched = devices.find(d => d.raw.deviceId === targetDeviceId)
if (!matched) return
clearTimeout(timer)
unsubList?.()
unsubList = null
leFuService.stopScan()
leFuService.connect(matched.raw)
unsubConnect = leFuService.onDeviceConnect(() => {
unsubConnect?.()
unsubConnect = null
wx.hideLoading()
wx.setStorageSync('connectDeviceInfo', matched.raw)
this.setData({ 'device.status': true })
this.onTapSync()
})
})
leFuService.startScan()
},
fail: () => {
wx.hideLoading()
wx.showToast({ title: '蓝牙未开启', icon: 'none' })
},
})
},
/** 同步用户至设备:只下发「待同步」的用户 */
onTapSync() {
if (!this._ensureConnected()) return
// 组装所有用户(self 第一个,作为主用户),性别:男=1 女=0
// 组装所有用户(本人第一个,作为主用户),性别:男=1 女=0
const self = this.data.self
const allUsers = [
{
id: 'self', name: this.data.self.name,
gender: this.data.self.gender === '男' ? 1 : 0,
age: this.data.self.age, height: this.data.self.height,
},
...(self ? [{
id: self.id, userId: self.userId, name: self.name,
gender: self.gender === '男' ? 1 : 0,
age: self.age, height: self.height, sync: self.sync,
}] : []),
...this.data.groups.flatMap(g => g.members.map(m => ({
id: String(m.id), name: m.name,
gender: m.gender === '男' ? 1 : 0, age: m.age, height: m.height,
id: String(m.id), userId: m.userId, name: m.name,
gender: m.gender === '男' ? 1 : 0, age: m.age, height: m.height, sync: m.sync,
}))),
]
if (!allUsers.length) {
wx.showToast({ title: '暂无用户可同步', icon: 'none' })
return
}
const mainUser = allUsers[0]
// 组装下发参数(SyncUserData),第一个为主用户(memberID 为空)
const syncDataList: SyncUserData[] = allUsers.map((user, index) => ({
// 只下发「待同步」的用户
const pendingUsers = allUsers.filter(u => u.sync !== '已同步')
if (!pendingUsers.length) {
wx.showToast({ title: '没有需要同步的用户', icon: 'none' })
return
}
// 组装下发参数(SyncUserData),主用户 memberID 为空
const syncDataList: SyncUserData[] = pendingUsers.map((user, index) => ({
userID: mainUser.id,
userName: user.name,
memberID: index === 0 ? '' : user.id,
memberID: user.id === mainUser.id ? '' : user.id,
age: user.age,
gender: user.gender,
height: user.height,
@@ -123,23 +248,35 @@ Page({
recentData: [],
}))
// 阶段1:清空设备用户,进度 0% → 100%
this.setData({ syncShow: true, syncPercent: 0, syncStatus: '正在清空设备用户...' })
leFuService.clearDeviceMembers()
.then(() => {
this.setData({ syncPercent: 100, syncStatus: '清空完成' })
// 阶段2:下发用户,进度从 0% 重新开始
this.setData({ syncPercent: 0, syncStatus: '正在同步信息,请勿离开...' })
return leFuService.syncMembersToDevice(syncDataList, (current, total) => {
this.setData({
syncPercent: Math.round((current / total) * 100),
syncStatus: `正在同步 ${current}/${total}...`,
})
})
// 直接下发待同步用户(不清空)
this.setData({ syncShow: true, syncPercent: 0, syncStatus: '正在同步信息,请勿离开...' })
leFuService.syncMembersToDevice(syncDataList, (current, total) => {
this.setData({
syncPercent: Math.round((current / total) * 100),
syncStatus: `正在同步 ${current}/${total}...`,
})
})
.then(() => {
this.setData({ syncPercent: 100, syncStatus: '同步完成' })
wx.showToast({ title: '用户同步成功', icon: 'success' })
const syncedIds = pendingUsers.map(u => u.userId).filter(Boolean)
if (targetDevice?.sn && syncedIds.length) {
markSynced({ sn: targetDevice.sn, list: syncedIds })
.then(() => {
// 标记成功后,把对应成员更新为「已同步」
const syncedSet = new Set(syncedIds)
this.setData({
self: this.data.self && syncedSet.has(this.data.self.userId)
? { ...this.data.self, sync: '已同步', syncClass: 'done' }
: this.data.self,
groups: this.data.groups.map(g => ({
...g,
members: g.members.map(m => syncedSet.has(m.userId) ? { ...m, sync: '已同步', syncClass: 'done' } : m),
})),
})
})
.catch(() => {})
}
if (syncCloseTimer !== null) { clearTimeout(syncCloseTimer); syncCloseTimer = null }
syncCloseTimer = setTimeout(() => {
syncCloseTimer = null