feat:搭建基本框架
This commit is contained in:
172
src/components/Stats/FoodChart.vue
Normal file
172
src/components/Stats/FoodChart.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="food-chart"
|
||||
element-loading-text="正在加载中...">
|
||||
<div class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊记录统计</span>
|
||||
</div>
|
||||
<div class="chart" ref="foodDailyChartRef"/>
|
||||
</div>
|
||||
<div class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊类别统计</span>
|
||||
</div>
|
||||
<div class="chart" ref="foodCategoryChartRef"/>
|
||||
</div>
|
||||
<div class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊排行榜</span>
|
||||
</div>
|
||||
<div class="chart rank-chart">
|
||||
<food-rank />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FoodRank from '@/components/Stats/FoodRank.vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import type { IStatsChart } from 'vue3-common/types'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
|
||||
const foodStore = useFoodStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const foodDailyChartRef = ref<HTMLElement>()
|
||||
const foodCategoryChartRef = ref<HTMLElement>()
|
||||
const billRankChartRef = ref<HTMLElement>()
|
||||
|
||||
let foodDailyChart: echarts.ECharts
|
||||
let foodCategoryChart: echarts.ECharts
|
||||
let billRankChart: echarts.ECharts
|
||||
|
||||
watch(() => foodStore.isRefresh, () => {
|
||||
updateChart()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
foodDailyChart = echarts.init(foodDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||
foodCategoryChart = echarts.init(foodCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||
// billRankChart = echarts.init(billRankChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||
})
|
||||
|
||||
const updateChart = () => {
|
||||
const MAX_CATEGORY_COUNT = 5
|
||||
const MAX_RANK_COUNT = 10
|
||||
|
||||
// 记录统计图
|
||||
// const dailyXAxis = foodStore.billDateStatsList.map((item) => item.name)
|
||||
// const dailyYAxis = foodStore.billDateStatsList.map((item) => item.value)
|
||||
const dailyXAxis = ['2024-01', '2024-02']
|
||||
const dailyYAxis = ['10', '6']
|
||||
foodDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '次数(次)', dailyYAxis))
|
||||
//
|
||||
// // 账本统计图
|
||||
// const bookXAxis = foodStore.billBookStatsList.map((item) => item.name)
|
||||
// const bookYAxis = foodStore.billBookStatsList.map((item) => item.value)
|
||||
// billBookChart.setOption(barChartOptions('账本', bookXAxis, '金额(元)', bookYAxis))
|
||||
// billBookChart.setOption({
|
||||
// series: [{
|
||||
// itemStyle: {
|
||||
// color: foodStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
|
||||
// },
|
||||
// markPoint: {
|
||||
// label: {
|
||||
// color: '#FFFFFF'
|
||||
// }
|
||||
// }
|
||||
// }]
|
||||
// })
|
||||
//
|
||||
// 类别统计图
|
||||
const foodCategoryList = [
|
||||
{ value: 12, name: '家常菜' },
|
||||
{ value: 10, name: '鲁菜' },
|
||||
{ value: 8, name: '川菜' },
|
||||
{ value: 5, name: '淮扬菜' },
|
||||
{ value: 2, name: '其他' }
|
||||
]
|
||||
foodCategoryChart.setOption(pieChartOptions('category', '次', foodCategoryList))
|
||||
//
|
||||
// // 排行榜统计图
|
||||
// const billRankList: IStatsChart[] = []
|
||||
// const resultList = foodStore.billRecordList.filter((item) => item.type === foodStore.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>
|
||||
|
||||
<style lang="scss">
|
||||
.food-chart {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(3, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
src/components/Stats/FoodRank.vue
Normal file
33
src/components/Stats/FoodRank.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div v-for="(item, index) in foodRankList.slice(0, MAX_RANK_COUNT)"
|
||||
:key="index" class="rank-item">
|
||||
<span class="rank-index">{{ index+1 }}.</span>
|
||||
<span class="rank-content">{{ item.value }}</span>
|
||||
<span class="rank-amount">{{ item.count }} 次</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const foodStore = useFoodStore()
|
||||
|
||||
const MAX_RANK_COUNT = 10
|
||||
|
||||
const foodRankList = computed(() => {
|
||||
return [{
|
||||
value: '红烧肉',
|
||||
count: 10
|
||||
}, {
|
||||
value: '红烧鱼',
|
||||
count: 8
|
||||
}, {
|
||||
value: '排骨汤',
|
||||
count: 5
|
||||
}, {
|
||||
value: '红烧带鱼',
|
||||
count: 2
|
||||
}]
|
||||
})
|
||||
</script>
|
||||
53
src/components/Stats/FoodStats.vue
Normal file
53
src/components/Stats/FoodStats.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="common-statistics">
|
||||
<stats-card
|
||||
title="菜谱总数" color="#CA6C65" unit="个" icon="iconfont icon-caipu"
|
||||
:value="foodStore.foodStats.recipeCount"/>
|
||||
|
||||
<stats-card
|
||||
title="菜谱类别" color="#6FBB69" unit="种" icon="iconfont icon-leibie"
|
||||
:value="foodStore.foodStats.categoryCount"/>
|
||||
|
||||
<stats-card
|
||||
title="做菜次数" color="#E4BF62" unit="次" icon="iconfont icon-cishu"
|
||||
:value="foodStore.foodStats.workCount"/>
|
||||
|
||||
<stats-card
|
||||
title="平均次数" color="#5470C6" unit="次/月" icon="iconfont icon-pinlv"
|
||||
:value="getAverageCount()"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatsCard from '@/components/StatsCard.vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
|
||||
const foodStore = useFoodStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await foodStore.queryFoodStats()
|
||||
})
|
||||
|
||||
const getAverageCount = () => {
|
||||
if (foodStore.queryDateType === 'month') {
|
||||
return foodStore.foodRecordList.length
|
||||
} else {
|
||||
const result = {}
|
||||
|
||||
// 遍历数据,按月统计出现次数
|
||||
foodStore.foodRecordList.forEach((item) => {
|
||||
const date = new Date(item.date)
|
||||
const month = String(date.getMonth() + 1)
|
||||
|
||||
if (result[month]) {
|
||||
result[month].count += 1
|
||||
} else {
|
||||
result[month] = { count: 1 }
|
||||
}
|
||||
})
|
||||
|
||||
return foodStore.foodRecordList.length / Object.keys(result).length
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user