会员个人中心页面初步完成
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
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 }
|
||||
Reference in New Issue
Block a user