From e0573857e02ee779151cd8b8be1b2c8ee473090a Mon Sep 17 00:00:00 2001
From: 17792275749 <812100002@qq.com>
Date: Fri, 15 May 2026 14:10:27 +0800
Subject: [PATCH] =?UTF-8?q?[AI=20Generated]:=20feat(*):=20=E5=AF=B9?=
=?UTF-8?q?=E6=8E=A5=E6=95=B0=E6=8D=AE=E9=A1=B5/=E6=B5=8B=E9=87=8F?=
=?UTF-8?q?=E6=8A=A5=E5=91=8A=E7=9C=9F=E5=AE=9E=E6=8E=A5=E5=8F=A3=EF=BC=8C?=
=?UTF-8?q?=E5=AE=8C=E5=96=84=E6=88=90=E5=91=98=E7=AE=A1=E7=90=86=E3=80=81?=
=?UTF-8?q?=E8=93=9D=E7=89=99=E8=BF=9E=E6=8E=A5=E5=8F=8A=E6=88=90=E5=91=98?=
=?UTF-8?q?=E9=80=89=E6=8B=A9=E6=8A=BD=E5=B1=89=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/claudeCodeTabState.xml | 2 +-
miniprogram/app.ts | 17 +-
.../selectMemberDrawer/selectMemberDrawer.ts | 91 +++++---
miniprogram/lefu/service.ts | 11 +-
miniprogram/pages/addMember/addMember.ts | 217 +++++++++--------
miniprogram/pages/addMember/addMember.wxml | 27 ++-
.../pages/connectedDevice/connectedDevice.ts | 2 +-
miniprogram/pages/data/data.less | 36 ++-
miniprogram/pages/data/data.ts | 221 +++++++++++++++---
miniprogram/pages/data/data.wxml | 61 +++--
miniprogram/pages/equipment/equipment.ts | 6 +-
.../pages/equipmentMember/equipmentMember.ts | 13 +-
.../measurementReport/measurementReport.less | 11 +-
.../measurementReport/measurementReport.ts | 168 ++++++++-----
.../measurementReport/measurementReport.wxml | 6 +-
.../supplementPersonal/supplementPersonal.ts | 6 +-
16 files changed, 612 insertions(+), 283 deletions(-)
diff --git a/.idea/claudeCodeTabState.xml b/.idea/claudeCodeTabState.xml
index faec8fa..14424ed 100644
--- a/.idea/claudeCodeTabState.xml
+++ b/.idea/claudeCodeTabState.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/miniprogram/app.ts b/miniprogram/app.ts
index 514a129..7a5194a 100644
--- a/miniprogram/app.ts
+++ b/miniprogram/app.ts
@@ -5,15 +5,14 @@ App({
},
onLaunch() {
-
// 已登录则跳过登录页
- // const userInfo = wx.getStorageSync('userInfo')
- // if (userInfo) {
- // const connectDeviceInfo = wx.getStorageSync('connectDeviceInfo')
- // const hasDevice = connectDeviceInfo && Object.keys(connectDeviceInfo).length > 0
- // wx.reLaunch({
- // url: hasDevice ? '/pages/home/home' : '/pages/connectedDevice/connectedDevice'
- // })
- // }
+ const userInfo = wx.getStorageSync('userInfo')
+ if (userInfo) {
+ const connectDeviceInfo = wx.getStorageSync('connectDeviceInfo')
+ const hasDevice = connectDeviceInfo && Object.keys(connectDeviceInfo).length > 0
+ wx.reLaunch({
+ url: hasDevice ? '/pages/home/home' : '/pages/connectedDevice/connectedDevice'
+ })
+ }
},
})
diff --git a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts b/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts
index 1d3c6af..a02bc3f 100644
--- a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts
+++ b/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts
@@ -1,25 +1,35 @@
-/** 成员数据项 */
-interface SelectMemberItem {
- /** 唯一标识 */
+import { get } from '../../utils/request/index'
+import { lefuService } from '../../lefu/index'
+
+/** GET /weighingScale/v3/select/addMembers/user 返回的单条结构 */
+interface AddMemberApiItem {
id: string
- /** 姓名 */
- name: string
- /** 性别 */
- gender: '男' | '女'
- /** 年龄 */
- age: number
- /** 身高(cm) */
+ realname: string
+ sex_dictText: '男' | '女'
+ birthday: string
height: number
- /** 头像 URL,无则显示彩色占位圆 */
+ weight: number
+ idCard: string | null
+ avatar: string | null
+}
+
+/** 抽屉内部成员项(含回填所需全量字段) */
+interface ExistingMember {
+ id: string
+ name: string
+ gender: '男' | '女'
+ age: number
+ height: number
+ weight: number
+ idCard: string
avatar?: string
- /** 是否已选中 */
selected?: boolean
}
/**
* 选择已有成员抽屉组件
- * Properties: show(控制显示), members(成员列表)
- * Events: close(关闭), select(选中成员,携带 SelectMemberItem)
+ * Properties: show(控制显示)
+ * Events: close(关闭), select(选中成员,携带 ExistingMember)
*/
Component({
properties: {
@@ -27,11 +37,6 @@ Component({
show: {
type: Boolean,
value: false
- },
- /** 成员列表 */
- members: {
- type: Array,
- value: [] as SelectMemberItem[]
}
},
@@ -39,25 +44,25 @@ Component({
/** 控制 wx:if,DOM 是否存在 */
innerShow: false,
/** 控制 --visible 类,触发 CSS transition */
- animVisible: false
+ animVisible: false,
+ /** 成员列表(组件内部拉取) */
+ members: [] as ExistingMember[]
},
observers: {
/**
- * 监听外部 show 变化,错开两帧:
- * 打开:先插入 DOM,下一帧加动画类
- * 关闭:先移除动画类,等 transition 结束再移除 DOM
+ * 监听外部 show 变化:
+ * 打开时先拉数据再展示抽屉;关闭时移除动画类后移除 DOM
*/
show(val: boolean) {
if (val) {
+ this._fetchMembers()
this.setData({ innerShow: true })
- // 延迟一帧,让 DOM 先以初始状态渲染,再触发 transition
setTimeout(() => {
this.setData({ animVisible: true })
}, 20)
} else {
this.setData({ animVisible: false })
- // 等 CSS transition 结束(0.3s)再移除 DOM
setTimeout(() => {
this.setData({ innerShow: false })
}, 300)
@@ -66,6 +71,40 @@ Component({
},
methods: {
+ /** 从生日字符串(YYYY-MM-DD)计算周岁 */
+ _calcAge(birthday: string): number {
+ if (!birthday) return 0
+ const [year, month, day] = birthday.split('-').map(Number)
+ if (!year || !month || !day) return 0
+ const now = new Date()
+ let age = now.getFullYear() - year
+ if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1
+ return Math.max(0, age)
+ },
+
+ /** 调接口获取可选成员列表 */
+ _fetchMembers() {
+ const userInfo = wx.getStorageSync('userInfo') as { userId?: string } | null
+ const sn = lefuService.deviceInfo?.serialNumber ?? ''
+ get('weighingScale/v3/select/addMembers/user', {
+ userId: userInfo?.userId ?? '',
+ sn,
+ }, { loading: false })
+ .then((res: any) => {
+ const members: ExistingMember[] = (res.result ?? []).map((item: AddMemberApiItem) => ({
+ id: item.id,
+ name: item.realname,
+ gender: item.sex_dictText,
+ age: this._calcAge(item.birthday),
+ height: item.height,
+ weight: item.weight,
+ idCard: item.idCard ?? '',
+ avatar: item.avatar ?? '',
+ }))
+ this.setData({ members })
+ })
+ },
+
/** 点击遮罩关闭 */
onTapMask() {
this.triggerEvent('close')
@@ -81,7 +120,7 @@ Component({
* @param e 携带 data-member 的点击事件
*/
onTapMember(e: WechatMiniprogram.TouchEvent) {
- const member = e.currentTarget.dataset.member as SelectMemberItem
+ const member = e.currentTarget.dataset.member as ExistingMember
this.triggerEvent('select', member)
}
}
diff --git a/miniprogram/lefu/service.ts b/miniprogram/lefu/service.ts
index 63b9764..09ce6df 100644
--- a/miniprogram/lefu/service.ts
+++ b/miniprogram/lefu/service.ts
@@ -124,7 +124,7 @@ class LeFuService {
this._busSubscribed = true
plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => {
- console.log(TAG, 'bus[devicesList],设备数:', devList?.length)
+ console.log(TAG, 'bus[devicesList],设备数:', devList?.length, devList)
if (!devList?.length) {
this._onDevicesListCb?.([])
return
@@ -190,8 +190,8 @@ class LeFuService {
})
}
- connect(device: ScannedDevice): void {
- console.log(TAG, 'connect():', device.name)
+ connect(rawDevice: RawDevice): void {
+ console.log(TAG, 'connect():', rawDevice.name)
this._intentionalConnect = true
this._stopKeepAlive()
this._stopReconnectTimer()
@@ -200,8 +200,11 @@ class LeFuService {
this._deviceInfo = null
this._onDeviceInfoCb = null
this._onDeviceConnectCb = null
+ // 直连缓存设备时不走 startScan,需手动初始化插件设备配置和总线订阅
+ plugin.Blue.setDeviceSetting(DEVICE_SETTINGS)
+ this._subscribeBus()
plugin.Blue.stopBluetoothDevicesDiscovery()
- plugin.Blue.createBLEConnection(device.raw)
+ plugin.Blue.createBLEConnection(rawDevice)
}
disconnect(): void {
diff --git a/miniprogram/pages/addMember/addMember.ts b/miniprogram/pages/addMember/addMember.ts
index 7de9830..59adf52 100644
--- a/miniprogram/pages/addMember/addMember.ts
+++ b/miniprogram/pages/addMember/addMember.ts
@@ -1,3 +1,16 @@
+import { get, put } from '../../utils/request/index'
+import { lefuService } from '../../lefu/index'
+
+/** GET /weighingScale/v3/selectUserBySn/user 返回的单条结构(编辑回填用) */
+interface SysUserDevice {
+ id: string
+ realname: string
+ idCard: string
+ height: number
+ sex: number
+ birthday: string
+}
+
/** 表单数据模型 */
interface MemberForm {
/** 姓名 */
@@ -10,80 +23,77 @@ interface MemberForm {
age: string
/** 身高(cm) */
height: string
+ /** 体重(kg) */
+ weight: string
}
/** 身份证解析结果 */
interface IdCardParsed {
- /** 性别,空字符串表示解析失败 */
gender: string
- /** 年龄,空字符串表示解析失败 */
age: string
+ sex: number
+ birthday: string
}
-/** 已有成员数据项 */
-interface SelectMemberItem {
- /** 唯一标识 */
- id: string
- /** 姓名 */
+/** 从抽屉组件 select 事件接收的成员数据 */
+interface ExistingMember {
name: string
- /** 性别 */
gender: '男' | '女'
- /** 年龄 */
age: number
- /** 身高(cm) */
height: number
- /** 头像 URL,无则显示彩色占位圆 */
- avatar?: string
- /** 是否已选中 */
- selected?: boolean
+ weight: number
+ idCard: string
}
/**
- * 添加成员页
- * 收集姓名、身份证号、身高,并根据身份证自动解析性别与年龄
- * 保存后返回设备成员列表页
+ * 添加/编辑成员页
+ * URL 参数含 userId 时为编辑模式,隐藏「从已有成员中选择」按钮并预填表单
*/
Page({
data: {
- // 表单数据
+ /** 编辑模式标记 */
+ isEdit: false,
+ /** 编辑时的用户 ID */
+ userId: '',
+
form: {
name: '',
idCard: '',
gender: '',
age: '',
- height: ''
+ height: '',
+ weight: ''
} as MemberForm,
- // 抽屉显示状态
showSelectDrawer: false,
-
- // TODO: 后续替换为接口数据
- existingMembers: [
- { id: '1', name: '陈小飞', gender: '男', age: 32, height: 172, avatar: '' },
- { id: '2', name: '李镇南', gender: '男', age: 35, height: 185, avatar: '' },
- { id: '3', name: '张颖', gender: '女', age: 35, height: 185, avatar: '' },
- { id: '4', name: '李镇南', gender: '男', age: 35, height: 185, avatar: '' },
- { id: '5', name: '赵雪', gender: '女', age: 31, height: 165, avatar: '' }
- ] as SelectMemberItem[]
},
- /**
- * 姓名输入
- */
+ onLoad(options: Record) {
+ const { userId } = options
+ if (!userId) return
+ this.setData({ isEdit: true, userId })
+ get('weighingScale/v2/getUserInfo', { id: userId }, { loading: false })
+ .then((res: any) => {
+ const u: SysUserDevice = res.result
+ if (!u) return
+ const parsed = this.parseIdCard(u.idCard ?? '')
+ this.setData({
+ 'form.name': u.realname ?? '',
+ 'form.idCard': u.idCard ?? '',
+ 'form.gender': parsed.gender,
+ 'form.age': parsed.age,
+ 'form.height': String(u.height ?? ''),
+ })
+ })
+ },
+
onNameInput(e: WechatMiniprogram.Input) {
- this.setData({
- 'form.name': e.detail.value
- })
+ this.setData({ 'form.name': e.detail.value })
},
- /**
- * 身份证号输入
- * 长度满 18 位时自动解析性别与年龄,否则清空
- */
onIdCardInput(e: WechatMiniprogram.Input) {
const idCard = (e.detail.value || '').trim().toUpperCase()
const parsed = this.parseIdCard(idCard)
-
this.setData({
'form.idCard': idCard,
'form.gender': parsed.gender,
@@ -91,102 +101,69 @@ Page({
})
},
- /**
- * 身高输入
- */
onHeightInput(e: WechatMiniprogram.Input) {
- this.setData({
- 'form.height': e.detail.value
- })
+ this.setData({ 'form.height': e.detail.value })
+ },
+
+ onWeightInput(e: WechatMiniprogram.Input) {
+ this.setData({ 'form.weight': e.detail.value })
},
/**
- * 解析身份证号,返回性别与年龄
- * @param idCard 18 位身份证号
+ * 解析身份证,返回性别、年龄、sex 数值、出生日期
*/
parseIdCard(idCard: string): IdCardParsed {
- // 简单校验:18 位且前 17 位为数字
- const reg = /^\d{17}[\dX]$/
- if (!reg.test(idCard)) {
- return { gender: '', age: '' }
- }
+ const empty: IdCardParsed = { gender: '', age: '', sex: 0, birthday: '' }
+ if (!/^\d{17}[\dX]$/.test(idCard)) return empty
- // 性别:第 17 位奇数为男,偶数为女
const genderCode = parseInt(idCard.charAt(16), 10)
- const gender = genderCode % 2 === 1 ? '男' : '女'
+ const sex = genderCode % 2 === 1 ? 1 : 2
+ const gender = sex === 1 ? '男' : '女'
- // 出生日期:第 7-14 位
const year = parseInt(idCard.substr(6, 4), 10)
const month = parseInt(idCard.substr(10, 2), 10)
const day = parseInt(idCard.substr(12, 2), 10)
+ if (!year || month < 1 || month > 12 || day < 1 || day > 31) return empty
- // 基本合法性校验
- if (
- !year || !month || !day ||
- month < 1 || month > 12 ||
- day < 1 || day > 31
- ) {
- return { gender: '', age: '' }
- }
-
- // 计算周岁(看是否过了今年生日)
const now = new Date()
let age = now.getFullYear() - year
- const nowMonth = now.getMonth() + 1
- const nowDay = now.getDate()
- if (nowMonth < month || (nowMonth === month && nowDay < day)) {
- age -= 1
- }
+ if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1
+ if (age < 0 || age > 150) return empty
- if (age < 0 || age > 150) {
- return { gender: '', age: '' }
- }
-
- return { gender, age: String(age) }
+ const mm = String(month).padStart(2, '0')
+ const dd = String(day).padStart(2, '0')
+ return { gender, age: String(age), sex, birthday: `${year}-${mm}-${dd}` }
},
- /**
- * 从已有成员中选择 — 打开抽屉
- */
onTapSelectExisting() {
this.setData({ showSelectDrawer: true })
},
- /** 关闭选择成员抽屉 */
onSelectMemberClose() {
this.setData({ showSelectDrawer: false })
},
- /**
- * 选中成员后填充表单
- * @param e 携带 detail(SelectMemberItem) 的自定义事件
- */
- onSelectMember(e: WechatMiniprogram.CustomEvent) {
- const { name, gender, age, height } = e.detail
+ onSelectMember(e: WechatMiniprogram.CustomEvent) {
+ const { name, gender, age, height, weight, idCard } = e.detail
this.setData({
showSelectDrawer: false,
'form.name': name,
+ 'form.idCard': idCard,
'form.gender': gender,
'form.age': String(age),
- 'form.height': String(height)
+ 'form.height': String(height),
+ 'form.weight': String(weight),
})
},
- /**
- * 取消:直接返回上一页
- */
- onCancel() {
+onCancel() {
wx.navigateBack()
},
- /**
- * 保存成员信息
- * 必填:姓名、身份证号(18 位且解析成功)、身高
- * 未通过时 toast 提示,通过后返回上一页
- */
onSubmit() {
- const { name, idCard, gender, age, height } = this.data.form
+ const { name, idCard, gender, age, height, weight } = this.data.form
const heightNum = parseFloat(height)
+ const weightNum = parseFloat(weight)
if (!name.trim()) {
wx.showToast({ title: '请填写姓名', icon: 'none' })
@@ -197,15 +174,55 @@ Page({
return
}
if (!gender || !age) {
- wx.showToast({ title: '身份证号有误,请检查', icon: 'none' })
+ wx.showToast({ title: '身份证号有误,请检查', icon: 'none' })
return
}
if (!height || isNaN(heightNum) || heightNum <= 0) {
wx.showToast({ title: '请填写身高', icon: 'none' })
return
}
+ if (!weight || isNaN(weightNum) || weightNum <= 0) {
+ wx.showToast({ title: '请填写体重', icon: 'none' })
+ return
+ }
- // TODO: 此处后续接入成员保存接口
- wx.navigateBack()
+ const parsed = this.parseIdCard(idCard)
+ const userInfo = wx.getStorageSync('userInfo')
+ const connectDeviceInfoRaw = wx.getStorageSync('connectDeviceInfo')
+
+ const payload: Record = {
+ parentUserId: userInfo?.userId ?? '',
+ scaleDeviceId: lefuService.deviceInfo?.serialNumber ?? '',
+ realname: name.trim(),
+ idCard,
+ sex: parsed.sex,
+ birthday: parsed.birthday,
+ height: heightNum,
+ weight: weightNum,
+ connectDeviceInfo: connectDeviceInfoRaw ? JSON.stringify(connectDeviceInfoRaw) : '',
+ }
+ if (this.data.isEdit) {
+ payload.id = this.data.userId
+ }
+
+ const successMsg = this.data.isEdit ? '保存成功' : '添加成功'
+ const failMsg = this.data.isEdit ? '保存失败,请重试' : '添加失败,请重试'
+
+ put('weighingScale/v3/edit/user', payload)
+ .then(() => {
+ wx.showToast({ title: successMsg, icon: 'success' })
+ setTimeout(() => {
+ // 通知上一页(equipmentMember)刷新成员列表
+ const pages = getCurrentPages()
+ const prevPage = pages[pages.length - 2] as any
+ if (prevPage && prevPage.data?.sn) {
+ prevPage._loadData(prevPage.data.sn)
+ }
+ wx.navigateBack()
+ }, 1500)
+ })
+ .catch(() => {
+ wx.showToast({ title: failMsg, icon: 'none' })
+ })
}
})
diff --git a/miniprogram/pages/addMember/addMember.wxml b/miniprogram/pages/addMember/addMember.wxml
index 85fb454..8ad52af 100644
--- a/miniprogram/pages/addMember/addMember.wxml
+++ b/miniprogram/pages/addMember/addMember.wxml
@@ -98,10 +98,32 @@
+
+
+
+
+ 体重
+ *
+
+
+
+
+ kg
+
+
+
-
- 从已有成员中选择
+
+
+ 从已有成员中选择
+
@@ -113,7 +135,6 @@
diff --git a/miniprogram/pages/connectedDevice/connectedDevice.ts b/miniprogram/pages/connectedDevice/connectedDevice.ts
index d4efd5a..e06a63b 100644
--- a/miniprogram/pages/connectedDevice/connectedDevice.ts
+++ b/miniprogram/pages/connectedDevice/connectedDevice.ts
@@ -170,7 +170,7 @@ Page({
}
wx.showLoading({ title: '连接中...', mask: true })
- lefuService.connect(raw)
+ lefuService.connect(raw.raw)
lefuService.onConnectState((state) => {
if (state === lefuService.BLUE_STATE.CONNECTFAILED) {
wx.hideLoading()
diff --git a/miniprogram/pages/data/data.less b/miniprogram/pages/data/data.less
index d4cee05..cd8a82a 100644
--- a/miniprogram/pages/data/data.less
+++ b/miniprogram/pages/data/data.less
@@ -34,13 +34,18 @@
align-items: center;
margin-bottom: 30rpx;
- /* 头像色块 */
+ /* 头像色块(展示姓名首字) */
.avatar {
width: 90rpx;
height: 90rpx;
margin-right: 24rpx;
border-radius: 18rpx;
- background: var(--avatar-bg);
+ color: #FFFFFF;
+ font-size: 36rpx;
+ font-weight: bold;
+ line-height: 90rpx;
+ text-align: center;
+ background-color: #1385FA;
}
/* 姓名 + meta */
@@ -269,4 +274,31 @@
}
}
}
+
+ /* ===== 空态 ===== */
+ .empty-state {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 160rpx;
+
+ .empty-icon {
+ width: 193rpx;
+ height: 138rpx;
+ margin-bottom: 40rpx;
+
+ image {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .empty-text {
+ color: #808080;
+ height: 30rpx;
+ font-size: 30rpx;
+ line-height: 30rpx;
+ }
+ }
}
diff --git a/miniprogram/pages/data/data.ts b/miniprogram/pages/data/data.ts
index 58f9a53..48bad4b 100644
--- a/miniprogram/pages/data/data.ts
+++ b/miniprogram/pages/data/data.ts
@@ -1,72 +1,219 @@
-import { getMockRecords, calcStats } from '../../mock/data.mock'
-import type { TimeRange, RecordItem, StatsData } from '../../mock/data.mock'
-import type { MemberItem } from '../../mock/member.mock'
-import { mockMembers } from '../../mock/member.mock'
+import { get } from '../../utils/request/index'
/** 加载状态机 */
type LoadStatus = 'idle' | 'loading' | 'loaded'
+/** 接口 type 参数:0=全部 1=近7天 2=近30天 3=近3月 */
+type RangeType = 0 | 1 | 2 | 3
+
/** 时间范围 Tab 项 */
interface RangeTab {
- key: TimeRange
+ key: RangeType
label: string
}
+/** 缓存中的用户信息 */
+interface StorageUserInfo {
+ userId: string
+ realname: string
+ sex: number
+ height: number
+ birthday: string
+ avatar: string | null
+ relationType: number | string | null
+ [key: string]: any
+}
+
+/** 页面展示用用户信息 */
+interface PageUserInfo extends StorageUserInfo {
+ sex_dictText: '男' | '女' | ''
+ age: number
+ isSelf: boolean
+}
+
+/** GET /weighingScale/v3/getMeasureStatistics 返回结构 */
+interface MeasureStatistics {
+ maxWeight: number
+ minWeight: number
+ avgWeight: number
+ weightChange: number
+}
+
+/** GET /weighingScale/v3/getMeasureHistoryPage records 单条 */
+interface MeasureRecord {
+ recordId: string
+ examTime: string
+ weight: number
+ bmi: number
+ fatRate: number
+}
+
+/** 统计空态默认值 */
+const EMPTY_STATS: MeasureStatistics = {
+ maxWeight: 0,
+ minWeight: 0,
+ avgWeight: 0,
+ weightChange: 0,
+}
+
+/** 从生日字符串(YYYY-MM-DD)计算周岁 */
+function calcAge(birthday: string): number {
+ if (!birthday) return 0
+ const [year, month, day] = birthday.split('-').map(Number)
+ if (!year || !month || !day) return 0
+ const now = new Date()
+ let age = now.getFullYear() - year
+ if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1
+ return Math.max(0, age)
+}
+
Page({
data: {
status: 'idle' as LoadStatus,
- activeRange: '7d' as TimeRange,
+ activeType: 1 as RangeType,
rangeTabs: [
- { key: '7d', label: '近7天' },
- { key: '30d', label: '近30天' },
- { key: '3m', label: '近3月' },
+ { key: 1, label: '近7天' },
+ { key: 2, label: '近30天' },
+ { key: 3, label: '近3月' },
] as RangeTab[],
- /** 当前查看的成员(默认本人) */
- currentMember: null as MemberItem | null,
+ userInfo: null as PageUserInfo | null,
- stats: null as StatsData | null,
- recordList: [] as RecordItem[],
+ stats: EMPTY_STATS as MeasureStatistics,
+
+ records: [] as MeasureRecord[],
+ pageNo: 1,
+ total: 0,
+ hasMore: false,
+ hasData: false,
+ refresherTriggered: false,
},
onLoad() {
- // 默认选本人(isSelf=true),后备取第一个
- const members: MemberItem[] = wx.getStorageSync('memberList') || mockMembers
- const self = members.find(m => m.isSelf) || members[0] || null
- this.setData({ currentMember: self })
+ const raw = wx.getStorageSync('userInfo') as StorageUserInfo | null
+ if (!raw?.userId) return
+ const userInfo: PageUserInfo = {
+ ...raw,
+ sex_dictText: raw.sex === 1 ? '男' : raw.sex === 2 ? '女' : '',
+ age: calcAge(raw.birthday),
+ isSelf: String(raw.relationType ?? '') === '1',
+ }
+ this.setData({ userInfo })
this.loadData()
},
- onReady() {},
- onShow() {},
- onHide() {},
- onUnload() {},
- onPullDownRefresh() {},
- onReachBottom() {},
+ /** 重置分页,并行拉统计 + 第一页列表 */
+ loadData() {
+ const userInfo = this.data.userInfo
+ if (!userInfo?.userId) return
+ this.setData({ status: 'loading', pageNo: 1, records: [] })
- onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent {
- return {}
+ const statsReq = get('weighingScale/v3/getMeasureStatistics', {
+ userId: userInfo.userId,
+ type: this.data.activeType,
+ }, { loading: false })
+
+ const pageReq = get('weighingScale/v3/getMeasureHistoryPage', {
+ userId: userInfo.userId,
+ type: this.data.activeType,
+ pageNo: 1,
+ pageSize: 10,
+ }, { loading: false })
+
+ wx.showLoading({ title: '加载中...', mask: true })
+ Promise.all([statsReq, pageReq])
+ .then(([statsRes, pageRes]: any[]) => {
+ const rawStats = statsRes.result
+ const stats: MeasureStatistics = rawStats ? {
+ maxWeight: rawStats.maxWeight ?? 0,
+ minWeight: rawStats.minWeight ?? 0,
+ avgWeight: rawStats.avgWeight ?? 0,
+ weightChange: rawStats.weightChange ?? 0,
+ } : EMPTY_STATS
+
+ const page = pageRes.result ?? {}
+ const records: MeasureRecord[] = (page.records ?? []).map((r: any) => ({
+ recordId: r.recordId ?? '',
+ examTime: r.examTime ?? '',
+ weight: r.weight ?? 0,
+ bmi: r.bmi ?? 0,
+ fatRate: r.fatRate ?? 0,
+ }))
+ const total = page.total ?? 0
+
+ this.setData({
+ stats,
+ records,
+ total,
+ hasMore: records.length < total,
+ hasData: records.length > 0,
+ })
+ })
+ .finally(() => {
+ wx.hideLoading()
+ this.setData({ status: 'loaded', refresherTriggered: false })
+ })
},
- /** 加载数据(切 Tab / 切成员 复用同一入口) */
- loadData() {
- this.setData({ status: 'loading' })
- setTimeout(() => {
- const records = getMockRecords(this.data.activeRange)
- const stats = calcStats(records)
- this.setData({ recordList: records, stats, status: 'loaded' })
- }, 300)
+ /** scroll-view 触底,加载下一页 */
+ onScrollToLower() {
+ if (!this.data.hasMore) return
+ const userInfo = this.data.userInfo
+ if (!userInfo?.userId) return
+ const nextPage = this.data.pageNo + 1
+
+ wx.showLoading({ title: '加载中...', mask: true })
+ get('weighingScale/v3/getMeasureHistoryPage', {
+ userId: userInfo.userId,
+ type: this.data.activeType,
+ pageNo: nextPage,
+ pageSize: 10,
+ }, { loading: false })
+ .then((res: any) => {
+ const page = res.result ?? {}
+ const newRecords: MeasureRecord[] = (page.records ?? []).map((r: any) => ({
+ recordId: r.recordId ?? '',
+ examTime: r.examTime ?? '',
+ weight: r.weight ?? 0,
+ bmi: r.bmi ?? 0,
+ fatRate: r.fatRate ?? 0,
+ }))
+ const allRecords = [...this.data.records, ...newRecords]
+ this.setData({
+ records: allRecords,
+ pageNo: nextPage,
+ hasMore: allRecords.length < this.data.total,
+ })
+ })
+ .finally(() => {
+ wx.hideLoading()
+ })
+ },
+
+ /** scroll-view 下拉刷新 */
+ onRefresh() {
+ this.setData({ refresherTriggered: true })
+ this.loadData()
},
/** 切换时间范围 Tab */
onTapRange(e: WechatMiniprogram.TouchEvent) {
- const key = e.currentTarget.dataset.key as TimeRange
- if (key === this.data.activeRange) return
- this.setData({ activeRange: key })
+ const key = Number(e.currentTarget.dataset.key) as RangeType
+ if (key === this.data.activeType) return
+ this.setData({ activeType: key })
this.loadData()
},
- /** 切换成员 */
+ /** 点击记录行,携带 recordId + userId 跳转测量报告 */
+ onTapRecord(e: WechatMiniprogram.TouchEvent) {
+ const recordId = e.currentTarget.dataset.recordId as string
+ const userId = this.data.userInfo?.userId ?? ''
+ wx.navigateTo({
+ url: `/pages/measurementReport/measurementReport?recordId=${recordId}&userId=${userId}`,
+ })
+ },
+
+ /** 切换成员占位 */
onTapSwitchMember() {
wx.showToast({ title: '切换成员', icon: 'none' })
},
diff --git a/miniprogram/pages/data/data.wxml b/miniprogram/pages/data/data.wxml
index 85b5863..3f418cf 100644
--- a/miniprogram/pages/data/data.wxml
+++ b/miniprogram/pages/data/data.wxml
@@ -1,4 +1,11 @@
-
+
@@ -6,20 +13,20 @@
-
+ {{ userInfo.realname[0] }}
- {{ currentMember.name }}
-
+ {{ userInfo.realname }}
+
本人
- {{ currentMember.gender }}
+ {{ userInfo.sex_dictText }}
·
- {{ currentMember.age }}岁
+ {{ userInfo.age }}岁
|
- {{ currentMember.height }}cm
+ {{ userInfo.height }}cm
切换成员
@@ -31,7 +38,7 @@
{{ item.label }}
@@ -54,26 +61,38 @@
体重变化
-
- {{ stats.weightDiff > 0 ? '+' : '' }}{{ stats.weightDiff }}kg
+
+ {{ stats.weightChange > 0 ? '+' : '' }}{{ stats.weightChange }}kg
-
-
-
-
- {{ item.time }}
-
- 体重:{{ item.weight }}kg
- BMI:{{ item.bmi }}
- 体脂率:{{ item.bodyFat }}%
+
+
+
+
+
+ {{ item.examTime }}
+
+ 体重:{{ item.weight }}kg
+ BMI:{{ item.bmi }}
+ 体脂率:{{ item.fatRate }}%
+
+
+
+
+
+
+
+
+
+
-
-
+ 暂无称重数据
+
+
diff --git a/miniprogram/pages/equipment/equipment.ts b/miniprogram/pages/equipment/equipment.ts
index b0ce340..0ce7d57 100644
--- a/miniprogram/pages/equipment/equipment.ts
+++ b/miniprogram/pages/equipment/equipment.ts
@@ -108,8 +108,7 @@ Page({
wx.showLoading({ title: '连接中...', mask: true })
wx.openBluetoothAdapter({
success: () => {
- const target = { name: cachedRaw.scaleDeviceName ?? cachedRaw.name ?? '', raw: cachedRaw, model: {} }
- lefuService.connect(target)
+ lefuService.connect(cachedRaw)
lefuService.onDeviceConnect(() => {
wx.hideLoading()
})
@@ -150,8 +149,7 @@ Page({
wx.showLoading({ title: '连接中...', mask: true })
wx.openBluetoothAdapter({
success: () => {
- const target = { name: rawDevice!.scaleDeviceName ?? rawDevice!.name ?? '', raw: rawDevice!, model: {} }
- lefuService.connect(target)
+ lefuService.connect(rawDevice!)
lefuService.onDeviceConnect(() => {
wx.hideLoading()
wx.setStorageSync('connectDeviceInfo', rawDevice)
diff --git a/miniprogram/pages/equipmentMember/equipmentMember.ts b/miniprogram/pages/equipmentMember/equipmentMember.ts
index 200c2e9..ca1cfc1 100644
--- a/miniprogram/pages/equipmentMember/equipmentMember.ts
+++ b/miniprogram/pages/equipmentMember/equipmentMember.ts
@@ -139,6 +139,9 @@ Page({
connected: lefuService.isConnected,
'deviceCard.name': deviceInfo?.name ?? '',
})
+ if (deviceInfo?.serialNumber) {
+ this._loadData(deviceInfo.serialNumber)
+ }
// deviceInfo 实时同步:进页面时可能还未到达
lefuService.onDeviceInfo((info) => {
this.setData({
@@ -157,12 +160,6 @@ Page({
})
},
- onShow() {
- if (this.data.sn) {
- this._loadData(this.data.sn)
- }
- },
-
onUnload() {
lefuService.onDeviceConnect(() => {})
lefuService.onDisconnected(() => {})
@@ -205,10 +202,10 @@ Page({
})
},
- /** 编辑成员(接口待接入) */
+ /** 编辑成员:仅携带 userId 跳转,详情由目标页自行查询 */
onTapEdit(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
- wx.showToast({ title: `编辑 ${id}`, icon: 'none' })
+ wx.navigateTo({ url: `/pages/addMember/addMember?userId=${id}` })
},
/** 删除成员 */
diff --git a/miniprogram/pages/measurementReport/measurementReport.less b/miniprogram/pages/measurementReport/measurementReport.less
index 60ac4e8..7affb80 100644
--- a/miniprogram/pages/measurementReport/measurementReport.less
+++ b/miniprogram/pages/measurementReport/measurementReport.less
@@ -18,11 +18,14 @@
.info-avatar {
width: 90rpx;
height: 90rpx;
- border-radius: 24rpx;
margin-right: 24rpx;
- box-sizing: border-box;
- background-color: #C6E6FF;
- border: 3rpx solid #50A6FF;
+ border-radius: 18rpx;
+ color: #FFFFFF;
+ font-size: 36rpx;
+ font-weight: bold;
+ line-height: 90rpx;
+ text-align: center;
+ background-color: #1385FA;
}
.info-meta {
diff --git a/miniprogram/pages/measurementReport/measurementReport.ts b/miniprogram/pages/measurementReport/measurementReport.ts
index 4d2b187..7f90b37 100644
--- a/miniprogram/pages/measurementReport/measurementReport.ts
+++ b/miniprogram/pages/measurementReport/measurementReport.ts
@@ -1,70 +1,122 @@
-/** 测量报告数据 */
+import { get } from '../../utils/request/index'
+
+/** GET /weighingScale/v3/getMeasureHistory result 结构 */
+interface MeasureHistoryVO {
+ recordId: string
+ height: number
+ weight: number
+ weightChange: number
+ bmi: number
+ bmiType: string
+ standardWeight: number
+ fatRate: number
+ fatValue: number
+ basalMetabolism: number
+ examTime: string
+ sn: string
+ userInfo: {
+ realname: string
+ sex: number
+ [key: string]: any
+ } | null
+}
+
+/** 页面渲染用测量记录 */
interface WeightRecord {
- /** 体重整数部分 */
- weight: number
- /** 体重小数部分,含小数点,如 ".20" */
- weightDecimal: string
- /** 测量时间显示,如 "今日 07:30" */
- date: string
- /** 与上次相比差值(负=减轻,正=增重) */
- diff: number
- /** 身高(cm) */
- height: number
- /** BMI 值 */
- bmi: number
- /** BMI 评级标签,如 "标准" */
- bmiLabel: string
- /** 标准体重(kg) */
- stdWeight: number
- /** 体脂率(%) */
- bodyFat: number
- /** 体脂肪(kg) */
- bodyFatMass: number
- /** 基础代谢(kcal) */
- bmr: number
+ /** 体重整数部分 */
+ weight: number
+ /** 体重小数部分,含小数点,如 ".20" */
+ weightDecimal: string
+ /** 测量时间 */
+ date: string
+ /** 与上次体重差值(负=减轻,正=增重) */
+ diff: number
+ /** 身高(cm) */
+ height: number
+ /** BMI 值 */
+ bmi: number
+ /** BMI 评级文本(来自接口 bmiType) */
+ bmiLabel: string
+ /** 标准体重(kg) */
+ stdWeight: number
+ /** 体脂率(%) */
+ bodyFat: number
+ /** 体脂肪量(kg) */
+ bodyFatMass: number
+ /** 基础代谢(kcal) */
+ bmr: number
+}
+
+/** 空态默认值,避免页面展示 null */
+const EMPTY_RECORD: WeightRecord = {
+ weight: 0,
+ weightDecimal: '',
+ date: '',
+ diff: 0,
+ height: 0,
+ bmi: 0,
+ bmiLabel: '',
+ stdWeight: 0,
+ bodyFat: 0,
+ bodyFatMass: 0,
+ bmr: 0,
+}
+
+/** 将体重数字拆成整数 + 小数字符串 */
+function splitWeight(weight: number): { int: number; decimal: string } {
+ const str = weight.toFixed(2)
+ const [intPart, decPart] = str.split('.')
+ return { int: Number(intPart), decimal: `.${decPart}` }
}
/**
* 测量报告页
- * 展示单次测量的详细数据报告
+ * URL 参数:recordId(记录 ID)、userId(用户 ID)
*/
Page({
- data: {
- // 报告数据
- record: {
- weight: 68,
- weightDecimal: '.20',
- date: '今日 07:30',
- diff: -0.3,
- height: 176,
- bmi: 22.4,
- bmiLabel: '标准',
- stdWeight: 67.2,
- bodyFat: 22.3,
- bodyFatMass: 52.1,
- bmr: 1652
- } as WeightRecord
- },
+ data: {
+ record: EMPTY_RECORD as WeightRecord,
+ /** 被查看用户的姓名(来自接口 userInfo.realname) */
+ realname: '',
+ },
- onLoad(options) {
- // TODO: 根据 options.id 获取报告详情
- },
+ onLoad(options: Record) {
+ const { recordId, userId } = options
+ if (!recordId || !userId) return
- onReady() {},
+ get('weighingScale/v3/getMeasureHistory', {
+ userId,
+ recordId,
+ }, { loading: true })
+ .then((res: any) => {
+ const d: MeasureHistoryVO = res.result
+ if (!d) return
- onShow() {},
+ const { int, decimal } = splitWeight(d.weight ?? 0)
+ const record: WeightRecord = {
+ weight: int,
+ weightDecimal: decimal,
+ date: d.examTime ?? '',
+ diff: d.weightChange ?? 0,
+ height: d.height ?? 0,
+ bmi: d.bmi ?? 0,
+ bmiLabel: d.bmiType ?? '',
+ stdWeight: d.standardWeight ?? 0,
+ bodyFat: d.fatRate ?? 0,
+ bodyFatMass: d.fatValue ?? 0,
+ bmr: d.basalMetabolism ?? 0,
+ }
+ this.setData({
+ record,
+ realname: d.userInfo?.realname ?? '',
+ })
+ })
+ },
- onHide() {},
-
- onUnload() {},
-
- /**
- * 分享报告
- */
- onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent {
- return {
- title: `${this.data.report.userName}的测量报告`,
- path: '/pages/measurementReport/measurementReport'
- }
- }
+ onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent {
+ return {
+ title: `${this.data.realname}的测量报告`,
+ path: '/pages/measurementReport/measurementReport',
+ }
+ },
})
diff --git a/miniprogram/pages/measurementReport/measurementReport.wxml b/miniprogram/pages/measurementReport/measurementReport.wxml
index e58b9cd..cf98741 100644
--- a/miniprogram/pages/measurementReport/measurementReport.wxml
+++ b/miniprogram/pages/measurementReport/measurementReport.wxml
@@ -2,10 +2,10 @@
-
+ {{ realname[0] }}
- 陈万宁
- 2026-05-06 09:30
+ {{ realname }}
+ {{ record.date }}
diff --git a/miniprogram/pages/supplementPersonal/supplementPersonal.ts b/miniprogram/pages/supplementPersonal/supplementPersonal.ts
index af01803..3c6f782 100644
--- a/miniprogram/pages/supplementPersonal/supplementPersonal.ts
+++ b/miniprogram/pages/supplementPersonal/supplementPersonal.ts
@@ -259,8 +259,10 @@ Page({
connectDeviceInfo
}
- put('weighingScale/v3/update/user', payload)
- .then(() => {
+ put('weighingScale/v3/update/user', payload)
+ .then(res => {
+ const { connectDeviceInfo: _, ...userInfo } = res.result
+ wx.setStorageSync('userInfo', userInfo)
wx.switchTab({ url: '/pages/home/home' })
})
}