feat: 增加存储详情功能
This commit is contained in:
54
src/components/Profile/StoragePopup.vue
Normal file
54
src/components/Profile/StoragePopup.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="profileStore.showStoragePopup"
|
||||
position="bottom"
|
||||
:safe-area-inset-bottom="true"
|
||||
custom-style="border-radius: 32rpx 32rpx 0 0; padding: 40rpx 32rpx;"
|
||||
>
|
||||
<view class="mb-5 text-center text-base text-gray-700 font-semibold">
|
||||
存储详情
|
||||
</view>
|
||||
|
||||
<view class="flex flex-col items-center">
|
||||
<wd-text :text="`${profileStore.storageTotal} / 1000`"></wd-text>
|
||||
<wd-progress
|
||||
:percentage="Number((profileStore.storageTotal / 1000 * 100).toFixed(2))"
|
||||
:color="['#00c740', '#ffb300', '#e2231a']"/>
|
||||
</view>
|
||||
|
||||
<view class="max-h-[40vh] overflow-y-auto">
|
||||
<wd-cell-group>
|
||||
<wd-cell v-for="item in profileStore.storageStats"
|
||||
:key="item.month"
|
||||
:title="`${item.name}(${item.value}条)`"
|
||||
center>
|
||||
<wd-button size="small" type="danger" plain @click="clearMonth(item.name)">清除</wd-button>
|
||||
</wd-cell>
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
|
||||
<wd-empty v-if="profileStore.storageStats.length === 0" icon="no-content" tip="暂无记录" />
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useProfileStore } from '@/store/profile'
|
||||
import { countRecordsByMonth, getTotalCount } from "@/utils/record";
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
function clearMonth(month: string) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: `确定清除 ${month} 的所有记录吗?`,
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
wx.removeStorageSync(`health_${month}`)
|
||||
profileStore.storageStats = countRecordsByMonth()
|
||||
profileStore.storageTotal = getTotalCount()
|
||||
wx.showToast({ title: '已清除', icon: 'success' })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -81,10 +81,11 @@ 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 { ref, computed, watch } from 'vue'
|
||||
import { useRecordStore } from '@/store/record'
|
||||
import type { IHealthRecord } from '@/utils/record'
|
||||
import { getDateStr, getDayRecords } from '@/utils/record'
|
||||
import { countRecordsByMonth, getDateStr, getDayRecords } from '@/utils/record'
|
||||
import type { IHealthRecord } from "@/type/record";
|
||||
import {onShow} from "@dcloudio/uni-app";
|
||||
|
||||
defineOptions({
|
||||
name: 'Home',
|
||||
@@ -114,7 +115,7 @@ const today = computed(() => {
|
||||
return `${m}月${d}日 周${w}`
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
onShow(() => {
|
||||
todayRecords.value = getDayRecords(getDateStr(Date.now()))
|
||||
})
|
||||
|
||||
@@ -196,3 +197,9 @@ function handelFabClick() {
|
||||
recordStore.showFab = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-card:not(:last-child)) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<wd-segmented v-model:value="current" :options="list" style="overflow: auto;" />
|
||||
<wd-segmented v-model:value="current" :options="list"/>
|
||||
</view>
|
||||
|
||||
<!-- 时间轴区域 -->
|
||||
<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">
|
||||
@@ -90,7 +90,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import type { IHealthRecord } from '@/utils/record'
|
||||
import {
|
||||
getMonthRecords,
|
||||
getMonthStr,
|
||||
@@ -100,6 +99,7 @@ import {
|
||||
groupByDate,
|
||||
} from '@/utils/record'
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import type { IHealthRecord } from "@/type/record";
|
||||
|
||||
const currentDate = ref(Date.now())
|
||||
const showDatePicker = ref(false)
|
||||
@@ -141,12 +141,16 @@ function openDatePicker() {
|
||||
showDatePicker.value = true
|
||||
}
|
||||
|
||||
function onDateConfirm({ value }) {
|
||||
function onDateConfirm() {
|
||||
loadRecords()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.wd-segmented) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:deep(.wd-segmented__item) {
|
||||
min-width: 60px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -20,16 +20,24 @@
|
||||
</view>
|
||||
</wd-card>
|
||||
|
||||
<wd-cell-group>
|
||||
<wd-cell title="存储详情" is-link @click="openStoragePopup"/>
|
||||
</wd-cell-group>
|
||||
|
||||
<profile-popup />
|
||||
|
||||
<storage-popup />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ProfilePopup from "@/components/Profile/ProfilePopup.vue";
|
||||
import StoragePopup from "@/components/Profile/StoragePopup.vue";
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { IProfile } from '@/utils/profile'
|
||||
import { getProfile } from '@/utils/profile'
|
||||
import { useProfileStore } from '@/store/profile'
|
||||
import type { IProfile } from "@/type/profile";
|
||||
import {countRecordsByMonth, getTotalCount} from "@/utils/record";
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
@@ -53,4 +61,10 @@ function openProfile() {
|
||||
|
||||
profileStore.showProfilePopup = true
|
||||
}
|
||||
|
||||
function openStoragePopup() {
|
||||
profileStore.storageStats = countRecordsByMonth()
|
||||
profileStore.storageTotal = getTotalCount()
|
||||
profileStore.showStoragePopup = true
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { IProfile } from '@/utils/profile'
|
||||
import type { IProfile } from "@/type/profile";
|
||||
import type {IStats} from "@/type";
|
||||
|
||||
export const useProfileStore = defineStore('profile', {
|
||||
state: () => ({
|
||||
showProfilePopup: false,
|
||||
showStoragePopup: false,
|
||||
profileForm: {
|
||||
gender: '',
|
||||
height: 0,
|
||||
name: '',
|
||||
weight: 0,
|
||||
} as IProfile,
|
||||
storageStats: [] as IStats[],
|
||||
storageTotal: 0,
|
||||
isRefresh: false,
|
||||
}),
|
||||
actions: {
|
||||
|
||||
4
src/type/index.ts
Normal file
4
src/type/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IStats {
|
||||
name: string;
|
||||
value: number
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IProfile {
|
||||
name: string
|
||||
gender: 'male' | 'female' | ''
|
||||
height: number | null
|
||||
weight: number | null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
export interface IProfile {
|
||||
name: string
|
||||
gender: 'male' | 'female' | ''
|
||||
height: number | null
|
||||
weight: number | null
|
||||
}
|
||||
import type { IProfile } from "@/type/profile";
|
||||
|
||||
const PROFILE_KEY = 'user_profile'
|
||||
|
||||
|
||||
@@ -1,29 +1,12 @@
|
||||
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'
|
||||
}
|
||||
import type {IHealthRecord, IRecordMeta, IRecordStats} from "@/type/record";
|
||||
import type {IStats} from "@/type";
|
||||
|
||||
const MONTH_KEEP = 12
|
||||
const RECORD_PREFIX = 'health_'
|
||||
|
||||
function getMonthKey(timestamp: number) {
|
||||
const d = new Date(timestamp)
|
||||
return `health_${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`
|
||||
return `${RECORD_PREFIX}${getMonthStr(timestamp)}`
|
||||
}
|
||||
|
||||
export function getDateStr(timestamp: number) {
|
||||
@@ -46,16 +29,17 @@ export function getTimeStr(timestamp: number) {
|
||||
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 cutoffKey = `${RECORD_PREFIX}${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, '0')}`
|
||||
const keys = wx.getStorageInfoSync().keys
|
||||
keys.forEach((key) => {
|
||||
if (key.startsWith('health_') && key < cutoffKey) {
|
||||
keys.forEach((key: string) => {
|
||||
if (key.startsWith(RECORD_PREFIX) && key < cutoffKey) {
|
||||
wx.removeStorageSync(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addRecord(type: string, data: Record<string, any>, timestamp: number) {
|
||||
// 键为月份 例如 health_2026-08
|
||||
const key = getMonthKey(timestamp)
|
||||
const all = wx.getStorageSync(key) || {}
|
||||
const date = getDateStr(timestamp)
|
||||
@@ -63,6 +47,7 @@ function addRecord(type: string, data: Record<string, any>, timestamp: number) {
|
||||
if (!all[date])
|
||||
all[date] = []
|
||||
|
||||
// 在按天存储
|
||||
all[date].push({
|
||||
id: Date.now() + Math.random().toString(36).slice(2, 6),
|
||||
time: getTimeStr(timestamp),
|
||||
@@ -97,31 +82,25 @@ export function addTemperature(value: number, location: string, note: string, ti
|
||||
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 {
|
||||
/**
|
||||
* 获取某天的记录
|
||||
* @param dateStr 日期 YYYY-MM-DD
|
||||
*/
|
||||
export function getDayRecords(dateStr: string): IHealthRecord[] {
|
||||
const [y, m] = dateStr.split('-')
|
||||
return `health_${y}-${m}`
|
||||
}
|
||||
|
||||
export function getDayRecords(date: string): IHealthRecord[] {
|
||||
const key = getMonthKeyForDate(date)
|
||||
const key = `${RECORD_PREFIX}${y}-${m}`
|
||||
const all = wx.getStorageSync(key) || {}
|
||||
const dayRecords = all[date] || []
|
||||
const dayRecords = all[dateStr] || []
|
||||
return [...dayRecords].sort((a, b) => a.time.localeCompare(b.time))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的记录
|
||||
* @param yearMonth 月份 YYYY-MM
|
||||
* @param type 类型
|
||||
*/
|
||||
export function getMonthRecords(yearMonth: string, type?: string): IHealthRecord[] {
|
||||
const key = `health_${yearMonth}`
|
||||
const key = `${RECORD_PREFIX}${yearMonth}`
|
||||
const all = wx.getStorageSync(key) || {}
|
||||
|
||||
const result: IHealthRecord[] = []
|
||||
@@ -146,6 +125,26 @@ function sortRecords(records: IHealthRecord[]): IHealthRecord[] {
|
||||
})
|
||||
}
|
||||
|
||||
export function countRecordsByMonth(): IStats[] {
|
||||
const keys = wx.getStorageInfoSync().keys
|
||||
|
||||
return keys
|
||||
.filter(k => k.startsWith(RECORD_PREFIX))
|
||||
.map(key => {
|
||||
const all: IHealthRecord = wx.getStorageSync(key) || {}
|
||||
const value: number = Object.values(all).reduce((sum: number, list: IHealthRecord[]) => sum + list.length, 0)
|
||||
const name = key.replace(RECORD_PREFIX, '')
|
||||
return { name, value }
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
|
||||
export function getTotalCount(): number {
|
||||
return wx.getStorageInfoSync().keys
|
||||
.filter(k => k.startsWith('health_'))
|
||||
.reduce((sum, k) => sum + Object.values(wx.getStorageSync(k) || {}).flat().length, 0)
|
||||
}
|
||||
|
||||
const META_MAP: Record<string, IRecordMeta> = {
|
||||
weight: { icon: '⚖️', label: '体重', color: 'bg-[#E89E3A]' },
|
||||
sleep: { icon: '😴', label: '睡眠', color: 'bg-[#2E5B8C]' },
|
||||
|
||||
Reference in New Issue
Block a user