feat: 增加删除记录功能
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
||||||
|
import { getOverview, saveOverview } from "@/utils/profile";
|
||||||
|
|
||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
|
if (getOverview().length === 0) {
|
||||||
|
saveOverview(['weight', 'sleep', 'sport', 'bloodPressure'])
|
||||||
|
}
|
||||||
console.log("App Launch");
|
console.log("App Launch");
|
||||||
});
|
});
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
|||||||
54
src/components/Profile/OverviewPopup.vue
Normal file
54
src/components/Profile/OverviewPopup.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
v-model="profileStore.showOverviewPopup"
|
||||||
|
position="bottom"
|
||||||
|
:safe-area-inset-bottom="true"
|
||||||
|
custom-style="border-radius: 32rpx 32rpx 0 0; padding: 40rpx 32rpx"
|
||||||
|
>
|
||||||
|
<view class="mb-4 text-center text-base text-gray-700 font-semibold">
|
||||||
|
今日概览(最多4个)
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wd-checkbox-group v-model="profileStore.overviewList" type="button" :max="4">
|
||||||
|
<wd-checkbox name="weight">体重</wd-checkbox>
|
||||||
|
<wd-checkbox name="sleep">睡眠</wd-checkbox>
|
||||||
|
<wd-checkbox name="sport">运动</wd-checkbox>
|
||||||
|
<wd-checkbox name="bloodPressure">血压</wd-checkbox>
|
||||||
|
<wd-checkbox name="bloodSugar">血糖</wd-checkbox>
|
||||||
|
<wd-checkbox name="temperature">体温</wd-checkbox>
|
||||||
|
</wd-checkbox-group>
|
||||||
|
|
||||||
|
<view class="flex w-full items-center justify-evenly">
|
||||||
|
<wd-button type="info" @click="cancelClick">
|
||||||
|
取消
|
||||||
|
</wd-button>
|
||||||
|
<wd-button type="primary" @click="saveClick">
|
||||||
|
保存
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useProfileStore } from '@/store/profile'
|
||||||
|
import { countRecordsByMonth, getTotalCount } from "@/utils/record";
|
||||||
|
import {saveOverview} from "@/utils/profile";
|
||||||
|
|
||||||
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
|
function cancelClick() {
|
||||||
|
profileStore.showOverviewPopup = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveClick() {
|
||||||
|
if (profileStore.overviewList.length < 4) {
|
||||||
|
wx.showToast({
|
||||||
|
title: '至少添加 4 项指标',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saveOverview(profileStore.overviewList)
|
||||||
|
profileStore.showOverviewPopup = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -28,12 +28,6 @@
|
|||||||
<text class="shrink-0 text-sm text-gray-400">cm</text>
|
<text class="shrink-0 text-sm text-gray-400">cm</text>
|
||||||
</view>
|
</view>
|
||||||
</wd-form-item>
|
</wd-form-item>
|
||||||
<wd-form-item title="体重" prop="weight">
|
|
||||||
<view class="flex items-center gap-2">
|
|
||||||
<wd-input v-model.number="profileStore.profileForm.weight" type="number" placeholder="请输入体重" />
|
|
||||||
<text class="shrink-0 text-sm text-gray-400">Kg</text>
|
|
||||||
</view>
|
|
||||||
</wd-form-item>
|
|
||||||
</wd-form>
|
</wd-form>
|
||||||
|
|
||||||
<view class="flex w-full items-center justify-evenly">
|
<view class="flex w-full items-center justify-evenly">
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="min-h-screen flex flex-col gap-3 bg-[#f2f4f3]">
|
<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="bg-gradient-to-r from-[#7c3aed] to-[#c084fc] p-2 flex items-center justify-between text-white">
|
||||||
<view class="mt-2 flex items-center justify-between">
|
<view class="flex flex-col">
|
||||||
<view class="flex flex-col">
|
<text class="text-lg text-white font-semibold">
|
||||||
<text class="text-lg text-gray-800 font-semibold">
|
{{ greeting }}
|
||||||
{{ greeting }}
|
</text>
|
||||||
</text>
|
<text class="mt-0.5 text-xs text-white">
|
||||||
<text class="mt-0.5 text-xs text-gray-400">
|
{{ today }}
|
||||||
{{ today }}
|
</text>
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<wd-img :width="40" :height="40" :src="imgURL" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<wd-card title="📊 今日概览" custom-class="!mx-2 !mb-0">
|
<wd-card title="📊 今日概览" custom-class="!mx-2 !mb-0">
|
||||||
@@ -18,22 +18,16 @@
|
|||||||
<view
|
<view
|
||||||
v-for="item in overviewItems"
|
v-for="item in overviewItems"
|
||||||
:key="item.label"
|
:key="item.label"
|
||||||
class="rounded-xl bg-[#f2f4f3] p-3 shadow-sm"
|
class="rounded-xl bg-[#f2f4f3] p-3 shadow-sm flex flex-col gap-1"
|
||||||
>
|
>
|
||||||
<view class="flex items-center gap-2">
|
<view class="flex items-center gap-2">
|
||||||
<view class="h-8 w-8 flex items-center justify-center rounded-full" :class="item.bg">
|
<text>{{ item.icon }}</text>
|
||||||
<text class="text-sm">
|
<text class="text-gray-400">{{ item.label }}</text>
|
||||||
{{ item.icon }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<text class="text-xs text-gray-400">
|
|
||||||
{{ item.label }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
<text class="mt-2 block text-xl text-gray-800 font-bold">
|
<text class="block text-xl text-gray-800 font-bold">
|
||||||
{{ item.value }}
|
{{ item.value }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mt-1 text-xs text-gray-400">
|
<text class="text-gray-400">
|
||||||
{{ item.sub }}
|
{{ item.sub }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -83,9 +77,14 @@ import GlucosePopup from "@/components/Home/GlucosePopup.vue";
|
|||||||
import TempPopup from "@/components/Home/TempPopup.vue";
|
import TempPopup from "@/components/Home/TempPopup.vue";
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useRecordStore } from '@/store/record'
|
import { useRecordStore } from '@/store/record'
|
||||||
import { countRecordsByMonth, getDateStr, getDayRecords } from '@/utils/record'
|
import {getDateStr, getDayRecords, getTimeStr} from '@/utils/record'
|
||||||
import type { IHealthRecord } from "@/type/record";
|
import type { IHealthRecord } from "@/type/record";
|
||||||
import {onShow} from "@dcloudio/uni-app";
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
|
import appLogo from '@/static/logo.png'
|
||||||
|
import {getOverview, getProfile} from "@/utils/profile";
|
||||||
|
import type {IOverview} from "@/type/profile";
|
||||||
|
|
||||||
|
const imgURL = appLogo
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
@@ -130,43 +129,73 @@ const overviewItems = computed(() => {
|
|||||||
// 体重:取最新一条
|
// 体重:取最新一条
|
||||||
const weight = list.filter(r => r.type === 'weight').pop()
|
const weight = list.filter(r => r.type === 'weight').pop()
|
||||||
// 血压:取最新一条
|
// 血压:取最新一条
|
||||||
const bp = list.filter(r => r.type === 'bloodPressure').pop()
|
const bloodPressure = list.filter(r => r.type === 'bloodPressure').pop()
|
||||||
// 睡眠:取最新一条
|
// 睡眠:取最新一条
|
||||||
const sleep = list.filter(r => r.type === 'sleep').pop()
|
const sleep = list.filter(r => r.type === 'sleep').pop()
|
||||||
// 运动:全部累加
|
// 运动:全部累加
|
||||||
const sports = list.filter(r => r.type === 'sport')
|
const sports = list.filter(r => r.type === 'sport')
|
||||||
const sportTotal = sports.reduce((sum, r) => sum + (r.duration || 0), 0)
|
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()
|
||||||
|
|
||||||
return [
|
const overviewList = getOverview()
|
||||||
{
|
const result = [] as IOverview[]
|
||||||
icon: '⚖️',
|
|
||||||
label: '体重',
|
overviewList.forEach((item) => {
|
||||||
value: weight ? `${weight.value} kg` : '未记录',
|
if (item === 'weight') {
|
||||||
sub: '',
|
const profile = getProfile()
|
||||||
bg: 'bg-[#E89E3A]',
|
result.push({
|
||||||
},
|
icon: '⚖️',
|
||||||
{
|
label: '体重',
|
||||||
icon: '😴',
|
value: weight ? `${weight.value} kg` : '未记录',
|
||||||
label: '睡眠',
|
sub: weight ? profile.height ? `BMI:${(weight.value / Math.pow(profile.height / 100, 2)).toFixed(2)} ` : '身高未知' : '',
|
||||||
value: sleep ? `${sleep.duration}h` : '未记录',
|
})
|
||||||
sub: '',
|
}
|
||||||
bg: 'bg-[#2E5B8C]',
|
else if (item === 'sleep') {
|
||||||
},
|
result.push({
|
||||||
{
|
icon: '😴',
|
||||||
icon: '🏃',
|
label: '睡眠',
|
||||||
label: '运动',
|
value: sleep ? `${sleep.duration}h` : '未记录',
|
||||||
value: sportTotal > 0 ? `${sportTotal}min` : '0min',
|
sub: sleep ? `${getTimeStr(sleep.startTime)} ~ ${getTimeStr(sleep.endTime)}` : '',
|
||||||
sub: sportTotal >= 30 ? '已达标' : '目标 30min',
|
})
|
||||||
bg: 'bg-[#3A7C5B]',
|
}
|
||||||
},
|
else if (item === 'sport') {
|
||||||
{
|
result.push({
|
||||||
icon: '❤️',
|
icon: '🏃',
|
||||||
label: '血压',
|
label: '运动',
|
||||||
value: bp ? `${bp.systolic}/${bp.diastolic}` : '未记录',
|
value: sportTotal > 0 ? `${sportTotal}min` : '0min',
|
||||||
sub: '',
|
sub: sportTotal >= 30 ? '已达标' : '目标 30min',
|
||||||
bg: 'bg-[#C04A3C]',
|
})
|
||||||
},
|
}
|
||||||
]
|
else if (item === 'bloodPressure') {
|
||||||
|
result.push({
|
||||||
|
icon: '❤️',
|
||||||
|
label: '血压',
|
||||||
|
value: bloodPressure ? `${bloodPressure.systolic}/${bloodPressure.diastolic}` : '未记录',
|
||||||
|
sub: bloodPressure ? `心率:${bloodPressure.heartRate}` : '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (item === 'bloodSugar') {
|
||||||
|
result.push({
|
||||||
|
icon: '🩸',
|
||||||
|
label: '血糖',
|
||||||
|
value: bloodSugar ? `${bloodSugar.value}mmol/L` : '未记录',
|
||||||
|
sub: bloodSugar ? `${bloodSugar.period}` : '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (item === 'temperature') {
|
||||||
|
result.push({
|
||||||
|
icon: '🌡️',
|
||||||
|
label: '体温',
|
||||||
|
value: temperature ? `${temperature.value}℃` : '未记录',
|
||||||
|
sub: temperature ? `${temperature.location}` : '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
// 今日计划
|
// 今日计划
|
||||||
|
|||||||
@@ -13,64 +13,57 @@
|
|||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<wd-segmented v-model:value="current" :options="list"/>
|
<wd-segmented
|
||||||
|
v-model:value="currentSegment"
|
||||||
|
:options="segmentList"
|
||||||
|
@change="handleChangeSegment"
|
||||||
|
>
|
||||||
|
<template #label="{ option }">
|
||||||
|
<view class="name">{{ option.payload }}</view>
|
||||||
|
</template>
|
||||||
|
</wd-segmented>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 时间轴区域 -->
|
<!-- 时间轴区域 -->
|
||||||
<view class="px-5 py-2">
|
<view class="px-5 py-2">
|
||||||
<wd-empty v-if="grouped.length === 0" icon="no-content" tip="暂无记录" />
|
<wd-empty v-if="grouped.length === 0" icon="no-content" tip="暂无记录" />
|
||||||
|
|
||||||
<!-- 外层:每个日期分组 -->
|
|
||||||
<view v-for="group in grouped" :key="group.date" class="mb-6">
|
<view v-for="group in grouped" :key="group.date" class="mb-6">
|
||||||
<!-- 日期分类标题 -->
|
|
||||||
<view class="mb-2 flex items-center gap-2 pl-1">
|
<view class="mb-2 flex items-center gap-2 pl-1">
|
||||||
<text class="text-sm text-gray-500 font-medium">{{ group.date }}</text>
|
<text class="text-sm text-gray-500 font-medium">{{ group.date }}</text>
|
||||||
<text class="text-xs text-gray-400">周{{ group.weekday }}</text>
|
<text class="text-sm text-gray-400">周{{ group.weekday }}</text>
|
||||||
<view class="ml-2 h-[1px] flex-1 bg-gray-200" />
|
<view class="ml-2 h-[1px] flex-1 bg-gray-200" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 该日期下的时间轴 -->
|
<view
|
||||||
<view class="relative pl-6">
|
|
||||||
<view
|
|
||||||
class="absolute bottom-1 top-1 w-[2px] from-[#07c160] via-[#2E5B8C] to-[#dde3e0] bg-gradient-to-b"
|
|
||||||
style="left: 0;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<view
|
|
||||||
v-for="(item, idx) in group.items"
|
v-for="(item, idx) in group.items"
|
||||||
:key="item.id || idx"
|
:key="item.id || idx"
|
||||||
class="relative mb-5 last:mb-0"
|
class="mb-5 last:mb-0"
|
||||||
>
|
>
|
||||||
<view
|
<!-- 卡片 -->
|
||||||
class="absolute border-[2.5px] border-white rounded-full shadow-sm"
|
<view class="rounded-xl bg-white p-3.5 shadow-sm">
|
||||||
style="width: 14px; height: 14px; left: -30px; top: 0; z-index: 2;"
|
<view class="flex items-center justify-between">
|
||||||
:class="getRecordMeta(item.type).color"
|
<text class="text-sm text-gray-400 font-mono">{{ item.time }}</text>
|
||||||
/>
|
<wd-tag :type="getRecordStatus(item).type" round>
|
||||||
|
{{ getRecordStatus(item).label }}
|
||||||
|
</wd-tag>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 卡片 -->
|
<view class="mt-1.5 flex items-center gap-2">
|
||||||
<view class="rounded-xl bg-white p-3.5 shadow-sm">
|
<text>{{ getRecordMeta(item.type).icon }}</text>
|
||||||
<view class="flex items-center justify-between">
|
<text class="text-gray-700 font-medium">{{ getRecordMeta(item.type).label }}</text>
|
||||||
<text class="text-xs text-gray-400 font-mono">{{ item.time }}</text>
|
</view>
|
||||||
<wd-tag :type="getRecordStatus(item).type" round>
|
|
||||||
{{ getRecordStatus(item).label }}
|
|
||||||
</wd-tag>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="mt-1.5 flex items-center gap-2">
|
<view class="flex items-center justify-between">
|
||||||
<view
|
<text class="mt-1.5 block text-l text-gray-800 font-bold">{{ getRecordValue(item) }}</text>
|
||||||
class="h-7 w-7 flex items-center justify-center rounded-lg text-sm"
|
<wd-button v-if="!item.note" type="danger" icon="delete" size="mini" round
|
||||||
:class="getRecordMeta(item.type).color"
|
@click="deleteClick(item)"></wd-button>
|
||||||
>
|
</view>
|
||||||
<text>{{ getRecordMeta(item.type).icon }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="text-sm text-gray-700 font-medium">{{ getRecordMeta(item.type).label }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<text class="mt-1.5 block text-xl text-gray-800 font-bold">{{ getRecordValue(item) }}</text>
|
<view class="flex items-center justify-between">
|
||||||
|
<text class="mt-0.5 block text-sm text-gray-400 leading-relaxed">{{ item.note }}</text>
|
||||||
<text v-if="item.note" class="mt-0.5 block text-xs text-gray-400 leading-relaxed">
|
<wd-button v-if="item.note" type="danger" icon="delete" size="mini" round
|
||||||
{{ item.note }}
|
@click="deleteClick(item)"></wd-button>
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -91,6 +84,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import {
|
import {
|
||||||
|
deleteRecordByDate,
|
||||||
getMonthRecords,
|
getMonthRecords,
|
||||||
getMonthStr,
|
getMonthStr,
|
||||||
getRecordMeta,
|
getRecordMeta,
|
||||||
@@ -103,8 +97,21 @@ import type { IHealthRecord } from "@/type/record";
|
|||||||
|
|
||||||
const currentDate = ref(Date.now())
|
const currentDate = ref(Date.now())
|
||||||
const showDatePicker = ref(false)
|
const showDatePicker = ref(false)
|
||||||
const list = ref<string[]>(['所有', '体重', '睡眠', '运动', '血压', '血糖', '心率'])
|
|
||||||
const current = ref('所有')
|
interface SegmentOption {
|
||||||
|
value: string
|
||||||
|
payload: string
|
||||||
|
}
|
||||||
|
const segmentList = ref<SegmentOption[]>([
|
||||||
|
{ value: '', payload: '所有' },
|
||||||
|
{ value: 'weight', payload: '体重' },
|
||||||
|
{ value: 'sleep', payload: '睡眠' },
|
||||||
|
{ value: 'sport', payload: '运动' },
|
||||||
|
{ value: 'bloodPressure', payload: '血压' },
|
||||||
|
{ value: 'bloodSugar', payload: '血糖' },
|
||||||
|
{ value: 'temperature', payload: '心率' },
|
||||||
|
])
|
||||||
|
const currentSegment = ref('')
|
||||||
|
|
||||||
const displayDate = computed(() => {
|
const displayDate = computed(() => {
|
||||||
const d = new Date(currentDate.value)
|
const d = new Date(currentDate.value)
|
||||||
@@ -115,7 +122,7 @@ const records = ref<IHealthRecord[]>([])
|
|||||||
const grouped = ref<ReturnType<typeof groupByDate>>([])
|
const grouped = ref<ReturnType<typeof groupByDate>>([])
|
||||||
|
|
||||||
function loadRecords() {
|
function loadRecords() {
|
||||||
const all = getMonthRecords(getMonthStr(currentDate.value))
|
const all = getMonthRecords(getMonthStr(currentDate.value), currentSegment.value)
|
||||||
records.value = all
|
records.value = all
|
||||||
grouped.value = groupByDate(all)
|
grouped.value = groupByDate(all)
|
||||||
}
|
}
|
||||||
@@ -144,6 +151,24 @@ function openDatePicker() {
|
|||||||
function onDateConfirm() {
|
function onDateConfirm() {
|
||||||
loadRecords()
|
loadRecords()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChangeSegment() {
|
||||||
|
loadRecords()
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteClick(item: IHealthRecord) {
|
||||||
|
console.log(item)
|
||||||
|
wx.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定删除这条记录吗?',
|
||||||
|
success(res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
deleteRecordByDate(item.date, item.id)
|
||||||
|
loadRecords()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -21,23 +21,28 @@
|
|||||||
</wd-card>
|
</wd-card>
|
||||||
|
|
||||||
<wd-cell-group>
|
<wd-cell-group>
|
||||||
|
<wd-cell title="今日概览" is-link @click="openOverviewPopup"/>
|
||||||
|
<wd-cell title="今日计划" is-link @click="openStoragePopup"/>
|
||||||
<wd-cell title="存储详情" is-link @click="openStoragePopup"/>
|
<wd-cell title="存储详情" is-link @click="openStoragePopup"/>
|
||||||
</wd-cell-group>
|
</wd-cell-group>
|
||||||
|
|
||||||
<profile-popup />
|
<profile-popup />
|
||||||
|
|
||||||
<storage-popup />
|
<storage-popup />
|
||||||
|
|
||||||
|
<overview-popup />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ProfilePopup from "@/components/Profile/ProfilePopup.vue";
|
import ProfilePopup from "@/components/Profile/ProfilePopup.vue";
|
||||||
import StoragePopup from "@/components/Profile/StoragePopup.vue";
|
import StoragePopup from "@/components/Profile/StoragePopup.vue";
|
||||||
|
import OverviewPopup from "@/components/Profile/OverviewPopup.vue";
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { getProfile } from '@/utils/profile'
|
import { getOverview, getProfile } from '@/utils/profile'
|
||||||
import { useProfileStore } from '@/store/profile'
|
import { useProfileStore } from '@/store/profile'
|
||||||
import type { IProfile } from "@/type/profile";
|
import type { IProfile } from "@/type/profile";
|
||||||
import {countRecordsByMonth, getTotalCount} from "@/utils/record";
|
import { countRecordsByMonth, getTotalCount } from "@/utils/record";
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
@@ -62,6 +67,11 @@ function openProfile() {
|
|||||||
profileStore.showProfilePopup = true
|
profileStore.showProfilePopup = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openOverviewPopup() {
|
||||||
|
profileStore.overviewList = getOverview()
|
||||||
|
profileStore.showOverviewPopup = true
|
||||||
|
}
|
||||||
|
|
||||||
function openStoragePopup() {
|
function openStoragePopup() {
|
||||||
profileStore.storageStats = countRecordsByMonth()
|
profileStore.storageStats = countRecordsByMonth()
|
||||||
profileStore.storageTotal = getTotalCount()
|
profileStore.storageTotal = getTotalCount()
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 5.0 KiB |
@@ -6,14 +6,15 @@ export const useProfileStore = defineStore('profile', {
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
showProfilePopup: false,
|
showProfilePopup: false,
|
||||||
showStoragePopup: false,
|
showStoragePopup: false,
|
||||||
|
showOverviewPopup: false,
|
||||||
profileForm: {
|
profileForm: {
|
||||||
gender: '',
|
gender: '',
|
||||||
height: 0,
|
height: 0,
|
||||||
name: '',
|
name: '',
|
||||||
weight: 0,
|
|
||||||
} as IProfile,
|
} as IProfile,
|
||||||
storageStats: [] as IStats[],
|
storageStats: [] as IStats[],
|
||||||
storageTotal: 0,
|
storageTotal: 0,
|
||||||
|
overviewList: [] as string[],
|
||||||
isRefresh: false,
|
isRefresh: false,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
|
|||||||
@@ -2,5 +2,11 @@ export interface IProfile {
|
|||||||
name: string
|
name: string
|
||||||
gender: 'male' | 'female' | ''
|
gender: 'male' | 'female' | ''
|
||||||
height: number | null
|
height: number | null
|
||||||
weight: number | null
|
}
|
||||||
|
|
||||||
|
export interface IOverview {
|
||||||
|
icon: string
|
||||||
|
label: string
|
||||||
|
value:string
|
||||||
|
sub: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import type { IProfile } from "@/type/profile";
|
|||||||
|
|
||||||
const PROFILE_KEY = 'user_profile'
|
const PROFILE_KEY = 'user_profile'
|
||||||
|
|
||||||
|
const OVERVIEW_KEY = 'user_overview'
|
||||||
|
|
||||||
function getEmptyProfile(): IProfile {
|
function getEmptyProfile(): IProfile {
|
||||||
return {
|
return {
|
||||||
gender: '',
|
gender: '',
|
||||||
height: null,
|
height: null,
|
||||||
name: '',
|
name: '',
|
||||||
weight: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,3 +19,12 @@ export function getProfile(): IProfile {
|
|||||||
export function saveProfile(data: IProfile) {
|
export function saveProfile(data: IProfile) {
|
||||||
wx.setStorageSync(PROFILE_KEY, data)
|
wx.setStorageSync(PROFILE_KEY, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOverview(): string[] {
|
||||||
|
return wx.getStorageSync(OVERVIEW_KEY) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveOverview(data: string[]) {
|
||||||
|
wx.setStorageSync(OVERVIEW_KEY, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,20 @@ function addRecord(type: string, data: Record<string, any>, timestamp: number) {
|
|||||||
wx.setStorageSync(key, all)
|
wx.setStorageSync(key, all)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteRecordByDate(date: string, id: string) {
|
||||||
|
const key = `health_${date.slice(0, 7)}`
|
||||||
|
const all = wx.getStorageSync(key) || {}
|
||||||
|
const list = all[date]
|
||||||
|
if (!Array.isArray(list)) return
|
||||||
|
|
||||||
|
const idx = list.findIndex((item: any) => item.id === id)
|
||||||
|
if (idx !== -1) {
|
||||||
|
list.splice(idx, 1)
|
||||||
|
if (list.length === 0) delete all[date]
|
||||||
|
wx.setStorageSync(key, all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function addWeight(value: number, note: string, timestamp: number) {
|
export function addWeight(value: number, note: string, timestamp: number) {
|
||||||
addRecord('weight', { value, note }, timestamp)
|
addRecord('weight', { value, note }, timestamp)
|
||||||
}
|
}
|
||||||
@@ -149,7 +163,7 @@ const META_MAP: Record<string, IRecordMeta> = {
|
|||||||
weight: { icon: '⚖️', label: '体重', color: 'bg-[#E89E3A]' },
|
weight: { icon: '⚖️', label: '体重', color: 'bg-[#E89E3A]' },
|
||||||
sleep: { icon: '😴', label: '睡眠', color: 'bg-[#2E5B8C]' },
|
sleep: { icon: '😴', label: '睡眠', color: 'bg-[#2E5B8C]' },
|
||||||
bloodPressure: { icon: '❤️', label: '血压', color: 'bg-[#C04A3C]' },
|
bloodPressure: { icon: '❤️', label: '血压', color: 'bg-[#C04A3C]' },
|
||||||
bloodSugar: { icon: '📈', label: '血糖', color: 'bg-[#9C27B0]' },
|
bloodSugar: { icon: '🩸', label: '血糖', color: 'bg-[#9C27B0]' },
|
||||||
sport: { icon: '🏃', label: '运动', color: 'bg-[#3A7C5B]' },
|
sport: { icon: '🏃', label: '运动', color: 'bg-[#3A7C5B]' },
|
||||||
temperature: { icon: '🌡️', label: '体温', color: 'bg-[#D32F2F]' },
|
temperature: { icon: '🌡️', label: '体温', color: 'bg-[#D32F2F]' },
|
||||||
}
|
}
|
||||||
@@ -270,10 +284,10 @@ export function groupByDate(records: IHealthRecord[]): Array<{
|
|||||||
map[r.date].push(r)
|
map[r.date].push(r)
|
||||||
})
|
})
|
||||||
return Object.keys(map)
|
return Object.keys(map)
|
||||||
.sort((a, b) => b.localeCompare(a)) // 日期倒序,最新的在上
|
.sort((a, b) => b.localeCompare(a))
|
||||||
.map(date => ({
|
.map(date => ({
|
||||||
date,
|
date,
|
||||||
weekday: WEEKDAY[new Date(date).getDay()],
|
weekday: WEEKDAY[new Date(date).getDay()],
|
||||||
items: map[date].sort((a, b) => a.time.localeCompare(b.time)), // 当天按时间正序
|
items: map[date].sort((a, b) => b.time.localeCompare(a.time)),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user