[AI Generated]: feat(*): 新增 lefu 蓝牙服务层、mock 业务数据层及 request 请求封装,接入各页面

This commit is contained in:
17792275749
2026-05-13 16:18:14 +08:00
parent 38d6f0867a
commit 3f12e34afd
18 changed files with 1222 additions and 540 deletions
@@ -1,102 +1,102 @@
/** 设备项模型 */
import { lefuService } from '../../lefu/index'
import type { ScannedDevice } from '../../lefu/index'
/** 展示用设备项 */
interface DeviceItem {
/** 设备唯一 id */
id: string
/** 设备名称 */
name: string
/** 设备图标 */
icon: string
/** 是否已连接 */
connected: boolean
/** 原始设备对象(连接时传回给 SDK) */
_raw: ScannedDevice
}
/** 页面状态 */
type DeviceStatus = 'idle' | 'searching' | 'found' | 'empty'
// 是否 mock 能搜索到设备,false 则走空状态
const MOCK_HAS_DEVICES = true
// 模拟搜索耗时(毫秒)
const MOCK_SEARCH_DURATION = 2000
Page({
/* 搜索定时器引用,onUnload 时清理 */
_searchTimer: null as number | null,
data: {
/**
* 页面状态
* - idle: 未搜索(进入页面默认态)
* - searching:搜索
* - found: 搜索完成且有设备
* - empty: 搜索完成但无设备
* - idle: 未搜索进入页面默认态
* - searching: 扫描
* - found: 扫描完成且有设备
* - empty: 扫描完成但无设备
*/
status: 'idle' as DeviceStatus,
/* 设备列表(包含已连接和未连接,每项含 connected: boolean) */
/** 扫描到的设备列表 */
deviceList: [] as DeviceItem[],
},
onLoad() {
onShow() {
// 注册回调后开始扫描
lefuService.onDevicesList((devices) => {
if (!devices.length) {
this.setData({ status: 'empty', deviceList: [] })
return
}
const list: DeviceItem[] = devices.map(d => ({
name: d.name,
connected: false,
_raw: d,
}))
this.setData({ status: 'found', deviceList: list })
})
lefuService.onConnectState((state) => {
const plugin = lefuService['_plugin']
if (!plugin) return
// 连接成功后跳转配网页
if (state === plugin.BLUE_STATE.CONNECTSUCCESS || state === plugin.BLUE_STATE.WIFISUCCESS) {
wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' })
}
})
this.onStartSearch()
},
onHide() {
// 页面隐藏时停止扫描,释放 bus 订阅
lefuService.stopScan()
},
onUnload() {
/* 清除搜索定时器,防止页面卸载后还触发 setData */
if (this._searchTimer) {
clearTimeout(this._searchTimer)
this._searchTimer = null
}
lefuService.stopScan()
},
/* 开始搜索 / 重新搜索 */
/** 开始 / 重新搜索 */
onStartSearch() {
this.setData({
status: 'searching',
deviceList: [],
})
/* 模拟搜索,后续接入蓝牙真实 API 时删除 */
this._searchTimer = setTimeout(() => {
if (MOCK_HAS_DEVICES) {
this.setData({
status: 'found',
deviceList: [
{ id: 'mock-connected', name: '智能体重秤 Pro', icon: '', connected: true },
{ id: 'mock-1', name: '体重秤 Basic', icon: '', connected: false },
{ id: 'mock-2', name: '体脂秤 Plus', icon: '', connected: false },
],
})
} else {
this.setData({ status: 'empty' })
}
}, MOCK_SEARCH_DURATION)
this.setData({ status: 'searching', deviceList: [] })
lefuService.startScan()
},
/* 连接某台设备:同时只能一台已连接,自动断开其他设备 */
/** 连接指定设备 */
onConnect(e: WechatMiniprogram.TouchEvent) {
const { id } = e.currentTarget.dataset
const deviceList = this.data.deviceList.map(item => ({
const index = e.currentTarget.dataset.index as number
const target = this.data.deviceList[index]
if (!target) return
// 更新 UI 连接状态
const list = this.data.deviceList.map((item, i) => ({
...item,
connected: item.id === id,
connected: i === index,
}))
this.setData({ deviceList })
/* TODO: 接入真实蓝牙连接逻辑 */
this.setData({ deviceList: list })
// 调用 SDK 连接(BleAdv 类型已在 startScan 内自动处理,BleConnect 类型需此处触发)
lefuService.connect(target._raw)
},
/* 断开当前连接设备 */
/** 断开当前连接 */
onDisconnect() {
const deviceList = this.data.deviceList.map(item => ({
...item,
connected: false,
}))
this.setData({ deviceList })
/* TODO: 接入真实蓝牙断开逻辑 */
const list = this.data.deviceList.map(item => ({ ...item, connected: false }))
this.setData({ deviceList: list })
lefuService.disconnect()
},
/* 查看帮助 */
/** 查看帮助 */
onOpenHelp() {
console.log('查看帮助')
/* TODO: 跳帮助页 */
wx.showToast({ title: '请确保设备已开机且在附近', icon: 'none' })
},
})
@@ -1,179 +1,167 @@
/** WiFi 项模型 */
import { lefuService } from '../../lefu/index'
import type { LeFuWifiItem, WifiVersion } from '../../lefu/index'
/** WiFi 展示项 */
interface WifiItem {
/** WiFi 唯一 id */
id: string
/** WiFi 名称(SSID) */
name: string
ssid: string
/** 是否已通过本页配网成功 */
connected: boolean
}
/** 当前已连接设备(顶部卡片) */
/** 当前已连接设备顶部卡片 */
interface CurrentDevice {
/** 设备唯一 id */
id: string
/** 设备名称 */
name: string
}
/** 密码态选中的 WiFi */
interface SelectedWifi {
/** WiFi id,空字符串表示未选中 */
id: string
/** WiFi 名称 */
name: string
ssid: string
}
/** 页面状态 */
type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password'
// 是否 mock 能搜索到 WiFi,false 则走空状态
const MOCK_HAS_WIFI = true
// 模拟搜索耗时(毫秒)
const MOCK_SEARCH_DURATION = 600
type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password' | 'configuring'
Page({
/* 搜索定时器引用,onUnload 时清理 */
_searchTimer: null as number | null,
/** 配网协议版本,两种都支持,默认 domain2(有鉴权) */
_wifiVersion: 'domain2' as WifiVersion,
data: {
/**
* 页面状态
* - idle: 未搜索(进入页面默认态,onLoad 立即流转)
* - searching: 搜索中
* - list: 搜索完成且有 WiFi
* - empty: 搜索完成但无 WiFi
* - password: 从列表选中某项后进入密码输入态
* - idle: 未搜索
* - searching: 正在从设备获取 WiFi 列表
* - list: 列表已就绪
* - empty: 设备未扫描到任何 WiFi
* - password: 选中某项,正在输入密码
* - configuring: 正在将 WiFi 写入设备
*/
status: 'idle' as WifiStatus,
/* 当前已连接的设备(从上一页带过来的固定卡片信息) */
/** 顶部固定卡片(从上一页状态中读取,当前先用占位) */
currentDevice: {
id: 'mock-device',
name: '智能体重秤 Pro',
id: '',
name: '智能体重秤',
} as CurrentDevice,
/* WiFi 列表(connected: true 表示已通过本页配网成功的网络) */
/** WiFi 列表 */
wifiList: [] as WifiItem[],
/* 是否已有任一 WiFi 配网成功,决定底部「确认」按钮是否可点击 */
/** 是否已有任一 WiFi 配网成功 */
hasConnected: false,
/* 当前密码态选中的 WiFi */
selectedWifi: {
id: '',
name: '',
} as SelectedWifi,
/** 当前密码态选中的 WiFi */
selectedWifi: { ssid: '' } as SelectedWifi,
/* 密码输入框值 */
/** 密码输入框值 */
selectWifiPWD: '',
/* 密码框明文/密文切换:true 为密文(默认),false 为明文 */
/** 密码框明文/密文切换true 为密文默认 */
type: true,
},
onLoad() {
/* 设计稿表达"进入页面立即搜索",但状态机仍由 idle 起步,
* 此处手动调用按钮事件,避免"自动逻辑"与"按钮逻辑"分叉 */
// idle 起步,通过统一入口触发搜索
this.onStartSearch()
},
onUnload() {
/* 清除搜索定时器,防止页面卸载后还触发 setData */
if (this._searchTimer) {
clearTimeout(this._searchTimer)
this._searchTimer = null
}
// 页面卸载时不调用 stopScan,蓝牙连接由 home 页维持
},
/* 开始搜索:idle/empty/list → searching → list/empty */
/** 从设备获取 WiFi 列表 */
onStartSearch() {
this.setData({ status: 'searching' })
this.setData({ status: 'searching', wifiList: [] })
this._searchTimer = setTimeout(() => {
if (MOCK_HAS_WIFI) {
this.setData({
status: 'list',
wifiList: [
{ id: 'wifi-1', name: 'Rk-Health-5G', connected: false },
{ id: 'wifi-2', name: 'Rk-Guest', connected: false },
{ id: 'wifi-3', name: 'ChinaNet-7Hk2', connected: false },
{ id: 'wifi-4', name: 'TP-LINK_8899', connected: false },
],
})
} else {
this.setData({ status: 'empty', wifiList: [] })
}
}, MOCK_SEARCH_DURATION)
lefuService.getWifiList()
.then((items: LeFuWifiItem[]) => {
if (!items.length) {
this.setData({ status: 'empty' })
return
}
const list: WifiItem[] = items.map(item => ({
ssid: item.ssid,
connected: false,
}))
this.setData({ status: 'list', wifiList: list })
})
.catch(() => {
this.setData({ status: 'empty' })
})
},
/* 空态刷新」:重新进入搜索 */
/** 空态刷新 */
onRefresh() {
this.onStartSearch()
},
/* 点击某个 WiFi 项 → 进密码态 */
/** 点击 WiFi 项 → 进密码态 */
onTapWifi(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
const target = this.data.wifiList.find(item => item.id === id)
if (!target) return
/* 已连接项再次点击不进入密码态 */
if (target.connected) return
const ssid = e.currentTarget.dataset.ssid as string
const target = this.data.wifiList.find(item => item.ssid === ssid)
if (!target || target.connected) return
this.setData({
status: 'password',
selectedWifi: { id: target.id, name: target.name },
selectedWifi: { ssid },
selectWifiPWD: '',
type: true,
})
},
/* 密码输入 */
/** 密码输入 */
passwordInput(e: WechatMiniprogram.Input) {
this.setData({ selectWifiPWD: e.detail.value })
},
/* 密码明文/密文切换 */
/** 明文/密文切换 */
inputTypeChange() {
this.setData({ type: !this.data.type })
},
/* 密码态「取消」:回列表态,清空当前选中 */
/** 密码态「取消」回列表 */
onCancelPwd() {
this.setData({
status: 'list',
selectedWifi: { id: '', name: '' },
selectedWifi: { ssid: '' },
selectWifiPWD: '',
})
},
/* 密码态「连接」:把对应项标 connected,回列表态 */
/** 密码态「连接」→ 将 WiFi 写入设备 */
onConfirmPwd() {
const { selectedWifi, wifiList } = this.data
if (!selectedWifi.id) return
const { selectedWifi, selectWifiPWD } = this.data
if (!selectedWifi.ssid) return
const next = wifiList.map(item => ({
...item,
connected: item.id === selectedWifi.id,
}))
this.setData({ status: 'configuring' })
wx.showLoading({ title: '正在配网...', mask: true })
this.setData({
status: 'list',
wifiList: next,
hasConnected: true,
selectedWifi: { id: '', name: '' },
selectWifiPWD: '',
})
lefuService.configWifi(selectedWifi.ssid, selectWifiPWD, this._wifiVersion)
.then(() => {
wx.hideLoading()
// 配网成功:标记该 WiFi,回列表态
const next = this.data.wifiList.map(item => ({
...item,
connected: item.ssid === selectedWifi.ssid,
}))
this.setData({
status: 'list',
wifiList: next,
hasConnected: true,
selectedWifi: { ssid: '' },
selectWifiPWD: '',
})
})
.catch((err: Error) => {
wx.hideLoading()
wx.showToast({ title: err.message || '配网失败,请重试', icon: 'none' })
this.setData({ status: 'password' })
})
},
/* 列表底部「确认」:必须有连接成功的 WiFi 才可点击 */
/** 底部「确认」:已有 WiFi 配网成功才可点击 */
onConfirm() {
if (!this.data.hasConnected) return
wx.navigateTo({
url: '/pages/supplementPersonal/supplementPersonal',
})
wx.navigateTo({ url: '/pages/supplementPersonal/supplementPersonal' })
},
})
+1
View File
@@ -40,6 +40,7 @@
height: 90rpx;
margin-right: 24rpx;
border-radius: 18rpx;
background: var(--avatar-bg);
}
/* 姓名 + meta */
+55 -141
View File
@@ -1,159 +1,73 @@
/** 时间范围 */
type TimeRange = '7d' | '30d' | '3m'
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'
/** 加载状态机 */
type LoadStatus = 'idle' | 'loading' | 'loaded'
/** 成员信息 */
interface MemberInfo {
/** 唯一 ID */
id: string
/** 姓名 */
name: string
/** 性别 */
gender: '男' | '女'
/** 年龄 */
age: number
/** 身高(cm) */
height: number
/** 头像色块(无图片时使用) */
avatar: string
/** 是否本人 */
isSelf: boolean
}
/** 统计指标 */
interface StatsData {
/** 最高体重(kg) */
maxWeight: number
/** 最低体重(kg) */
minWeight: number
/** 平均体重(kg) */
avgWeight: number
/** 体重变化(kg,正负) */
weightDiff: number
}
/** 时间范围 Tab 项 */
interface RangeTab {
key: TimeRange
label: string
key: TimeRange
label: string
}
/** 单条测量记录 */
interface RecordItem {
/** 唯一 ID */
id: string
/** 测量时间,格式 YYYY.MM.DD HH:mm */
time: string
/** 体重(kg) */
weight: number
/** BMI */
bmi: number
/** 体脂率(%) */
bodyFat: number
}
/**
* 历史数据页
* 状态机:idle → loading → loaded
* 切换时间范围 / 切换成员 均复用同一加载流程
*/
Page({
data: {
// 加载状态
status: 'idle' as LoadStatus,
data: {
status: 'idle' as LoadStatus,
activeRange: '7d' as TimeRange,
rangeTabs: [
{ key: '7d', label: '近7天' },
{ key: '30d', label: '近30天' },
{ key: '3m', label: '近3月' },
] as RangeTab[],
// 当前选中的时间范围
activeRange: '7d' as TimeRange,
/** 当前查看的成员(默认本人) */
currentMember: null as MemberItem | null,
// 时间范围 Tab 选项
rangeTabs: [
{ key: '7d', label: '近7天' },
{ key: '30d', label: '近30天' },
{ key: '3m', label: '近3月' }
] as RangeTab[],
stats: null as StatsData | null,
recordList: [] as RecordItem[],
},
// 当前成员
currentMember: {
id: 'm-001',
name: '陈小飞',
gender: '男',
age: 28,
height: 175,
avatar: '#1385FA',
isSelf: true
} as MemberInfo,
onLoad() {
// 默认选本人(isSelf=true),后备取第一个
const members: MemberItem[] = wx.getStorageSync('memberList') || mockMembers
const self = members.find(m => m.isSelf) || members[0] || null
this.setData({ currentMember: self })
this.loadData()
},
// 统计数据
stats: {
maxWeight: 73.5,
minWeight: 71.2,
avgWeight: 72.3,
weightDiff: -2.3
} as StatsData,
onReady() {},
onShow() {},
onHide() {},
onUnload() {},
onPullDownRefresh() {},
onReachBottom() {},
// 历史记录列表
recordList: [] as RecordItem[]
},
onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent {
return {}
},
onLoad() {
// idle 起步,通过统一入口触发加载
this.loadData()
},
/** 加载数据(切 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)
},
onReady() {},
/** 切换时间范围 Tab */
onTapRange(e: WechatMiniprogram.TouchEvent) {
const key = e.currentTarget.dataset.key as TimeRange
if (key === this.data.activeRange) return
this.setData({ activeRange: key })
this.loadData()
},
onShow() {},
onHide() {},
onUnload() {},
onPullDownRefresh() {},
onReachBottom() {},
onShareAppMessage(opts): WechatMiniprogram.Page.ICustomShareContent {
console.log(opts.target)
return {}
},
/**
* 加载数据(切 Tab / 切成员 复用)
*/
loadData() {
this.setData({ status: 'loading' })
// TODO: 后续接入真实接口,这里先用 mock
const mockList: RecordItem[] = [
{ id: 'r-001', time: '2026.05.12 08:12', weight: 71.2, bmi: 23.2, bodyFat: 18.5 },
{ id: 'r-002', time: '2026.05.11 21:30', weight: 71.8, bmi: 23.4, bodyFat: 18.7 },
{ id: 'r-003', time: '2026.05.11 08:05', weight: 71.5, bmi: 23.3, bodyFat: 18.6 },
{ id: 'r-004', time: '2026.05.10 07:58', weight: 72.0, bmi: 23.5, bodyFat: 18.9 },
{ id: 'r-005', time: '2026.05.10 22:10', weight: 72.6, bmi: 23.7, bodyFat: 19.1 },
{ id: 'r-006', time: '2026.05.09 08:20', weight: 72.8, bmi: 23.8, bodyFat: 19.2 },
{ id: 'r-007', time: '2026.05.08 08:00', weight: 73.5, bmi: 24.0, bodyFat: 19.5 }
]
this.setData({ recordList: mockList, status: 'loaded' })
},
/**
* 切换时间范围 Tab
*/
onTapRange(e: WechatMiniprogram.TouchEvent) {
const key = e.currentTarget.dataset.key as TimeRange
if (key === this.data.activeRange) return
this.setData({ activeRange: key })
this.loadData()
},
/**
* 切换成员(后续接入成员选择浮层)
*/
onTapSwitchMember() {
// TODO: 接入成员选择浮层
wx.showToast({ title: '切换成员', icon: 'none' })
}
/** 切换成员 */
onTapSwitchMember() {
wx.showToast({ title: '切换成员', icon: 'none' })
},
})
+1 -1
View File
@@ -6,7 +6,7 @@
<!-- 成员信息 -->
<view class="userInfo">
<view class="avatar" style="background: {{ currentMember.avatar }};"></view>
<view class="avatar" style="--avatar-bg: {{ currentMember.avatar }};"></view>
<view class="info">
<view class="info-name-row">
<view class="info-name">{{ currentMember.name }}</view>
@@ -162,6 +162,7 @@
margin-right: 24rpx;
box-sizing: border-box;
border: 3rpx solid #50A6FF;
background: var(--avatar-bg);
}
.member-info {
@@ -1,135 +1,132 @@
/** 成员项 */
interface MemberItem {
/** 唯一 ID */
id: string
/** 姓名 */
name: string
/** 性别 */
gender: '男' | '女'
/** 年龄 */
age: number
/** 身高(cm) */
height: number
/** 头像色块(无图片时使用) */
avatar: string
/** 是否本人 */
isSelf: boolean
/** 是否他人添加(只读,不显示编辑/删除) */
isFromOther: boolean
}
import { lefuService } from '../../lefu/index'
import type { DeviceMember } from '../../lefu/index'
import { mockMembers } from '../../mock/member.mock'
import type { MemberItem } from '../../mock/member.mock'
/** 设备卡片 */
interface DeviceCard {
/** 设备 ID */
id: string
/** 设备名称 */
name: string
/** 已使用容量(成员数) */
capacityUsed: number
/** 总容量 */
capacityTotal: number
id: string
name: string
capacityUsed: number
capacityTotal: number
}
/**
* 设备成员页
* 两态:已连接(connected=true) / 未连接(connected=false)
* connected 为页面内部全局状态,后续接入真实设备状态时替换
* MemberItem → DeviceMember 转换
* lefu 层只认识 DeviceMember,业务层负责转换
*/
function toDeviceMember(m: MemberItem): DeviceMember {
return {
id: m.id,
name: m.name,
gender: m.gender === '男' ? 1 : 0,
age: m.age,
height: m.height,
isSelf: m.isSelf,
}
}
/**
* 将成员列表全量同步到设备
* 成功/失败均给出 Toast 提示
*/
function syncToDevice(members: MemberItem[]): void {
const deviceMembers = members.map(toDeviceMember)
wx.showLoading({ title: '正在同步成员...', mask: true })
lefuService.syncMembersToDevice(deviceMembers)
.then(() => {
wx.hideLoading()
wx.showToast({ title: '成员同步成功', icon: 'success' })
})
.catch((err: Error) => {
wx.hideLoading()
wx.showToast({ title: err.message || '同步失败,请重试', icon: 'none' })
})
}
Page({
data: {
// 设备连接状态(全局,内部控制)
connected: true,
data: {
/** 设备连接状态(后续接入 lefuService.onConnectState 替换) */
connected: true,
// 设备卡片
deviceCard: {
id: 'device-001',
name: 'MX-W30A1',
capacityUsed: 4,
capacityTotal: 10
} as DeviceCard,
deviceCard: {
id: 'device-001',
name: 'MX-W30A1',
capacityUsed: 0,
capacityTotal: 10,
} as DeviceCard,
// 成员列表(色块占位头像)
memberList: [
{
id: 'm-001',
name: '陈小飞',
gender: '男',
age: 28,
height: 175,
avatar: '#1385FA',
isSelf: true,
isFromOther: false
},
{
id: 'm-002',
name: '李镇南',
gender: '男',
age: 32,
height: 178,
avatar: '#2AC79F',
isSelf: false,
isFromOther: false
},
{
id: 'm-003',
name: '张颖',
gender: '女',
age: 26,
height: 165,
avatar: '#FF8FAB',
isSelf: false,
isFromOther: false
},
{
id: 'm-004',
name: '李镇南',
gender: '男',
age: 60,
height: 170,
avatar: '#FFB347',
isSelf: false,
isFromOther: true
}
] as MemberItem[]
},
memberList: [] as MemberItem[],
},
/**
* 编辑成员
*/
onTapEdit(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
// TODO: 后续接入编辑成员浮层/页面
wx.showToast({ title: `编辑 ${id}`, icon: 'none' })
},
onLoad() {
this._loadMembers()
},
/**
* 删除成员
*/
onTapDelete(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
wx.showModal({
title: '提示',
content: '确定删除该成员?',
success: (res) => {
if (!res.confirm) return
// TODO: 后续接入删除成员接口
const list = this.data.memberList.filter(m => m.id !== id)
this.setData({
memberList: list,
'deviceCard.capacityUsed': list.length
})
}
})
},
/** 从 storage / mock 加载成员列表 */
_loadMembers() {
const list: MemberItem[] = wx.getStorageSync('memberList') || mockMembers
this.setData({
memberList: list,
'deviceCard.capacityUsed': list.length,
})
},
/**
* 添加成员:跳转到 addMember 页
* 未连接态禁用
*/
onTapAddMember() {
if (!this.data.connected) return
wx.navigateTo({
url: '/pages/addMember/addMember'
})
}
/** 编辑成员 */
onTapEdit(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
// TODO: 跳转编辑成员页面,编辑完成后同样需要调用 syncToDevice
wx.showToast({ title: `编辑 ${id}`, icon: 'none' })
},
/** 删除成员:更新本地 → 同步设备 */
onTapDelete(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string
wx.showModal({
title: '提示',
content: '确定删除该成员?',
success: (res) => {
if (!res.confirm) return
const list = this.data.memberList.filter(m => m.id !== id)
wx.setStorageSync('memberList', list)
this.setData({
memberList: list,
'deviceCard.capacityUsed': list.length,
})
// 删除后立即同步到设备
if (this.data.connected) {
syncToDevice(list)
}
},
})
},
/** 添加成员:跳转 addMember 页(未连接态禁用) */
onTapAddMember() {
if (!this.data.connected) return
wx.navigateTo({
url: '/pages/addMember/addMember',
})
},
/**
* addMember 页保存成功后返回时刷新列表并同步设备
* 在 onShow 中检测 storage 变化
*/
onShow() {
const stored: MemberItem[] = wx.getStorageSync('memberList') || []
const current = this.data.memberList
// 数量有变化说明刚刚新增了成员
if (stored.length !== current.length) {
this.setData({
memberList: stored,
'deviceCard.capacityUsed': stored.length,
})
if (this.data.connected) {
syncToDevice(stored)
}
}
},
})
@@ -38,7 +38,7 @@
<block wx:for="{{ memberList }}" wx:key="id">
<view class="member-item">
<!-- 头像色块 -->
<view class="member-avatar" style="background: {{ item.avatar }};"></view>
<view class="member-avatar" style="--avatar-bg: {{ item.avatar }};"></view>
<!-- 信息列 -->
<view class="member-info">
+46 -103
View File
@@ -1,118 +1,61 @@
import { mockLatestRecord } from '../../mock/home.mock'
import type { WeightRecord } from '../../mock/home.mock'
/** 加载状态机 */
type LoadStatus = 'idle' | 'loading' | 'loaded'
/** 设备信息 */
interface DeviceInfo {
/** 设备名称 */
name: string
/** 蓝牙是否已连接 */
connected: boolean
name: string
connected: boolean
}
/** 最近一次测量记录 */
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
}
/**
* 首页
* 状态机:idle → loading → loaded
* loaded 后按 hasData 分为有数据态 / 空态
*/
Page({
data: {
// 加载状态
status: 'idle' as LoadStatus,
data: {
status: 'idle' as LoadStatus,
hasData: false,
device: {
name: '智能体重秤',
connected: false,
} as DeviceInfo,
record: null as WeightRecord | null,
},
// 是否有称重数据
hasData: true,
onLoad() {
this.loadData()
},
// 设备信息
device: {
name: '智能体重秤 Pro',
connected: true
} as DeviceInfo,
onReady() {},
onShow() {},
onHide() {},
onUnload() {},
onPullDownRefresh() {},
onReachBottom() {},
// 最近一次测量记录(hasData=true 时有效)
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
},
onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent {
return {}
},
onLoad() {
// idle 起步,通过统一入口触发加载
this.loadData()
},
/** 加载首页数据(当前使用 mock,后续替换为真实接口) */
loadData() {
this.setData({ status: 'loading' })
// 模拟异步加载
setTimeout(() => {
this.setData({
status: 'loaded',
hasData: true,
record: mockLatestRecord,
})
}, 300)
},
onReady() {},
/** 跳转历史数据页 */
onTapHistory() {
wx.switchTab({ url: '/pages/data/data' })
},
onShow() {},
onHide() {},
onUnload() {},
onPullDownRefresh() {},
onReachBottom() {},
onShareAppMessage(opts): WechatMiniprogram.Page.ICustomShareContent {
console.log(opts.target)
return {}
},
/**
* 加载首页数据(后续接入真实接口)
*/
loadData() {
this.setData({ status: 'loading' })
// TODO: 后续接入真实接口,当前使用 mock
this.setData({ status: 'loaded' })
},
/**
* 跳转数据页查看历史
*/
onTapHistory() {
wx.switchTab({ url: '/pages/data/data' })
},
/**
* 切换设备(后续接入设备选择浮层)
*/
onTapSwitchDevice() {
// TODO: 接入设备选择浮层
wx.showToast({ title: '切换设备', icon: 'none' })
}
/** 切换设备 */
onTapSwitchDevice() {
wx.showToast({ title: '切换设备', icon: 'none' })
},
})