Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
469 lines
18 KiB
TypeScript
469 lines
18 KiB
TypeScript
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
|
||
delFlag?: number
|
||
}
|
||
|
||
/** 展示用分组 */
|
||
interface GroupItem {
|
||
title: string
|
||
members: MemberItem[]
|
||
}
|
||
|
||
/** 获取设备成员列表(运维模式走 ops/getDeviceUsers,普通模式走 v3/selectUserBySn/user) */
|
||
const getDeviceMembers = (sn: string, userId: string) =>
|
||
get<SysUserDevice[]>(
|
||
getApp<IAppOption>().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<string, any>) =>
|
||
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<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() {
|
||
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,
|
||
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
|
||
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
|
||
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<string, any> | 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<IAppOption>().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() {
|
||
wx.navigateTo({ url: '/pages/addUser/addUser' })
|
||
}
|
||
})
|