feat(userManagement): 同步前读设备主用户并支持长按清空重新下发

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
17792275749
2026-08-25 16:11:55 +08:00
co-authored by Claude Opus 4.7
parent f6d6591c69
commit 2595080d9a
3 changed files with 134 additions and 45 deletions
+14
View File
@@ -443,6 +443,20 @@ class LeFuService {
}) })
} }
/**
* 获取设备端主用户的 userId 列表
* 未连接 reject;成功返回主用户 userId 数组,无主用户返回 null
*/
fetchDeviceUserIds(): Promise<string[] | null> {
return new Promise((resolve, reject) => {
if (!this._activeProtocol) { reject(new Error('设备未连接')); return }
this._activeProtocol.dataFetchUserID((res: any) => {
console.log(TAG, 'dataFetchUserID:', res)
resolve(res ?? null)
})
})
}
// ─── 私有:保活与重连 ─────────────────────── // ─── 私有:保活与重连 ───────────────────────
/** 启动保活心跳:文档建议每 10s 发送一次 keepAlive,防止设备主动断开 */ /** 启动保活心跳:文档建议每 10s 发送一次 keepAlive,防止设备主动断开 */
private _startKeepAlive(): void { private _startKeepAlive(): void {
@@ -21,6 +21,17 @@ interface SysUserDevice {
syncStatus?: number syncStatus?: number
} }
/** 同步用的用户项 */
interface SyncUser {
id: string
userId: string
name: string
gender: number
age: number
height: number
sync: string
}
/** 展示用成员项 */ /** 展示用成员项 */
interface MemberItem { interface MemberItem {
id: string id: string
@@ -204,13 +215,10 @@ Page({
}) })
}, },
/** 同步用户至设备:只下发「待同步」的用户 */ /** 组装所有用户(本人第一个,作为主用户),性别:男=1 女=0 */
onTapSync() { _buildAllUsers(): SyncUser[] {
if (!this._ensureConnected()) return
// 组装所有用户(本人第一个,作为主用户),性别:男=1 女=0
const self = this.data.self const self = this.data.self
const allUsers = [ return [
...(self ? [{ ...(self ? [{
id: self.id, userId: self.userId, name: self.name, id: self.id, userId: self.userId, name: self.name,
gender: self.gender === '男' ? 1 : 0, gender: self.gender === '男' ? 1 : 0,
@@ -221,19 +229,112 @@ Page({
gender: m.gender === '男' ? 1 : 0, age: m.age, height: m.height, sync: m.sync, gender: m.gender === '男' ? 1 : 0, age: m.age, height: m.height, sync: m.sync,
}))), }))),
] ]
},
/** 同步完成后的统一处理:标记同步 + 更新列表状态 + 关闭弹框 */
_handleSyncSuccess(users: SyncUser[], successText: string) {
this.setData({ syncPercent: 100, syncStatus: '同步完成' })
wx.showToast({ title: successText, icon: 'success' })
const syncedIds = users.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
this.setData({ syncShow: false })
}, 1000)
},
/** 同步用户至设备:只下发「待同步」的用户 */
onTapSync() {
if (!this._ensureConnected()) return
const allUsers = this._buildAllUsers()
if (!allUsers.length) { if (!allUsers.length) {
wx.showToast({ title: '暂无用户可同步', icon: 'none' }) wx.showToast({ title: '暂无用户可同步', icon: 'none' })
return return
} }
const mainUser = allUsers[0]
// 只下发「待同步」的用户 // 只下发「待同步」的用户
const pendingUsers = allUsers.filter(u => u.sync !== '已同步') const pendingUsers = allUsers.filter(u => u.sync !== '已同步')
if (!pendingUsers.length) { if (!pendingUsers.length) {
wx.showToast({ title: '没有需要同步的用户', icon: 'none' }) wx.showToast({ title: '没有需要同步的用户', icon: 'none' })
return return
} }
// 组装下发参数(SyncUserData),主用户 memberID 为空
// 先读设备端主用户列表,判断设备是否已有主用户
leFuService.fetchDeviceUserIds()
.then((deviceUserIds) => this._syncUsers(pendingUsers, deviceUserIds))
.catch(() => {
wx.showToast({ title: '读取设备用户失败', icon: 'none' })
})
},
/** 根据设备端主用户情况组装并下发(一主九子,子用户挂在主用户下) */
_syncUsers(pendingUsers: SyncUser[], deviceUserIds: any) {
// dataFetchUserID 只返回主用户,非空表示设备已有主用户
const userIds = Array.isArray(deviceUserIds) ? deviceUserIds : [deviceUserIds].filter(Boolean)
const hasMainUser = userIds.length > 0
// 主用户 ID:已有主用户用设备端的,否则用第一个用户作为主用户
const mainUserId = hasMainUser ? String(userIds[0]) : pendingUsers[0].id
const syncDataList: SyncUserData[] = pendingUsers.map((user, index) => ({ const syncDataList: SyncUserData[] = pendingUsers.map((user, index) => ({
userID: mainUserId,
userName: user.name,
memberID: hasMainUser ? user.id : (user.id === pendingUsers[0].id ? '' : user.id),
age: user.age,
gender: user.gender,
height: user.height,
isAthleteMode: 0,
deviceHeaderIndex: index,
currentWeight: '',
targetWeight: '',
idealWeight: '',
recentData: [],
}))
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._handleSyncSuccess(pendingUsers, '用户同步成功'))
.catch((err: Error) => {
this.setData({ syncShow: false })
wx.showToast({ title: err.message || '同步失败,请重试', icon: 'none' })
})
},
/** 长按标题:清除设备用户信息并全部重新下发 */
onLongPressSectionTitle() {
if (!this._ensureConnected()) return
const allUsers = this._buildAllUsers()
if (!allUsers.length) {
wx.showToast({ title: '暂无用户可下发', icon: 'none' })
return
}
this._resendAllUsers(allUsers)
},
/** 清除设备用户后全部重新下发 */
_resendAllUsers(allUsers: SyncUser[]) {
const mainUser = allUsers[0]
const syncDataList: SyncUserData[] = allUsers.map((user, index) => ({
userID: mainUser.id, userID: mainUser.id,
userName: user.name, userName: user.name,
memberID: user.id === mainUser.id ? '' : user.id, memberID: user.id === mainUser.id ? '' : user.id,
@@ -248,44 +349,18 @@ Page({
recentData: [], recentData: [],
})) }))
// 直接下发待同步用户(不清空) this.setData({ syncShow: true, syncPercent: 0, syncStatus: '正在清空并重新下发...' })
this.setData({ syncShow: true, syncPercent: 0, syncStatus: '正在同步信息,请勿离开...' }) leFuService.clearDeviceMembers()
leFuService.syncMembersToDevice(syncDataList, (current, total) => { .then(() => leFuService.syncMembersToDevice(syncDataList, (current, total) => {
this.setData({ this.setData({
syncPercent: Math.round((current / total) * 100), syncPercent: Math.round((current / total) * 100),
syncStatus: `正在同步 ${current}/${total}...`, syncStatus: `正在同步 ${current}/${total}...`,
}) })
}) }))
.then(() => { .then(() => this._handleSyncSuccess(allUsers, '重新下发成功'))
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
this.setData({ syncShow: false })
}, 1000)
})
.catch((err: Error) => { .catch((err: Error) => {
this.setData({ syncShow: false }) this.setData({ syncShow: false })
wx.showToast({ title: err.message || '同步失败,请重试', icon: 'none' }) wx.showToast({ title: err.message || '重新下发失败', icon: 'none' })
}) })
}, },
@@ -23,7 +23,7 @@
<!-- 已录入用户 + 同步按钮 --> <!-- 已录入用户 + 同步按钮 -->
<view class="section-header"> <view class="section-header">
<view class="section-title">已录入用户({{ memberCount }}/10</view> <view class="section-title" bind:longpress="onLongPressSectionTitle">已录入用户({{ memberCount }}/10</view>
<view class="sync-btn" bind:tap="onTapSync">同步用户至设备</view> <view class="sync-btn" bind:tap="onTapSync">同步用户至设备</view>
</view> </view>