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