diff --git a/miniprogram/pages/equipment/equipment.less b/miniprogram/pages/equipment/equipment.less
index c4e4a1f..7dabe06 100644
--- a/miniprogram/pages/equipment/equipment.less
+++ b/miniprogram/pages/equipment/equipment.less
@@ -226,6 +226,9 @@
height: 26rpx;
font-size: 26rpx;
line-height: 26rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
}
}
diff --git a/miniprogram/pages/equipment/equipment.ts b/miniprogram/pages/equipment/equipment.ts
index 9476aa0..2840239 100644
--- a/miniprogram/pages/equipment/equipment.ts
+++ b/miniprogram/pages/equipment/equipment.ts
@@ -33,6 +33,7 @@ interface DeviceItem {
let unsubDevicesList: (() => void) | null = null
let unsubConnect: (() => void) | null = null
+let unsubDisconnected: (() => void) | null = null
let scanTimer: number | null = null
Page({
@@ -45,6 +46,34 @@ Page({
onShow() {
this.loadDevices()
+ this._subscribeDisconnected()
+ },
+
+ onHide() {
+ leFuService.stopScan()
+ unsubDisconnected?.()
+ unsubDisconnected = null
+ },
+
+ onUnload() {
+ leFuService.stopScan()
+ unsubDisconnected?.()
+ unsubDisconnected = null
+ unsubDevicesList?.()
+ unsubDevicesList = null
+ unsubConnect?.()
+ unsubConnect = null
+ if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null }
+ },
+
+ /** 订阅断开事件:设备长时间未操作或意外断开时,同步页面状态为未连接 */
+ _subscribeDisconnected() {
+ unsubDisconnected?.()
+ unsubDisconnected = leFuService.onDisconnected(() => {
+ this.setData({
+ devices: this.data.devices.map(d => ({ ...d, status: false })),
+ })
+ })
},
/** 拉取体重设备列表 */
@@ -182,8 +211,11 @@ Page({
},
/** 用户管理 */
- onTapUser() {
- wx.navigateTo({ url: '/pages/userManagement/userManagement' })
+ 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)}`,
+ })
},
onTapAdd() {
diff --git a/miniprogram/pages/equipment/equipment.wxml b/miniprogram/pages/equipment/equipment.wxml
index 56d93d4..edf6c27 100644
--- a/miniprogram/pages/equipment/equipment.wxml
+++ b/miniprogram/pages/equipment/equipment.wxml
@@ -78,7 +78,7 @@
设备连接
-
+
diff --git a/miniprogram/pages/userManagement/userManagement.less b/miniprogram/pages/userManagement/userManagement.less
index cdbb72a..8dc5b7b 100644
--- a/miniprogram/pages/userManagement/userManagement.less
+++ b/miniprogram/pages/userManagement/userManagement.less
@@ -13,7 +13,7 @@
.device-card {
width: 100%;
- padding: 30rpx;
+ padding: 30rpx 24rpx;
display: flex;
border-radius: 24rpx;
background-color: #FFFFFF;
@@ -24,9 +24,8 @@
width: 88rpx;
height: 88rpx;
overflow: hidden;
- margin-right: 24rpx;
+ margin-right: 20rpx;
border-radius: 24rpx;
- background-color: #1385FA;
}
.device-info {
@@ -49,35 +48,57 @@
}
.device-status {
- height: 40rpx;
display: flex;
- padding: 0 16rpx;
- border-radius: 20rpx;
- align-items: center;
- background-color: #EAFAF6;
+ flex-wrap: nowrap;
- .status-dot {
- width: 12rpx;
- height: 12rpx;
- border-radius: 50%;
- background-color: #2AC79F;
- margin-right: 8rpx;
+ .device-status_ {
+ height: 40rpx;
+ padding: 0 16rpx;
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ border-radius: 20rpx;
+ overflow: hidden;
+ background-color: #F2F3F6;
+
+ .status-dot {
+ width: 12rpx;
+ height: 12rpx;
+ border-radius: 50%;
+ background-color: #77849E;
+ margin-right: 8rpx;
+ }
+
+ .status-text {
+ color: #77849E;
+ height: 22rpx;
+ font-size: 22rpx;
+ line-height: 22rpx;
+ }
}
- .status-text {
- color: #2AC79F;
- height: 22rpx;
- font-size: 22rpx;
- line-height: 22rpx;
+ .connected {
+ background-color: #EAFAF6 !important;
+
+ .status-dot {
+ background-color: #2AC79F !important;
+ }
+
+ .status-text {
+ color: #2AC79F !important;
+ }
}
}
}
.device-meta {
color: #808080;
- height: 26rpx;
- font-size: 26rpx;
- line-height: 26rpx;
+ height: 24rpx;
+ font-size: 24rpx;
+ line-height: 24rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
}
}
@@ -176,6 +197,11 @@
overflow: hidden;
border-radius: 24rpx;
margin-right: 24rpx;
+ color: #252535;
+ font-size: 40rpx;
+ font-weight: bold;
+ line-height: 88rpx;
+ text-align: center;
&.member-avatar--male {
background-color: #C6E6FF;
diff --git a/miniprogram/pages/userManagement/userManagement.ts b/miniprogram/pages/userManagement/userManagement.ts
index f4d0544..dde7a89 100644
--- a/miniprogram/pages/userManagement/userManagement.ts
+++ b/miniprogram/pages/userManagement/userManagement.ts
@@ -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('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) {
+ 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
diff --git a/miniprogram/pages/userManagement/userManagement.wxml b/miniprogram/pages/userManagement/userManagement.wxml
index f685855..5d75224 100644
--- a/miniprogram/pages/userManagement/userManagement.wxml
+++ b/miniprogram/pages/userManagement/userManagement.wxml
@@ -3,17 +3,21 @@