Files
gym-manage/gym-manage-uniapp/common/memberInfo/bodyTestStore.js
T
2026-06-04 14:18:53 +08:00

284 lines
9.5 KiB
JavaScript

import { bodyTestMock } from './mockData.js'
function clone(value) {
return JSON.parse(JSON.stringify(value))
}
function formatRecordTime(date) {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
const h = String(date.getHours()).padStart(2, '0')
const min = String(date.getMinutes()).padStart(2, '0')
return `${y}-${m}-${d} ${h}:${min}`
}
function formatIsoDate(date) {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
function formatTime(date) {
const h = String(date.getHours()).padStart(2, '0')
const min = String(date.getMinutes()).padStart(2, '0')
return `${h}:${min}`
}
export function getDefaultBodyTestState() {
return {
settings: { ...bodyTestMock.settings },
device: { ...bodyTestMock.device },
records: clone(bodyTestMock.records)
}
}
export function mergeBodyTestState(saved) {
const defaults = getDefaultBodyTestState()
if (!saved) return defaults
return {
settings: { ...defaults.settings, ...(saved.settings || {}) },
device: { ...defaults.device, ...(saved.device || {}) },
records: saved.records?.length ? saved.records : defaults.records
}
}
export function getLatestBodyTestRecord(store) {
const records = store.bodyTest?.records || []
return records.length ? { ...records[0] } : null
}
export function getBodyTestRecordById(store, id) {
const numId = Number(id)
const record = (store.bodyTest?.records || []).find((item) => item.id === numId)
return record ? { ...record } : null
}
export function getBodyTestHistory(store, year) {
let list = (store.bodyTest?.records || []).map((item) => ({ ...item }))
if (year && year !== 'all') {
list = list.filter((r) => r.date.startsWith(String(year)))
}
return list
}
export function getBodyTestChangeBadge(record, previous) {
if (!previous?.metrics || !record?.metrics) return null
const diff = Math.round((record.metrics.bodyFat - previous.metrics.bodyFat) * 10) / 10
if (diff === 0) return null
const sign = diff > 0 ? '+' : ''
return { key: 'bodyFat', text: `体脂率${sign}${diff}%`, good: diff < 0 }
}
export function getBodyTestYears(store) {
const years = new Set((store.bodyTest?.records || []).map((r) => r.date.slice(0, 4)))
return ['all', ...Array.from(years).sort().reverse()]
}
export function computeGrade(score) {
if (score >= 90) return { grade: 'A', gradeLabel: '优秀' }
if (score >= 80) return { grade: 'B+', gradeLabel: '良好' }
if (score >= 70) return { grade: 'B', gradeLabel: '中等' }
if (score >= 60) return { grade: 'C', gradeLabel: '一般' }
return { grade: 'D', gradeLabel: '需改善' }
}
export function computeScore(metrics) {
const bmi = metrics.bmi || 22
const bodyFat = metrics.bodyFat || 25
const muscle = metrics.muscleMass || 22
const bmiScore = bmi >= 18.5 && bmi <= 24 ? 90 : bmi >= 17 && bmi <= 27 ? 75 : 60
const fatScore = bodyFat <= 22 ? 92 : bodyFat <= 26 ? 80 : bodyFat <= 30 ? 68 : 55
const muscleScore = muscle >= 22 ? 88 : muscle >= 20 ? 76 : 62
return Math.round((bmiScore + fatScore + muscleScore) / 3)
}
export function computeChanges(current, previous) {
if (!previous?.metrics) return {}
const keys = ['weight', 'bmi', 'bodyFat', 'muscleMass', 'visceralFat', 'bmr', 'bodyWater', 'boneMass']
const changes = {}
keys.forEach((key) => {
const cur = Number(current.metrics[key])
const prev = Number(previous.metrics[key])
if (Number.isFinite(cur) && Number.isFinite(prev)) {
const diff = Math.round((cur - prev) * 10) / 10
changes[key] = diff
}
})
return changes
}
export function formatChangeValue(key, diff, unitSystem = 'metric') {
if (diff === undefined || diff === null) return '--'
const sign = diff > 0 ? '+' : ''
const units = {
weight: unitSystem === 'metric' ? 'kg' : 'lb',
bodyFat: '%',
muscleMass: 'kg',
bmi: '',
visceralFat: '级',
bmr: 'kcal',
bodyWater: '%',
boneMass: 'kg'
}
const unit = units[key] || ''
return `${sign}${diff}${unit}`
}
export function buildBodyReportSummary(record, previous) {
if (!record) {
return {
date: '--',
weight: '--',
bmi: '--',
bodyFat: '--',
bmr: '--',
status: '暂无数据',
change: '--'
}
}
const changes = computeChanges(record, previous)
const weightChange = changes.weight
let changeText = '--'
if (weightChange !== undefined) {
const sign = weightChange > 0 ? '+' : ''
changeText = `${sign}${weightChange}kg`
}
return {
date: record.date,
weight: String(record.metrics.weight),
bmi: String(record.metrics.bmi),
bodyFat: `${record.metrics.bodyFat}%`,
bmr: String(record.metrics.bmr),
status: record.status,
change: changeText,
recordId: record.id
}
}
export function getBodyTestTrendData(store, metricKey, limit = 6) {
const records = [...(store.bodyTest?.records || [])].reverse().slice(-limit)
return records.map((item) => ({
id: item.id,
date: item.date,
label: item.date.slice(5),
value: Number(item.metrics[metricKey]) || 0
}))
}
export function getCompareData(store, idA, idB) {
const a = getBodyTestRecordById(store, idA)
const b = getBodyTestRecordById(store, idB)
if (!a || !b) return null
const keys = ['weight', 'bmi', 'bodyFat', 'muscleMass', 'visceralFat', 'bmr']
const metrics = keys.map((key) => ({
key,
label: bodyTestMock.metricDefs.find((m) => m.key === key)?.label || key,
valueA: a.metrics[key],
valueB: b.metrics[key],
diff: Math.round((a.metrics[key] - b.metrics[key]) * 10) / 10
}))
return { recordA: a, recordB: b, metrics }
}
export function getRecommendedCourses(record) {
const ids = record?.recommendedCourseIds || []
return bodyTestMock.recommendedCourses.filter((c) => ids.includes(c.id))
}
export function updateBodyTestSettings(store, patch) {
store.bodyTest.settings = { ...store.bodyTest.settings, ...patch }
return store
}
export function connectBodyTestDevice(store) {
store.bodyTest.device = {
...store.bodyTest.device,
connected: true,
battery: Math.min(100, (store.bodyTest.device.battery || 80) + Math.floor(Math.random() * 5)),
lastConnected: formatRecordTime(new Date())
}
return store
}
export function disconnectBodyTestDevice(store) {
store.bodyTest.device = { ...store.bodyTest.device, connected: false }
return store
}
function nextRecordId(records) {
return (records || []).reduce((max, item) => Math.max(max, item.id || 0), 0) + 1
}
/** 模拟一次完整体测并写入记录 */
export function saveSimulatedBodyTestRecord(store, finalMetrics) {
const now = new Date()
const previous = getLatestBodyTestRecord(store)
const metrics = { ...finalMetrics }
const heightCm = Number(store.profile?.height) || 165
const heightM = heightCm / 100
metrics.bmi = Math.round((metrics.weight / (heightM * heightM)) * 10) / 10
const score = computeScore(metrics)
const { grade, gradeLabel } = computeGrade(score)
const status = score >= 80 ? '比较健康' : score >= 70 ? '需关注' : '建议改善'
const radar = {
weight: Math.min(95, Math.round(score * 0.9 + Math.random() * 5)),
bodyFat: Math.min(95, Math.round(100 - metrics.bodyFat * 2.5)),
muscle: Math.min(95, Math.round(metrics.muscleMass * 3.2)),
bone: Math.min(95, Math.round(metrics.boneMass * 32)),
water: Math.min(95, Math.round(metrics.bodyWater * 1.4)),
bmr: Math.min(95, Math.round(metrics.bmr / 16))
}
const record = {
id: nextRecordId(store.bodyTest.records),
date: formatIsoDate(now),
time: formatTime(now),
score,
grade,
gradeLabel,
status,
metrics,
radar,
bodySegments: clone(bodyTestMock.records[0].bodySegments),
advice: clone(bodyTestMock.records[0].advice),
recommendedCourseIds: [1, 2]
}
if (previous) {
record.changes = computeChanges(record, previous)
}
store.bodyTest.records.unshift(record)
store.bodyReport = buildBodyReportSummary(record, previous)
return record
}
/** 测量过程实时数据插值 */
export function interpolateMeasuringMetrics(progress, profile) {
const baseWeight = Number(profile?.weight) || 64
const target = {
weight: baseWeight - 0.3 + Math.random() * 0.2,
bodyFat: 24.5 + Math.random() * 0.8,
muscleMass: 22.4 + Math.random() * 0.3,
visceralFat: 6,
bmr: 1380 + Math.floor(Math.random() * 20),
bodyWater: 52.5 + Math.random(),
boneMass: 2.4,
protein: 16.2
}
const ratio = Math.min(1, progress / 100)
return {
weight: Math.round((baseWeight + (target.weight - baseWeight) * ratio) * 10) / 10,
bodyFat: Math.round((26 + (target.bodyFat - 26) * ratio) * 10) / 10,
muscleMass: Math.round((21.5 + (target.muscleMass - 21.5) * ratio) * 10) / 10,
bmr: Math.round(1340 + (target.bmr - 1340) * ratio),
bodyWater: Math.round((51 + (target.bodyWater - 51) * ratio) * 10) / 10
}
}
export { bodyTestMock }