import { get, post, del, put } 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 delFlag?: number } /** 同步用的用户项 */ interface SyncUser { id: string userId: string name: string gender: number age: number height: number sync: string delFlag?: number } /** 展示用成员项 */ interface MemberItem { id: string userId: string name: string gender: string age: number height: number sync: string syncClass: string /** 1=本人 2=自己添加 3=他人添加 */ relationType: number | null /** 0=员工 1=家庭用户 */ dataType?: number delFlag?: number } /** 展示用分组 */ interface GroupItem { title: string members: MemberItem[] } /** 获取设备成员列表(运维模式走 ops/getDeviceUsers,普通模式走 v3/selectUserBySn/user) */ const getDeviceMembers = (sn: string, userId: string) => get( getApp().globalData.operationsEngineer ? 'weighingScale/ops/getDeviceUsers' : 'weighingScale/v3/selectUserBySn/user', { sn, userId }, ) /** 标记用户已同步到设备 */ const markSynced = (data: { sn: string; list: string[] }) => post('weighingScale/v6/markSynced', data, { loading: false }) /** 删除设备成员 */ const deleteMember = (id: string) => del('weighingScale/v6/del/user', { id }, { loading: false }) /** 添加设备成员(本人) */ const addSelfUser = (data: Record) => put('weighingScale/v6/edit/user', data) /** equipment 页传入的目标设备 */ let targetDevice: { sn: string; deviceId: string; name: string; connected: boolean; connectDeviceInfo: string } | null = null Page({ data: { device: { name: '智能体重秤', status: false, deviceId: '', sn: '' }, memberCount: 0, hasData: false, self: null as null | MemberItem, groups: [] as GroupItem[], syncShow: false, syncPercent: 0, syncStatus: '' }, onLoad(options: Record) { 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() { unsubDeviceConnect?.() unsubDisconnected?.() unsubDeviceConnect = null unsubDisconnected = null if (syncCloseTimer !== null) { clearTimeout(syncCloseTimer); syncCloseTimer = null } }, /** 订阅连接状态,实时同步设备卡(判断是否连接的目标设备) */ _subscribeDeviceState() { unsubDeviceConnect?.() unsubDisconnected?.() unsubDeviceConnect = leFuService.onDeviceConnect(() => { this.setData({ 'device.status': leFuService.lastRawDevice?.deviceId === targetDevice?.deviceId }) }) unsubDisconnected = leFuService.onDisconnected(() => { 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.delFlag === 1 ? '已删除未同步' : (item.syncStatus === 1 ? '已同步' : '未同步'), syncClass: item.delFlag === 1 ? 'deleted' : (item.syncStatus === 1 ? 'done' : 'pending'), relationType: item.relationType, dataType: item.dataType, delFlag: item.delFlag, }) 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((err: any) => { console.error('[userManagement] 加载用户列表失败:', err) wx.showToast({ title: err?.msg || err?.message || '加载用户列表失败', icon: 'none' }) }) }, /** 校验是否连接的是目标设备:不是则弹框提示连接/取消 */ _ensureConnected(): boolean { 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' }) }, }) }, /** 组装所有用户(本人第一个,作为主用户),性别:男=1 女=0 */ _buildAllUsers(): SyncUser[] { const self = this.data.self return [ ...(self ? [{ id: self.id, userId: self.userId, name: self.name, gender: self.gender === '男' ? 1 : 0, age: self.age, height: self.height, sync: self.sync, delFlag: self.delFlag, }] : []), ...this.data.groups.flatMap(g => g.members.map(m => ({ id: String(m.id), userId: m.userId, name: m.name, gender: m.gender === '男' ? 1 : 0, age: m.age, height: m.height, sync: m.sync, delFlag: m.delFlag, }))), ] }, /** 同步完成后的统一处理:上报后刷新列表 + 关闭弹框 */ _finishSync(reportUsers: SyncUser[] | null, successText: string) { this.setData({ syncPercent: 100, syncStatus: '同步完成' }) wx.showToast({ title: successText, icon: 'success' }) if (reportUsers && reportUsers.length && targetDevice?.sn) { const syncedIds = reportUsers.map(u => u.userId).filter(Boolean) if (syncedIds.length) { markSynced({ sn: targetDevice.sn, list: syncedIds }) .then(() => this._loadMembers()) .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) { wx.showToast({ title: '暂无用户可同步', icon: 'none' }) return } // 有删除标记(delFlag=1):清空设备后下发未删除的用户,上报所有用户 const hasDeleted = allUsers.some(u => u.delFlag === 1) if (hasDeleted) { const normalUsers = allUsers.filter(u => u.delFlag !== 1) this._clearAndSync(normalUsers, allUsers) return } // 无删除标记:只下发「待同步」的用户 const pendingUsers = allUsers.filter(u => u.sync !== '已同步') if (!pendingUsers.length) { wx.showToast({ title: '没有需要同步的用户', icon: 'none' }) return } // 先读设备端主用户列表,判断设备是否已有主用户 leFuService.fetchDeviceUserIds() .then((deviceUserIds) => this._syncUsers(pendingUsers, deviceUserIds)) .catch((err: any) => { console.error('[userManagement] 读取设备用户失败:', err) wx.showToast({ title: err?.message || '读取设备用户失败', 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].userId const syncDataList: SyncUserData[] = pendingUsers.map((user, index) => ({ userID: mainUserId, userName: user.name, memberID: hasMainUser ? user.userId : (user.userId === pendingUsers[0].userId ? '' : user.userId), 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._finishSync(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._clearAndSync(allUsers, null) }, /** 清除设备用户后重新下发;reportUsers 为 null 时不上报后台 */ _clearAndSync(syncUsers: SyncUser[], reportUsers: SyncUser[] | null) { const mainUser = syncUsers[0] const syncDataList: SyncUserData[] = syncUsers.map((user, index) => ({ userID: mainUser.userId, userName: user.name, memberID: user.userId === mainUser.userId ? '' : user.userId, 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.clearDeviceMembers() .then(() => leFuService.syncMembersToDevice(syncDataList, (current, total) => { this.setData({ syncPercent: Math.round((current / total) * 100), syncStatus: `正在同步 ${current}/${total}...`, }) })) .then(() => this._finishSync(reportUsers, '重新下发成功')) .catch((err: Error) => { this.setData({ syncShow: false }) wx.showToast({ title: err.message || '重新下发失败', icon: 'none' }) }) }, /** 添加用户 */ onTapAddUser() { if (this.data.memberCount >= 10) { wx.showToast({ title: '最多添加10位用户', icon: 'none' }) return } const sn = targetDevice?.sn || '' wx.navigateTo({ url: `/pages/addUser/addUser?sn=${encodeURIComponent(sn)}` }) }, /** 编辑成员:跳转 addUser 编辑模式 */ onTapEdit(e: WechatMiniprogram.TouchEvent) { const item = e.currentTarget.dataset.item as MemberItem if (item.delFlag === 1) return const sn = targetDevice?.sn || '' wx.navigateTo({ url: `/pages/addUser/addUser?sn=${encodeURIComponent(sn)}&id=${encodeURIComponent(item.id)}` }) }, /** 删除成员 */ onTapDelete(e: WechatMiniprogram.TouchEvent) { const item = e.currentTarget.dataset.item as MemberItem if (item.delFlag === 1) return wx.showModal({ title: '删除用户', content: `是否确认删除「${item.name}」?`, cancelText: '取消', confirmText: '删除', confirmColor: '#F24439', success: (res) => { if (!res.confirm) return deleteMember(item.id) .then(() => { wx.showToast({ title: '删除成功', icon: 'success' }) this._loadMembers() }) .catch((err: any) => { console.error('[userManagement] 删除成员失败:', err) wx.showToast({ title: err?.msg || err?.message || '删除失败,请重试', icon: 'none' }) }) }, }) }, /** 一键添加本人:仅调接口把当前登录用户添加为设备成员,不下发设备 */ onTapAddSelf() { if (this.data.memberCount >= 10) { wx.showToast({ title: '最多添加10位用户', icon: 'none' }) return } const userInfo = wx.getStorageSync('userInfo') as Record | null if (!userInfo?.userId) { wx.showToast({ title: '请先完善个人信息', icon: 'none' }) return } const sn = targetDevice?.sn if (!sn) return addSelfUser({ dataType: 0, parentUserId: userInfo.userId, sn, scaleDeviceId: sn, realname: userInfo.realname ?? '', idCard: userInfo.idCard || null, sex: userInfo.sex ?? 0, birthday: userInfo.birthday ?? '', height: userInfo.height ?? 0, weight: userInfo.weight ?? 0, mode: getApp().globalData.operationsEngineer ? 'ops' : 'normal', }) .then(() => { wx.showToast({ title: '添加成功', icon: 'success' }) this._loadMembers() }) .catch((err: any) => { console.error('[userManagement] 添加本人失败:', err) wx.showToast({ title: err?.msg || err?.message || '添加失败,请重试', icon: 'none' }) }) }, /** 添加新用户 */ onTapAddNew() { if (this.data.memberCount >= 10) { wx.showToast({ title: '最多添加10位用户', icon: 'none' }) return } const sn = targetDevice?.sn || '' wx.navigateTo({ url: `/pages/addUser/addUser?sn=${encodeURIComponent(sn)}` }) } })