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 }