Files
bodyWeight/miniprogram/pages/detailedReport/detailedReport.ts
T
17792275749andClaude Opus 4.7 b4d3a034ba fix(detailedReport): 卡片无数据时隐藏整卡,wx:if 统一写 block 上
- 删除多余的 isEightElectrode 字段
- 各指标卡片 rows 为空时不渲染标题
- wx:if 统一移到 block 上,符合 wxml 规范

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-26 16:08:37 +08:00

365 lines
12 KiB
TypeScript

// pages/detailedReport/detailedReport.ts
import { get } from '../../utils/request/index'
/** 身体成分单项指标(bodyDataList 元素) */
interface LefuBodyDatum {
/** 指标 key,如 ppBMI、ppFat、ppMuscleKgLeftArm */
bodyParamKey: string
/** 指标中文名称 */
bodyParamName: string
/** UI 渲染颜色数组 */
colorArray?: string[]
/** 当前标准等级 */
currentStandard?: number
/** 当前测量值(字符串) */
currentValue: string
/** 指标介绍 */
introduction?: string
/** 标准范围数组 */
standardArray?: number[]
/** 当前等级标题 */
standardTitle?: string
/** 标准标题数组 */
standardTitleArray?: string[]
/** 标准颜色 */
standColor?: string
/** 标准评估结果 */
standeEvaluation?: string
/** 标准建议 */
standSuggestion?: string
/** 单位 */
unit?: string
}
/** 用户信息 */
interface MeasureUserInfo {
userId?: string
realname?: string
sex?: number
height?: number
birthday?: string
avatar?: string | null
[key: string]: any
}
/** 测量历史详情 */
interface MeasureHistoryVO {
recordId?: string
/** 身高(cm) */
height?: number
/** 体重(kg) */
weight?: number
/** 与上次体重增减量(kg) */
weightChange?: number
bmi?: number
/** BMI 类型:消瘦/正常/超重/肥胖 */
bmiType?: string
standardWeight?: number
fatRate?: number
fatValue?: number
basalMetabolism?: number
/** 测量时间 */
examTime?: string
sn?: string
userInfo?: MeasureUserInfo
/** 电极类型:0=4电极,1/5=8电极 */
scaleType?: number
bodyDataList?: LefuBodyDatum[]
}
/** 查询测量历史详情(recordId 不传时返回最近一次) */
const getMeasureHistory = (params: { userId?: string; recordId?: string }) =>
get<MeasureHistoryVO>('weighingScale/v3/getMeasureHistory', params)
/** 指标行(身体成分/脂肪分析等卡片) */
interface MetricRow {
label: string
value: string
unit: string
status: string
color: string
}
/** 基础指标网格单元 */
interface BasicCell {
label: string
value: string
unit: string
}
/** 躯干/四肢节段单元(同时含质量与占比) */
interface BodyPart {
label: string
massValue: string
massUnit: string
rateValue: string
rateUnit: string
}
/** 指标卡片 */
interface MetricCard {
title: string
rows: MetricRow[]
}
/** 躯干/四肢卡片(每行 1~2 个节段单元) */
interface BodyPartsCard {
title: string
rows: BodyPart[][]
}
const EMPTY_BASIC_CELL: BasicCell = { label: '', value: '', unit: '' }
const DEFAULT_STATUS_COLOR = '#B6B6B6'
/** 将 bodyDataList 转为 key -> 指标 的映射 */
const toMap = (list: LefuBodyDatum[] = []): Record<string, LefuBodyDatum> => {
const map: Record<string, LefuBodyDatum> = {}
list.forEach((d) => {
if (d && d.bodyParamKey) map[d.bodyParamKey] = d
})
return map
}
/** 按 key 取指标并转成行,缺失返回 null */
const pick = (map: Record<string, LefuBodyDatum>, key: string): MetricRow | null => {
const d = map[key]
if (!d) return null
return {
label: d.bodyParamName || key,
value: d.currentValue ?? '',
unit: d.unit || '',
status: d.standardTitle || '',
color: d.standColor || DEFAULT_STATUS_COLOR,
}
}
/** 按一组 key 构建行,自动跳过缺失项 */
const buildRows = (map: Record<string, LefuBodyDatum>, keys: string[]): MetricRow[] =>
keys.map((k) => pick(map, k)).filter((r): r is MetricRow => r !== null)
/** 构建节段单元(质量 + 占比) */
const buildPart = (
map: Record<string, LefuBodyDatum>,
label: string,
massKey: string,
rateKey: string,
): BodyPart => ({
label,
massValue: map[massKey]?.currentValue ?? '--',
massUnit: map[massKey]?.unit ?? 'kg',
rateValue: map[rateKey]?.currentValue ?? '--',
rateUnit: map[rateKey]?.unit ?? '%',
})
/** 体重拆分整数 + 小数 */
const splitWeight = (weight: number): { int: string; decimal: string } => {
const str = weight.toFixed(2)
const [int, dec] = str.split('.')
return { int, decimal: `.${dec}` }
}
/** 根据 BMI 计算刻度圆圈位置(百分比) */
const calcBmiLeft = (bmi?: number): number => {
if (bmi == null || isNaN(bmi)) return 0
const pct = (bmi - 14) / (40 - 14)
return Math.min(Math.max(pct, 0), 1) * 100
}
Page({
data: {
// 导航栏背景色,根据滚动位置动态变化
navBgColor: 'rgba(255,255,255,0)',
header: {
date: '',
weightInt: '--',
weightDecimal: '',
bmiText: '',
changeText: '',
},
bmiCircleLeft: 0,
basicRows: [] as BasicCell[][],
bodyComposition: { title: '身体成分', rows: [] } as MetricCard,
fatAnalysis: { title: '脂肪分析', rows: [] } as MetricCard,
physique: { title: '体态管理', rows: [] } as MetricCard,
weightControl: { title: '体重管控', rows: [] } as MetricCard,
// 以下卡片为 8 电极专属
cellWater: { title: '细胞液分析', rows: [] } as MetricCard,
advancedComposition: { title: '进阶成分', rows: [] } as MetricCard,
bodyFat: { title: '躯干/四肢脂肪', rows: [] } as BodyPartsCard,
bodyMuscle: { title: '躯干/四肢肌肉', rows: [] } as BodyPartsCard,
},
onLoad(options: Record<string, string | undefined>) {
const recordId = options.recordId || ''
const userInfo = (wx.getStorageSync('userInfo') || {}) as Record<string, any>
const userId = options.userId || userInfo.userId || ''
this.fetchHistory(userId, recordId)
},
async fetchHistory(userId: string, recordId: string) {
if (!userId && !recordId) return
try {
const res = await getMeasureHistory({ userId, recordId })
this.applyResult(res.result)
} catch {
// 请求层已弹错误提示
}
},
applyResult(data: MeasureHistoryVO) {
const map = toMap(data.bodyDataList)
const isEight = data.scaleType === 1 || data.scaleType === 5
// 头部
const hasWeight = data.weight != null && !isNaN(data.weight)
const weight = hasWeight ? splitWeight(data.weight as number) : { int: '--', decimal: '' }
const wc = data.weightChange
const changeText =
wc != null && wc !== 0 ? `较上次${wc > 0 ? '+' : ''}${wc}kg` : ''
const header = {
date: data.examTime || '--',
weightInt: weight.int,
weightDecimal: weight.decimal,
bmiText: data.bmiType ? `BMI${data.bmiType}` : '',
changeText,
}
// 基础指标(2 行 x 3 列,第 6 格留空保持布局)
const basicKeys = ['ppWeightKg', 'ppBMI', 'ppFat', 'ppBodyScore', 'ppBodyHealth']
const basicCells: BasicCell[] = basicKeys.map((k) => {
const d = map[k]
if (!d) return { ...EMPTY_BASIC_CELL }
return { label: d.bodyParamName || k, value: d.currentValue ?? '', unit: d.unit || '' }
})
basicCells.push({ ...EMPTY_BASIC_CELL })
const basicRows = [basicCells.slice(0, 3), basicCells.slice(3, 6)]
// 身体成分
const bodyComposition = {
title: '身体成分',
rows: buildRows(map, [
'ppWaterPercentage',
'ppWaterKg',
'ppProteinPercentage',
'ppProteinKg',
'ppBodyfatKg',
'ppMuscleKg',
'ppMusclePercentage',
'ppBodySkeletalKg',
'ppBodySkeletal',
'ppBoneKg',
]),
}
// 脂肪分析
const fatAnalysis = {
title: '脂肪分析',
rows: buildRows(map, [
'ppVisceralFat',
'ppBodyFatSubCutPercentage',
'ppBodyFatSubCutKg',
]),
}
// 体态管理(理想体重 key 随电极类型变化,推测腰臀比为 8 电极专属)
const idealWeightKey = isEight ? 'ppBodyStandardWeightKg' : 'ppIdealWeightKg'
const physiqueKeys = [idealWeightKey, 'ppLoseFatWeightKg', 'ppBodyAge', 'ppBodyType']
if (isEight) physiqueKeys.push('ppWHR')
const physique = { title: '体态管理', rows: buildRows(map, physiqueKeys) }
// 体重管控
const weightControl = {
title: '体重管控',
rows: buildRows(map, [
'ppControlWeightKg',
'ppFatControlKg',
'ppBodyMuscleControl',
]),
}
// 8 电极专属卡片
const cellWater = isEight
? { title: '细胞液分析', rows: buildRows(map, ['ppWaterICWKg', 'ppWaterECWKg']) }
: { title: '细胞液分析', rows: [] }
const advancedComposition = isEight
? {
title: '进阶成分',
rows: buildRows(map, ['ppMineralKg', 'ppCellMassKg', 'ppSmi']),
}
: { title: '进阶成分', rows: [] }
// 节段两列一行:[[左臂,右臂],[左腿,右腿],[躯干]]
const chunkParts = (parts: BodyPart[]): BodyPart[][] => [
[parts[0], parts[1]],
[parts[2], parts[3]],
[parts[4]],
]
const fatPartLabels: [string, string, string][] = [
['左臂', 'ppBodyFatKgLeftArm', 'ppBodyFatRateLeftArm'],
['右臂', 'ppBodyFatKgRightArm', 'ppBodyFatRateRightArm'],
['左腿', 'ppBodyFatKgLeftLeg', 'ppBodyFatRateLeftLeg'],
['右腿', 'ppBodyFatKgRightLeg', 'ppBodyFatRateRightLeg'],
['躯干', 'ppBodyFatKgTrunk', 'ppBodyFatRateTrunk'],
]
const bodyFat = isEight
? {
title: '躯干/四肢脂肪',
rows: chunkParts(
fatPartLabels.map(([label, massKey, rateKey]) =>
buildPart(map, label, massKey, rateKey),
),
),
}
: { title: '躯干/四肢脂肪', rows: [] }
const musclePartLabels: [string, string, string][] = [
['左臂', 'ppMuscleKgLeftArm', 'ppMuscleRateLeftArm'],
['右臂', 'ppMuscleKgRightArm', 'ppMuscleRateRightArm'],
['左腿', 'ppMuscleKgLeftLeg', 'ppMuscleRateLeftLeg'],
['右腿', 'ppMuscleKgRightLeg', 'ppMuscleRateRightLeg'],
['躯干', 'ppMuscleKgTrunk', 'ppMuscleRateTrunk'],
]
const bodyMuscle = isEight
? {
title: '躯干/四肢肌肉',
rows: chunkParts(
musclePartLabels.map(([label, massKey, rateKey]) =>
buildPart(map, label, massKey, rateKey),
),
),
}
: { title: '躯干/四肢肌肉', rows: [] }
this.setData({
header,
bmiCircleLeft: calcBmiLeft(data.bmi),
basicRows,
bodyComposition,
fatAnalysis,
physique,
weightControl,
cellWater,
advancedComposition,
bodyFat,
bodyMuscle,
})
},
// 滚动时导航栏背景渐显
onScroll(e: WechatMiniprogram.ScrollViewScroll) {
const scrollTop = e.detail.scrollTop
const opacity = Math.min(Math.max(scrollTop / 300, 0), 1)
this.setData({
navBgColor: `rgba(255,255,255,${opacity})`,
})
},
})