Files
health-mp/src/utils/record.ts
2026-08-24 22:58:08 +08:00

281 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, any>, 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<string, IRecordMeta> = {
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<string, IHealthRecord[]> = {}
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)), // 当天按时间正序
}))
}