Files
health-mp/src/pages/index/index.vue

295 lines
9.1 KiB
Vue
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.

<template>
<view class="min-h-screen flex flex-col bg-gradient-to-b from-[#a78bfa] to-[#f3e8ff]">
<view class="self-center py-3 flex items-center gap-2 text-gray-800">
<wd-img :width="40" :height="40" :src="imgURL" />
<view class="flex min-w-0 flex-col">
<text class="truncate text-lg text-white font-semibold">
{{ greeting }}
</text>
<text class="mt-0.5 text-xs text-white/70">
{{ today }} · 记录每一次改变
</text>
</view>
</view>
<wd-card title="📊 今日概览">
<view class="grid grid-cols-2 gap-3">
<view
v-for="item in overviewItems"
:key="item.label"
class="min-h-[80px] relative rounded-2xl bg-[#f8faf7] px-3.5 py-3 shadow-sm border border-[#eef0ef] flex flex-col justify-between"
>
<view class="flex items-center justify-between">
<view class="flex items-center gap-1.5">
<text class="text-lg">{{ item.icon }}</text>
<text class="text-sm text-gray-400 font-medium">{{ item.label }}</text>
</view>
<wd-button v-if="item.value === '未记录'"
size="mini" round icon="plus"
:plain="true" type="primary"
@click="addClick(item.type)"/>
</view>
<text class="mt-2 text-base text-gray-800 font-bold tracking-tight">
{{ item.value }}
</text>
<view class="mt-auto pt-1.5">
<text class="text-sm text-gray-400">{{ item.sub }}</text>
</view>
</view>
</view>
</wd-card>
<!-- 今日计划 -->
<wd-card title="🎯 今日计划">
<wd-empty v-if="plans.length === 0" icon="no-content" tip="暂无计划" />
<view v-for="(plan, idx) in plans" :key="idx">
<view class="flex items-center justify-between py-2.5">
<view class="flex items-center gap-2.5">
<text class="text-lg">{{ plan.icon }}</text>
<text class="text-sm">{{ plan.label }}</text>
</view>
<wd-tag v-if="plan.done" type="primary" round>
已完成
</wd-tag>
<wd-tag v-else type="danger" round>
未完成
</wd-tag>
</view>
<view v-if="idx < plans.length - 1" class="h-1px w-full bg-[#f2f4f3]" />
</view>
</wd-card>
<record-popup />
<weight-popup />
<sleep-popup />
<sport-popup />
<blood-pressure-popup />
<blood-sugar-popup />
<temperature-popup />
<liu-drag-button v-if="recordStore.showFab" @clickBtn="handelFabClick">
<wd-icon name="plus" size="24px"></wd-icon>
</liu-drag-button>
</view>
</template>
<script lang="ts" setup>
import RecordPopup from "@/components/Home/RecordPopup.vue";
import WeightPopup from "@/components/Home/WeightPopup.vue";
import SleepPopup from "@/components/Home/SleepPopup.vue";
import SportPopup from "@/components/Home/SportPopup.vue";
import BloodPressurePopup from "@/components/Home/BloodPressurePopup.vue";
import BloodSugarPopup from "@/components/Home/BloodSugarPopup.vue";
import TemperaturePopup from "@/components/Home/TemperaturePopup.vue";
import { ref, computed, watch } from 'vue'
import { useRecordStore } from '@/store/record'
import {getDateStr, getDayRecords, getTimeStr} from '@/utils/record'
import type { IHealthRecord } from "@/type/record";
import { onShow } from "@dcloudio/uni-app";
import { getOverview, getPlan, getProfile } from "@/utils/profile";
import appLogo from '@/static/logo.png'
import type { IOverview, IPlan } from "@/type/profile";
defineOptions({
name: 'Home',
})
const imgURL = appLogo
const recordStore = useRecordStore()
const todayRecords = ref<IHealthRecord[]>([])
const now = new Date()
const hour = now.getHours()
const greeting = computed(() => {
if (hour < 6)
return '夜深了 🌙'
if (hour < 12)
return '早上好 ☀️'
if (hour < 18)
return '下午好 👋'
return '晚上好 ✨'
})
const today = computed(() => {
const m = now.getMonth() + 1
const d = now.getDate()
const w = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()]
return `${m}${d}日 周${w}`
})
onShow(() => {
todayRecords.value = getDayRecords(getDateStr(Date.now()))
})
watch(() => recordStore.isRecord, () => {
todayRecords.value = getDayRecords(getDateStr(Date.now()))
})
// 今日概览
const overviewItems = computed(() => {
const list = todayRecords.value
// 体重:取最新一条
const weight = list.filter(r => r.type === 'weight').pop()
// 血压:取最新一条
const bloodPressure = list.filter(r => r.type === 'bloodPressure').pop()
// 睡眠:取最新一条
const sleep = list.filter(r => r.type === 'sleep').pop()
// 运动:全部累加
const sports = list.filter(r => r.type === 'sport')
const sportTotal = sports.reduce((sum, r) => sum + (r.duration || 0), 0)
// 血糖:取最新一条
const bloodSugar = list.filter(r => r.type === 'bloodSugar').pop()
// 体温:取最新一条
const temperature = list.filter(r => r.type === 'temperature').pop()
const overviewList = getOverview()
const result = [] as IOverview[]
overviewList.forEach((item) => {
if (item === 'weight') {
const profile = getProfile()
result.push({
type: 'weight',
icon: '⚖️',
label: '体重',
value: weight ? `${weight.value} kg` : '未记录',
sub: weight ? profile.height ? `BMI${(weight.value / Math.pow(profile.height / 100, 2)).toFixed(2)} ` : '身高未知' : '',
})
}
else if (item === 'sleep') {
result.push({
type: 'sleep',
icon: '😴',
label: '睡眠',
value: sleep ? `${sleep.duration}h` : '未记录',
sub: sleep ? `${getTimeStr(sleep.startTime)} ~ ${getTimeStr(sleep.endTime)}` : '',
})
}
else if (item === 'sport') {
result.push({
type: 'sport',
icon: '🏃',
label: '运动',
value: sportTotal > 0 ? `${sportTotal}min` : '未记录',
sub: sportTotal >= 30 ? '已达标' : '目标 30min',
})
}
else if (item === 'bloodPressure') {
result.push({
type: 'bloodPressure',
icon: '❤️',
label: '血压',
value: bloodPressure ? `${bloodPressure.systolic}/${bloodPressure.diastolic}` : '未记录',
sub: bloodPressure ? `心率:${bloodPressure.heartRate}` : '',
})
}
else if (item === 'bloodSugar') {
result.push({
type: 'bloodSugar',
icon: '🩸',
label: '血糖',
value: bloodSugar ? `${bloodSugar.value}mmol/L` : '未记录',
sub: bloodSugar ? `${bloodSugar.period}` : '',
})
}
else if (item === 'temperature') {
result.push({
type: 'temperature',
icon: '🌡️',
label: '体温',
value: temperature ? `${temperature.value}` : '未记录',
sub: temperature ? `${temperature.location}` : '',
})
}
})
return result
})
// 今日计划
const plans = computed(() => {
const list = todayRecords.value
const hasWeight = list.some(r => r.type === 'weight')
const sportTotal = list
.filter(r => r.type === 'sport')
.reduce((sum, r) => sum + (r.duration || 0), 0)
const sleepOnTime = list
.filter(r => r.type === 'sleep')
.some((r) => r.duration >= 8)
const hasBloodPressure = list.some(r => r.type === 'bloodPressure')
const hasBloodSugar = list.some(r => r.type === 'bloodSugar')
const hasTemperature = list.some(r => r.type === 'temperature')
const planList = getPlan()
const result = [] as IPlan[]
planList.forEach((item) => {
if (item === 'weight') {
result.push({ icon: '⚖️', label: '记录体重', done: hasWeight })
}
else if (item === 'sleep') {
result.push({ icon: '😴️', label: '睡眠时长8小时', done: sleepOnTime })
}
else if (item === 'sport') {
result.push({ icon: '🏃', label: '运动30分钟', done: sportTotal >= 30 })
}
else if (item === 'bloodPressure') {
result.push({ icon: '❤️', label: '测量血压', done: hasBloodPressure })
}
else if (item === 'bloodSugar') {
result.push({ icon: '🩸', label: '测量血糖', done: hasBloodSugar })
}
else if (item === 'temperature') {
result.push({ icon: '🌡️', label: '记录体温', done: hasTemperature })
}
})
return result
})
function addClick(type: string) {
switch (type) {
case 'weight':
recordStore.showFab = false
recordStore.openWeight()
break
case 'sleep':
recordStore.showFab = false
recordStore.openSleep()
break
case 'sport':
recordStore.showFab = false
recordStore.openSport()
break
case 'bloodPressure':
recordStore.showFab = false
recordStore.openBloodPressure()
break
case 'bloodSugar':
recordStore.showFab = false
recordStore.openBloodSugar()
break
case 'temperature':
recordStore.showFab = false
recordStore.openTemperature()
break
}
}
function handelFabClick() {
recordStore.showRecordPopup = true
recordStore.showFab = false
}
</script>