199 lines
5.7 KiB
Vue
199 lines
5.7 KiB
Vue
<template>
|
|
<view class="min-h-screen flex flex-col gap-3 bg-[#f2f4f3]">
|
|
<view class="border-b border-gray-100 bg-white px-4 pb-3">
|
|
<view class="mt-2 flex items-center justify-between">
|
|
<view class="flex flex-col">
|
|
<text class="text-lg text-gray-800 font-semibold">
|
|
{{ greeting }}
|
|
</text>
|
|
<text class="mt-0.5 text-xs text-gray-400">
|
|
{{ today }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<wd-card title="📊 今日概览" custom-class="!mx-2 !mb-0">
|
|
<view class="grid grid-cols-2 gap-3">
|
|
<view
|
|
v-for="item in overviewItems"
|
|
:key="item.label"
|
|
class="rounded-xl bg-[#f2f4f3] p-3 shadow-sm"
|
|
>
|
|
<view class="flex items-center gap-2">
|
|
<view class="h-8 w-8 flex items-center justify-center rounded-full" :class="item.bg">
|
|
<text class="text-sm">
|
|
{{ item.icon }}
|
|
</text>
|
|
</view>
|
|
<text class="text-xs text-gray-400">
|
|
{{ item.label }}
|
|
</text>
|
|
</view>
|
|
<text class="mt-2 block text-xl text-gray-800 font-bold">
|
|
{{ item.value }}
|
|
</text>
|
|
<text class="mt-1 text-xs text-gray-400">
|
|
{{ item.sub }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</wd-card>
|
|
|
|
<!-- 今日计划 -->
|
|
<wd-card title="🎯 今日计划" custom-class="!mx-2 !mb-0">
|
|
<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="success" 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 />
|
|
<bp-popup />
|
|
<glucose-popup />
|
|
<temp-popup />
|
|
|
|
<wd-fab v-if="recordStore.showFab" type="primary" :expandable="false" @click="handelFabClick" />
|
|
</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 BpPopup from "@/components/Home/BpPopup.vue";
|
|
import GlucosePopup from "@/components/Home/GlucosePopup.vue";
|
|
import TempPopup from "@/components/Home/TempPopup.vue";
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import { useRecordStore } from '@/store/record'
|
|
import type { IHealthRecord } from '@/utils/record'
|
|
import { getDateStr, getDayRecords } from '@/utils/record'
|
|
|
|
defineOptions({
|
|
name: 'Home',
|
|
})
|
|
|
|
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}`
|
|
})
|
|
|
|
onMounted(() => {
|
|
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 bp = 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)
|
|
|
|
return [
|
|
{
|
|
icon: '⚖️',
|
|
label: '体重',
|
|
value: weight ? `${weight.value} kg` : '未记录',
|
|
sub: '',
|
|
bg: 'bg-[#E89E3A]',
|
|
},
|
|
{
|
|
icon: '😴',
|
|
label: '睡眠',
|
|
value: sleep ? `${sleep.duration}h` : '未记录',
|
|
sub: '',
|
|
bg: 'bg-[#2E5B8C]',
|
|
},
|
|
{
|
|
icon: '🏃',
|
|
label: '运动',
|
|
value: sportTotal > 0 ? `${sportTotal}min` : '0min',
|
|
sub: sportTotal >= 30 ? '已达标' : '目标 30min',
|
|
bg: 'bg-[#3A7C5B]',
|
|
},
|
|
{
|
|
icon: '❤️',
|
|
label: '血压',
|
|
value: bp ? `${bp.systolic}/${bp.diastolic}` : '未记录',
|
|
sub: '',
|
|
bg: 'bg-[#C04A3C]',
|
|
},
|
|
]
|
|
})
|
|
|
|
// 今日计划
|
|
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) => {
|
|
const hour = new Date(r.startTime).getHours()
|
|
return hour <= 23
|
|
})
|
|
const hasBp = list.some(r => r.type === 'bloodPressure')
|
|
|
|
return [
|
|
{ icon: '⚖️', label: '记录体重', done: hasWeight },
|
|
{ icon: '❤️', label: '测量血压', done: hasBp },
|
|
{ icon: '🏃', label: '运动30分钟', done: sportTotal >= 30 },
|
|
{ icon: '🛏️', label: '23:00 前入睡', done: sleepOnTime },
|
|
]
|
|
})
|
|
|
|
function handelFabClick() {
|
|
recordStore.showRecordPopup = true
|
|
recordStore.showFab = false
|
|
}
|
|
</script>
|