import { getProfile } from '@/utils/profile' export interface IHealthRecord { id: string type: string time: string date?: string [key: string]: any } export interface IRecordMeta { icon: string label: string color: string } export interface IRecordStats { label: string type: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'volcano' | 'lightblue' | 'cyan' | 'pink' | 'purple' } const MONTH_KEEP = 12 function getMonthKey(timestamp: number) { const d = new Date(timestamp) return `health_${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}` } export function getDateStr(timestamp: number) { const d = new Date(timestamp) return d.toISOString().slice(0, 10) } export function getMonthStr(timestamp: number): string { const d = new Date(timestamp) const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, '0') return `${y}-${m}` } export function getTimeStr(timestamp: number) { const d = new Date(timestamp) return d.toTimeString().slice(0, 5) } function cleanOldMonths() { const cutoff = new Date() cutoff.setMonth(cutoff.getMonth() - MONTH_KEEP) const cutoffKey = `health_${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, '0')}` const keys = wx.getStorageInfoSync().keys keys.forEach((key) => { if (key.startsWith('health_') && key < cutoffKey) { wx.removeStorageSync(key) } }) } function addRecord(type: string, data: Record, timestamp: number) { const key = getMonthKey(timestamp) const all = wx.getStorageSync(key) || {} const date = getDateStr(timestamp) if (!all[date]) all[date] = [] all[date].push({ id: Date.now() + Math.random().toString(36).slice(2, 6), time: getTimeStr(timestamp), type, ...data, }) wx.setStorageSync(key, all) } export function addWeight(value: number, note: string, timestamp: number) { addRecord('weight', { value, note }, timestamp) } export function addSleep(startTime: number, endTime: number, duration: number, note: string, timestamp: number) { addRecord('sleep', { startTime, endTime, duration, note }, timestamp) } export function addSport(category: string, duration: number, note: string, timestamp: number) { addRecord('sport', { category, duration, note }, timestamp) } export function addBloodPressure(systolic: number, diastolic: number, heartRate: number, note: string, timestamp: number) { addRecord('bloodPressure', { systolic, diastolic, heartRate, note }, timestamp) } export function addBloodSugar(value: number, period: string, note: string, timestamp: number) { addRecord('bloodSugar', { value, period, note }, timestamp) } export function addTemperature(value: number, location: string, note: string, timestamp: number) { addRecord('temperature', { value, location, note }, timestamp) } function getAllMonthKeys(): string[] { return wx.getStorageInfoSync().keys.filter(k => k.startsWith('health_')).sort() } // ─── 根据时间戳反推它属于哪个月的 key ─── function getMonthKeyForTimestamp(timestamp: number): string { const d = new Date(timestamp) return `health_${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}` } // ─── 根据 "YYYY-MM-DD" 反推月份 key ─── function getMonthKeyForDate(dateStr: string): string { const [y, m] = dateStr.split('-') return `health_${y}-${m}` } export function getDayRecords(date: string): IHealthRecord[] { const key = getMonthKeyForDate(date) const all = wx.getStorageSync(key) || {} const dayRecords = all[date] || [] return [...dayRecords].sort((a, b) => a.time.localeCompare(b.time)) } export function getMonthRecords(yearMonth: string, type?: string): IHealthRecord[] { const key = `health_${yearMonth}` const all = wx.getStorageSync(key) || {} const result: IHealthRecord[] = [] Object.keys(all).forEach((date) => { all[date].forEach((r: IHealthRecord) => { if (!type || r.type === type) { result.push({ date, ...r }) } }) }) return sortRecords(result) } // 排序:先按 date 正序,再按 time 正序 function sortRecords(records: IHealthRecord[]): IHealthRecord[] { return records.sort((a, b) => { const dateCmp = a.date.localeCompare(b.date) if (dateCmp !== 0) return dateCmp return a.time.localeCompare(b.time) }) } const META_MAP: Record = { weight: { icon: '⚖️', label: '体重', color: 'bg-[#E89E3A]' }, sleep: { icon: '😴', label: '睡眠', color: 'bg-[#2E5B8C]' }, bloodPressure: { icon: '❤️', label: '血压', color: 'bg-[#C04A3C]' }, bloodSugar: { icon: '📈', label: '血糖', color: 'bg-[#9C27B0]' }, sport: { icon: '🏃', label: '运动', color: 'bg-[#3A7C5B]' }, temperature: { icon: '🌡️', label: '体温', color: 'bg-[#D32F2F]' }, } export function getRecordMeta(type: string): IRecordMeta { return META_MAP[type] || { icon: '📋', label: '其他', color: 'bg-gray-400' } } export function getRecordValue(record: IHealthRecord): string { switch (record.type) { case 'weight': return `${record.value} kg` case 'bloodPressure': return `${record.systolic}/${record.diastolic} mmHg${record.heartRate ? ` · 心率 ${record.heartRate}` : ''}` case 'bloodSugar': return `${record.value} mmol/L · ${record.period}` case 'heartRate': return `${record.value} bpm` case 'temperature': return `${record.value} ℃${record.location ? ` · ${record.location}` : ''}` case 'sport': return `${record.category} · ${record.duration}min` case 'sleep': return `${new Date(record.startTime).toTimeString().slice(0, 5)} ~ ${new Date(record.endTime).toTimeString().slice(0, 5)}(${record.duration}h)` default: return '' } } export function getRecordStatus(record: IHealthRecord): IRecordStats { const profile = getProfile() switch (record.type) { case 'bloodPressure': if (record.systolic >= 140 || record.diastolic >= 90) { return { label: '偏高', type: 'danger' } } if (record.systolic < 90 || record.diastolic < 60) { return { label: '偏低', type: 'danger' } } return { label: '正常', type: 'primary' } case 'weight': { if (!profile.height) { return { label: '身高未知', type: 'default' } } const bmi = (record.value * 10000) / (profile.height * profile.height) if (bmi < 18.5) return { label: '偏瘦', type: 'warning' } if (bmi < 24) return { label: '正常', type: 'primary' } if (bmi < 28) return { label: '偏重', type: 'danger' } return { label: '肥胖', type: 'danger' } } case 'bloodSugar': { const period = record.period || '' let limit = 11.1 if (period.includes('空腹') || period.includes('睡前')) limit = 6.1 else if (period.includes('餐后1h')) limit = 11.1 else if (period.includes('餐后2h')) limit = 7.8 if (record.value < 3.9) return { label: '偏低', type: 'danger' } if (record.value >= limit) return { label: '偏高', type: 'danger' } return { label: '正常', type: 'primary' } } case 'heartRate': if (record.value < 60) return { label: '偏慢', type: 'warning' } if (record.value <= 100) return { label: '正常', type: 'primary' } return { label: '偏快', type: 'warning' } case 'temperature': if (record.value < 36.1) return { label: '偏低', type: 'warning' } if (record.value <= 37.2) return { label: '正常', type: 'primary' } return { label: '偏高', type: 'danger' } case 'sport': return record.duration >= 30 ? { label: '达标', type: 'success' } : { label: '偏少', type: 'warning' } case 'sleep': if (record.duration >= 7) return { label: '充足', type: 'success' } if (record.duration >= 6) return { label: '良好', type: 'primary' } return { label: '不足', type: 'warning' } default: return { label: '未知', type: 'default' } } } const WEEKDAY = ['日', '一', '二', '三', '四', '五', '六'] export function groupByDate(records: IHealthRecord[]): Array<{ date: string weekday: string items: IHealthRecord[] }> { const map: Record = {} records.forEach((r) => { if (!r.date) return if (!map[r.date]) map[r.date] = [] map[r.date].push(r) }) return Object.keys(map) .sort((a, b) => b.localeCompare(a)) // 日期倒序,最新的在上 .map(date => ({ date, weekday: WEEKDAY[new Date(date).getDay()], items: map[date].sort((a, b) => a.time.localeCompare(b.time)), // 当天按时间正序 })) }