feat:更新运动健康模块

This commit is contained in:
2025-11-04 22:35:53 +08:00
parent 5f3a7ca5b4
commit ee20c19b72
16 changed files with 182 additions and 394 deletions

View File

@@ -1,29 +0,0 @@
import { cloudService } from './index'
import { IExercise } from '@/types/health'
export const addExerciseApi = (exercise: IExercise): Promise<boolean> =>
cloudService({
url: '/home-api/exercise',
method: 'post',
data: exercise
})
export const updateExerciseApi = (id: number, exercise: IExercise): Promise<boolean> =>
cloudService({
url: `/home-api/exercise/${id}`,
method: 'put',
data: exercise
})
export const deleteExerciseApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-api/exercise/${id}`,
method: 'delete'
})
export const queryExerciseApi = (query: IExerciseQuery): Promise<IExercise[]> =>
cloudService({
url: '/home-api/exercise',
method: 'get',
params: query
})

16
src/apis/health.ts Normal file
View File

@@ -0,0 +1,16 @@
import { cloudService } from './index'
import { IHealth, IHealthQuery } from '@/types/health'
export const updateHealthApi = (id: number, health: IHealth): Promise<boolean> =>
cloudService({
url: `/home-api/health/${id}`,
method: 'put',
data: health
})
export const queryHealthApi = (query: IHealthQuery): Promise<IHealth[]> =>
cloudService({
url: '/home-api/health',
method: 'get',
params: query
})

View File

@@ -1,29 +0,0 @@
import { cloudService } from './index'
import { IWeight } from '@/types/health'
export const addWeightApi = (weight: IWeight): Promise<boolean> =>
cloudService({
url: '/home-api/weight',
method: 'post',
data: weight
})
export const updateWeightApi = (id: number, weight: IWeight): Promise<boolean> =>
cloudService({
url: `/home-api/weight/${id}`,
method: 'put',
data: weight
})
export const deleteWeightApi = (id: number): Promise<boolean> =>
cloudService({
url: `/home-api/weight/${id}`,
method: 'delete'
})
export const queryWeightApi = (query: IWeightQuery): Promise<IWeight[]> =>
cloudService({
url: '/home-api/weight',
method: 'get',
params: query
})

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="bill-chart" <div class="bill-chart common-chart"
v-loading="billStore.isLoading" v-loading="billStore.isLoading"
element-loading-text="正在加载中..."> element-loading-text="正在加载中...">
<div class="chart-container card-item"> <div class="chart-container card-item">
@@ -8,19 +8,22 @@
</div> </div>
<div class="chart" ref="billDailyChartRef"/> <div class="chart" ref="billDailyChartRef"/>
</div> </div>
<div class="chart-container card-item"> <div class="chart-container card-item">
<div class="content"> <div class="content">
<span class="title">📊类别统计</span> <span class="title">📊类别统计</span>
</div> </div>
<div class="chart" ref="billCategoryChartRef"/> <div class="chart" ref="billCategoryChartRef"/>
</div> </div>
<div class="chart-container card-item"> <div class="chart-container card-item">
<div class="content"> <div class="content">
<span class="title">📊账本统计</span> <span class="title">📊账本统计</span>
</div> </div>
<div class="chart" ref="billBookChartRef"/> <div class="chart" ref="billBookChartRef"/>
</div> </div>
<div v-if="isMobile()" class="chart-container card-item">
<div class="chart-container card-item">
<div class="content"> <div class="content">
<span class="title">📊排行榜</span> <span class="title">📊排行榜</span>
</div> </div>
@@ -28,12 +31,6 @@
<bill-rank /> <bill-rank />
</div> </div>
</div> </div>
<div v-else class="chart-container card-item">
<div class="content">
<span class="title">📊排行榜</span>
</div>
<div class="chart" ref="billRankChartRef"/>
</div>
</div> </div>
</template> </template>
@@ -44,7 +41,6 @@ import * as echarts from 'echarts'
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil' import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
import { useBillStore } from '@/store/bill.ts' import { useBillStore } from '@/store/bill.ts'
import { useAppStore } from '@/store/app.ts' import { useAppStore } from '@/store/app.ts'
import type { IStatsChart } from 'vue3-common/types'
import { isMobile } from 'vue3-common/utils/layoutUtil' import { isMobile } from 'vue3-common/utils/layoutUtil'
const billStore = useBillStore() const billStore = useBillStore()
@@ -53,12 +49,10 @@ const appStore = useAppStore()
const billDailyChartRef = ref<HTMLElement>() const billDailyChartRef = ref<HTMLElement>()
const billBookChartRef = ref<HTMLElement>() const billBookChartRef = ref<HTMLElement>()
const billCategoryChartRef = ref<HTMLElement>() const billCategoryChartRef = ref<HTMLElement>()
const billRankChartRef = ref<HTMLElement>()
let billDailyChart: echarts.ECharts let billDailyChart: echarts.ECharts
let billBookChart: echarts.ECharts let billBookChart: echarts.ECharts
let billCategoryChart: echarts.ECharts let billCategoryChart: echarts.ECharts
let billRankChart: echarts.ECharts
watch(() => billStore.isRefresh, () => { watch(() => billStore.isRefresh, () => {
updateChart() updateChart()
@@ -68,15 +62,10 @@ onMounted(() => {
billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null) billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
billBookChart = echarts.init(billBookChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null) billBookChart = echarts.init(billBookChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
billCategoryChart = echarts.init(billCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null) billCategoryChart = echarts.init(billCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
if (!isMobile()) {
billRankChart = echarts.init(billRankChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
}
}) })
const updateChart = () => { const updateChart = () => {
const MAX_CATEGORY_COUNT = 5 const MAX_CATEGORY_COUNT = 5
const MAX_RANK_COUNT = 10
// 收支统计图 // 收支统计图
const dailyXAxis = billStore.billDateStatsList.map((item) => item.name) const dailyXAxis = billStore.billDateStatsList.map((item) => item.name)
@@ -129,19 +118,5 @@ const updateChart = () => {
}] }]
}) })
} }
// 排行榜统计图
const billRankList: IStatsChart[] = []
const resultList = billStore.billRecordList.filter((item) => item.type === billStore.billType)
resultList.sort((a, b) => b.amount - a.amount).forEach((item) => {
billRankList.push({
name: item.content,
value: item.amount
})
})
if (!isMobile()) {
billRankChart.setOption(pieChartOptions('rank', '元', billRankList.slice(0, MAX_RANK_COUNT)))
}
} }
</script> </script>

View File

@@ -3,7 +3,7 @@
:key="index" class="rank-item"> :key="index" class="rank-item">
<span class="rank-index">{{ index+1 }}.</span> <span class="rank-index">{{ index+1 }}.</span>
<span class="rank-content">{{ item.content }}</span> <span class="rank-content">{{ item.content }}</span>
<span class="rank-amount">{{ item.amount }}</span> <span class="rank-amount">{{ formatAmount(item.amount) }}</span>
</div> </div>
</template> </template>
@@ -11,6 +11,7 @@
import { useBillStore } from '@/store/bill.ts' import { useBillStore } from '@/store/bill.ts'
import { getBillSortByAmount } from '@/utils/home/billUtil.ts' import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
import { computed } from 'vue' import { computed } from 'vue'
import { formatAmount } from 'vue3-common/utils/numberUtil'
const billStore = useBillStore() const billStore = useBillStore()

View File

@@ -16,8 +16,8 @@
<span>运动项目</span> <span>运动项目</span>
</template> </template>
<el-select v-model="healthStore.currentHealth.project" <el-select v-model="healthStore.currentHealth.sportProject"
placeholder="请选择运行项目" style="width: 150px;"> placeholder="请选择运行项目" clearable style="width: 150px;">
<el-option v-for="(item, index) in projectList" :key="index" <el-option v-for="(item, index) in projectList" :key="index"
:label="item" :value="item"/> :label="item" :value="item"/>
</el-select> </el-select>
@@ -29,7 +29,8 @@
<span>运动时长</span> <span>运动时长</span>
</template> </template>
<el-input-number v-model="healthStore.currentHealth.duration" <el-input-number v-model="healthStore.currentHealth.sportDuration"
:min="0" :max="300" :precision="0"
style="width: 120px;"></el-input-number> style="width: 120px;"></el-input-number>
<span style="margin-left: 5px;">分钟</span> <span style="margin-left: 5px;">分钟</span>
@@ -41,7 +42,8 @@
<span>体重</span> <span>体重</span>
</template> </template>
<el-input-number v-model="healthStore.currentHealth.value" <el-input-number v-model="healthStore.currentHealth.weight"
:min="0" :max="200"
style="width: 120px;"></el-input-number> style="width: 120px;"></el-input-number>
<span style="margin-left: 5px;">千克</span> <span style="margin-left: 5px;">千克</span>
@@ -59,14 +61,27 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useHealthStore } from '@/store/health' import { useHealthStore } from '@/store/health'
import { formatDate, isBeforeDate } from 'vue3-common/utils/dateUtil' import { formatDate, isBeforeDate } from 'vue3-common/utils/dateUtil'
import { ElMessage } from 'element-plus'
const healthStore = useHealthStore() const healthStore = useHealthStore()
const projectList = ['羽毛球', '乒乓球', '跑步', '其他'] const projectList = ['羽毛球', '乒乓球', '跑步', '其他']
const onSubmit = async () => { const onSubmit = async () => {
// await periodStore.updatePeriodDay() const { sportProject, sportDuration, weight } = healthStore.currentHealth
// periodStore.currentPeriodDay = periodStore.getPeriodDay(periodStore.selectDate) if (sportProject === '' && sportDuration === 0 && weight === 0) {
return
}
if (sportDuration) {
if (!sportProject) {
ElMessage.error('请选择运动项目')
return
}
}
await healthStore.updateHealthDay()
healthStore.currentHealth = healthStore.getHealthDay(healthStore.selectDate)
} }
</script> </script>

View File

@@ -1,43 +0,0 @@
<template>
<div class="health-legend card-item">
<div class="legend-item">
<div class="small-circle weight"></div>
<span>体重</span>
</div>
<div class="legend-item">
<div class="small-circle exercise"></div>
<span>运动</span>
</div>
</div>
</template>
<script lang="ts" setup>
</script>
<style lang="scss">
.health-legend {
padding: 10px;
display: flex;
gap: 5px;
.legend-item {
display: flex;
align-items: center;
gap: 5px;
.small-circle {
width: 6px;
height: 6px;
border-radius: 50%;
}
}
.weight {
background-color: #DC2626;
}
.exercise {
background-color: #16A34A;
}
}
</style>

View File

@@ -0,0 +1,23 @@
<template>
<div v-for="(item, index) in ledgerRankList.slice(0, MAX_RANK_COUNT)"
:key="index" class="rank-item">
<span class="rank-index">{{ index+1 }}.</span>
<span class="rank-content">{{ item.content }}</span>
<span class="rank-amount">{{ formatAmount(item.totalPrice) }}</span>
</div>
</template>
<script setup lang="ts">
import { useLedgerStore } from '@/store/ledger.ts'
import { getLedgerSortByPrice } from '@/utils/home/ledgerUtil.ts'
import { computed } from 'vue'
import { formatAmount } from 'vue3-common/utils/numberUtil'
const ledgerStore = useLedgerStore()
const MAX_RANK_COUNT = 10
const ledgerRankList = computed(() => {
return getLedgerSortByPrice(ledgerStore.ledgerRecordList)
})
</script>

View File

@@ -1,129 +1,56 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { IHandleApi } from 'vue3-common/types' import type { IHandleApi } from 'vue3-common/types'
import { queryHealthApi, updateHealthApi } from '@/apis/health.ts'
import { IHealth, IHealthQuery } from '@/types/health'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import {
addExerciseApi,
deleteExerciseApi,
queryExerciseApi,
updateExerciseApi
} from '@/apis/exercise'
import { addWeightApi, deleteWeightApi, queryWeightApi, updateWeightApi } from '@/apis/weight'
import { IExercise, IExerciseQuery, IHealth, IWeight } from '@/types/health'
export const useHealthStore = defineStore('health', { export const useHealthStore = defineStore('health', {
state: () => ({ state: () => ({
weightRecordList: [] as IWeight[], healthRecordList: [] as IHealth[],
exerciseRecordList: [] as IExercise[],
queryInfo: { queryInfo: {
startDate: '', startDate: '',
endDate: '' endDate: ''
} as IExerciseQuery, } as IHealthQuery,
weightApiType: 'ADD' as IHandleApi, healthApiType: 'ADD' as IHandleApi,
exerciseApiType: 'ADD' as IHandleApi,
currentExercise: {
project: '',
duration: 0,
date: ''
} as IExercise,
currentHealth: { currentHealth: {
project: '', sportProject: '',
duration: 0, sportDuration: 0,
value: 0, weight: 0,
date: '' date: ''
} as IHealth, } as IHealth,
currentWeight: {
value: '',
date: ''
} as IWeight,
selectDate: '', selectDate: '',
isScrollEnd: false, isScrollEnd: false,
isRefresh: false isRefresh: false
}), }),
actions: { actions: {
async queryExerciseRecordList() { async queryHealthRecordList() {
this.exerciseRecordList = await queryExerciseApi(this.queryInfo) this.healthRecordList = await queryHealthApi(this.queryInfo)
}, },
async queryWeightRecordList() { async updateHealthDay() {
this.weightRecordList = await queryWeightApi(this.queryInfo) await updateHealthApi(this.currentHealth.id as number, this.currentHealth)
},
async handleExerciseApi(exercise: IExercise) {
switch (this.exerciseApiType) {
case 'ADD':
await addExerciseApi(exercise)
ElMessage.success('新增锻炼记录成功')
break
case 'UPDATE':
await updateExerciseApi(exercise.id as number, exercise)
ElMessage.success('更新锻炼记录成功')
break
case 'DELETE':
await deleteExerciseApi(exercise.id as number)
ElMessage.success('删除锻炼记录成功')
break
default:
break
}
await this.refreshInfo()
},
async handleWeightApi(weight: IWeight) {
switch (this.weightApiType) {
case 'ADD':
await addWeightApi(weight)
ElMessage.success('新增体重记录成功')
break
case 'UPDATE':
await updateWeightApi(weight.id as number, weight)
ElMessage.success('更新体重记录成功')
break
case 'DELETE':
await deleteWeightApi(weight.id as number)
ElMessage.success('删除体重记录成功')
break
default:
break
}
await this.refreshInfo() await this.refreshInfo()
ElMessage.success('更新记录成功')
}, },
getHealthDay(date: string): IHealth { getHealthDay(date: string): IHealth {
const exercise = this.exerciseRecordList.find((item) => item.date === date) const result = this.healthRecordList.find((item) => item.date === date)
const weight = this.weightRecordList.find((item) => item.date === date)
this.currentHealth = this.getEmptyHealth()
if (exercise) { if (result) {
this.currentHealth.project = exercise.project return result
this.currentHealth.duration = exercise.duration } else {
return this.getEmptyHealth(date)
} }
if (weight) {
this.currentHealth.value = weight.value
}
this.currentHealth.date = date
}, },
async refreshInfo() { async refreshInfo() {
await this.queryExerciseRecordList() await this.queryHealthRecordList()
await this.queryWeightRecordList()
this.isRefresh = !this.isRefresh this.isRefresh = !this.isRefresh
}, },
getEmptyExercise(): IExercise { getEmptyHealth(date: string): IHealth {
return { return {
project: '', id: 0,
duration: 0, date,
date: '' sportDuration: 0,
} sportProject: '',
}, weight: 0
getEmptyWeight(): IWeight {
return {
value: 0,
date: ''
}
},
getEmptyHealth(): IHealth {
return {
date: '',
duration: 0,
project: '',
value: 0
} }
} }
} }

View File

@@ -10,6 +10,7 @@ import {
updatePeriodSettingApi updatePeriodSettingApi
} from '@/apis/period.ts' } from '@/apis/period.ts'
import { getCurrentMonth } from 'vue3-common/utils/dateUtil' import { getCurrentMonth } from 'vue3-common/utils/dateUtil'
import { ElMessage } from 'element-plus'
export const usePeriodStore = defineStore('period', { export const usePeriodStore = defineStore('period', {
state: () => ({ state: () => ({
@@ -43,6 +44,7 @@ export const usePeriodStore = defineStore('period', {
async updatePeriodDay() { async updatePeriodDay() {
await updatePeriodDayApi(this.currentPeriodDay.id as number, this.currentPeriodDay) await updatePeriodDayApi(this.currentPeriodDay.id as number, this.currentPeriodDay)
await this.refreshInfo() await this.refreshInfo()
ElMessage.success('更新记录成功')
}, },
async queryPeriodSetting() { async queryPeriodSetting() {
this.periodSetting = await queryPeriodSettingApi() this.periodSetting = await queryPeriodSettingApi()

View File

@@ -7,66 +7,6 @@
align-self: center; align-self: center;
} }
.bill-chart {
display: grid;
grid-template-rows: repeat(4, 1fr);
gap: 10px;
.chart-container {
display: grid;
grid-template-rows: 40px 1fr;
.content {
padding: 0 5px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #009FA8;
.title {
font-size: 1em;
font-weight: bold;
}
}
.chart {
height: 300px;
width: 100%;
}
.rank-chart {
overflow: auto;
display: flex;
flex-direction: column;
gap: 5px;
.rank-item {
padding: 10px;
display: grid;
grid-template-columns: 20px 1fr 80px;
align-items: center;
.rank-amount {
justify-self: flex-end;
}
}
.rank-item:nth-child(1) {
color: #d4af37;
font-weight: bold;
}
.rank-item:nth-child(2) {
color: #a8a8a8;
}
.rank-item:nth-child(3) {
color: #b87333;
}
}
}
}
.el-switch { .el-switch {
.el-switch__core { .el-switch__core {
height: 32px; height: 32px;

View File

@@ -1,48 +0,0 @@
.bill-view {
.bill-chart {
flex-grow: 1;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 2vh;
.chart-container {
display: grid;
grid-template-rows: 40px 1fr;
.content {
padding: 0 5px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #009FA8;
.title {
font-size: 1em;
font-weight: bold;
}
}
}
}
@media (max-width: 1200px) {
.bill-chart {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
gap: 2vh;
.chart-container {
.chart {
min-width: 300px;
min-height: 250px;
}
}
}
}
.bill-dialog {
width: 950px;
}
}

View File

@@ -52,6 +52,67 @@ $tabBar-height: 70px;
gap: 10px; gap: 10px;
} }
.common-chart {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
gap: 10px;
.chart-container {
display: grid;
grid-template-rows: 40px 1fr;
.content {
padding: 0 5px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #009FA8;
.title {
font-size: 1em;
font-weight: bold;
}
}
.chart {
height: 300px;
width: 100%;
}
}
}
.rank-chart {
overflow: auto;
display: flex;
flex-direction: column;
gap: 5px;
.rank-item {
padding: 10px;
display: grid;
grid-template-columns: 20px 1fr 80px;
align-items: center;
.rank-amount {
justify-self: flex-end;
}
}
.rank-item:nth-child(1) {
color: #d4af37;
font-weight: bold;
}
.rank-item:nth-child(2) {
color: #a8a8a8;
}
.rank-item:nth-child(3) {
color: #b87333;
}
}
.title-bar { .title-bar {
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
height: $titleBar-height; height: $titleBar-height;

View File

@@ -22,3 +22,7 @@ export const statsByField = (records: ILedgerRecord[],
.sort((a, b) => b.value - a.value) .sort((a, b) => b.value - a.value)
.slice(0, 5) .slice(0, 5)
} }
export const getLedgerSortByPrice = (records: ILedgerRecord[]) => {
return records.sort((a, b) => b?.totalPrice - a?.totalPrice)
}

View File

@@ -4,21 +4,19 @@
@dayclick="dateClick" @did-move="changeMonthClick" @dayclick="dateClick" @did-move="changeMonthClick"
:is-dark="appStore.isDark" :is-dark="appStore.isDark"
class="health-calendar card-item"/> class="health-calendar card-item"/>
<health-legend />
<health-form /> <health-form />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import HealthLegend from '@/components/Health/HealthLegend.vue'
import HealthForm from '@/components/Health/HealthForm.vue' import HealthForm from '@/components/Health/HealthForm.vue'
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page' import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
import { formatDate, getStartEndDate } from 'vue3-common/utils/dateUtil' import { formatDate, getStartEndDate } from 'vue3-common/utils/dateUtil'
import { onMounted, reactive } from 'vue' import { onMounted, reactive, watch } from 'vue'
import { useHealthStore } from '@/store/health' import { useHealthStore } from '@/store/health'
import { useAppStore } from '@/store/app.ts' import { useAppStore } from '@/store/app.ts'
import { IExercise, IWeight } from '@/types/health' import { IHealth } from '@/types/health'
const healthStore = useHealthStore() const healthStore = useHealthStore()
const appStore = useAppStore() const appStore = useAppStore()
@@ -28,24 +26,22 @@ const dailyInfo = reactive({
attrs: [] as any[] attrs: [] as any[]
}) })
watch(() => healthStore.isRefresh, () => {
dailyInfo.attrs = []
updateRecordList()
})
onMounted(async () => { onMounted(async () => {
const result = getStartEndDate(formatDate(new Date()), 'month') const result = getStartEndDate(formatDate(new Date()), 'month')
healthStore.queryInfo = { startDate: result[0], endDate: result[1] } healthStore.queryInfo = { startDate: result[0], endDate: result[1] }
await healthStore.refreshInfo() await healthStore.refreshInfo()
updateRecordList() healthStore.selectDate = formatDate(new Date())
healthStore.getHealthDay(formatDate(new Date())) healthStore.currentHealth = healthStore.getHealthDay(formatDate(new Date()))
}) })
const updateRecordList = () => { const updateRecordList = () => {
healthStore.weightRecordList.forEach((item: IWeight) => { healthStore.healthRecordList.forEach((item: IHealth) => {
dailyInfo.attrs.push({
dot: 'red',
dates: item.date
})
})
healthStore.exerciseRecordList.forEach((item: IExercise) => {
dailyInfo.attrs.push({ dailyInfo.attrs.push({
dot: 'green', dot: 'green',
dates: item.date dates: item.date
@@ -55,7 +51,7 @@ const updateRecordList = () => {
const dateClick = (day: CalendarDay) => { const dateClick = (day: CalendarDay) => {
healthStore.selectDate = formatDate(day.date) healthStore.selectDate = formatDate(day.date)
healthStore.getHealthDay(formatDate(day.date)) healthStore.currentHealth = healthStore.getHealthDay(formatDate(day.date))
} }
/** /**

View File

@@ -1,23 +1,34 @@
<template> <template>
<div class="mobile-ledger-stats common-mobile-view"> <div class="mobile-ledger-stats common-mobile-view">
<div class="ledger-chart"> <div class="ledger-chart common-chart">
<div class="chart-container card-item"> <div class="chart-container card-item">
<div class="content"> <div class="content">
<span class="title">📊类别统计</span> <span class="title">📊类别统计</span>
</div> </div>
<div class="chart" ref="ledgerCategoryChartRef"/> <div class="chart" ref="ledgerCategoryChartRef"/>
</div> </div>
<div class="chart-container card-item"> <div class="chart-container card-item">
<div class="content"> <div class="content">
<span class="title">📊付款人统计</span> <span class="title">📊付款人统计</span>
</div> </div>
<div class="chart" ref="ledgerPayerChartRef"/> <div class="chart" ref="ledgerPayerChartRef"/>
</div> </div>
<div class="chart-container card-item">
<div class="content">
<span class="title">📊排行榜</span>
</div>
<div class="chart rank-chart">
<ledger-rank />
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import LedgerRank from '@/components/Ledger/LedgerRank.vue'
import { useLedgerStore } from '@/store/ledger.ts' import { useLedgerStore } from '@/store/ledger.ts'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { statsByField } from '@/utils/home/ledgerUtil.ts' import { statsByField } from '@/utils/home/ledgerUtil.ts'
@@ -70,37 +81,3 @@ const updateChart = () => {
} }
} }
</script> </script>
<style lang="scss">
.mobile-ledger-stats {
display: flex;
flex-direction: column;
gap: 10px;
.ledger-chart {
flex-grow: 1;
display: grid;
grid-template-rows: repeat(2, 1fr);
gap: 10px;
.chart-container {
display: grid;
grid-template-rows: 40px 1fr;
.content {
padding: 0 5px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #009FA8;
.title {
font-size: 1em;
font-weight: bold;
}
}
}
}
}
</style>