289 lines
8.4 KiB
Vue
289 lines
8.4 KiB
Vue
<template>
|
||
<div class="statistics-container">
|
||
<!-- 第一行:宝贝统计 -->
|
||
<el-row :gutter="20" class="data-overview">
|
||
<el-col :span="8" v-for="item in babyData" :key="item.title">
|
||
<el-card shadow="hover" class="data-card">
|
||
<div class="card-content">
|
||
<div class="icon-box" :style="{ backgroundColor: item.color }">
|
||
<i :class="item.icon"></i>
|
||
</div>
|
||
<div class="text-box">
|
||
<div class="title">{{ item.title }}</div>
|
||
<div class="value">{{ item.value.toLocaleString() }}</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 第二行:用户统计 -->
|
||
<el-row :gutter="20" class="data-overview" style="margin-top: 20px;">
|
||
<el-col :span="8" v-for="item in userData" :key="item.title">
|
||
<el-card shadow="hover" class="data-card">
|
||
<div class="card-content">
|
||
<div class="icon-box" :style="{ backgroundColor: item.color }">
|
||
<i :class="item.icon"></i>
|
||
</div>
|
||
<div class="text-box">
|
||
<div class="title">{{ item.title }}</div>
|
||
<div class="value">{{ item.value.toLocaleString() }}</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 图表区域 -->
|
||
<el-row :gutter="20" class="charts-row" style="margin-top: 20px;">
|
||
<!-- 宝贝增长趋势柱状图 -->
|
||
<el-col :span="12">
|
||
<el-card shadow="hover" header="宝贝增长趋势">
|
||
<div ref="babyChartRef" class="chart-box"></div>
|
||
</el-card>
|
||
</el-col>
|
||
|
||
<!-- 用户增长趋势柱状图 -->
|
||
<el-col :span="12">
|
||
<el-card shadow="hover" header="用户增长趋势">
|
||
<div ref="userChartRef" class="chart-box"></div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted, shallowRef, nextTick } from 'vue';
|
||
import * as echarts from 'echarts';
|
||
import { getBabyCounts, getUserCounts } from '@/api/babyhealth';
|
||
|
||
// --- 类型定义 ---
|
||
interface SummaryItem {
|
||
title: string;
|
||
value: number;
|
||
icon: string;
|
||
color: string;
|
||
percentage: number;
|
||
isUp: boolean;
|
||
}
|
||
|
||
// --- 响应式数据 ---
|
||
const babyChartRef = ref<HTMLElement | null>(null);
|
||
const userChartRef = ref<HTMLElement | null>(null);
|
||
const babyChartInstance = shallowRef<echarts.ECharts | null>(null);
|
||
const userChartInstance = shallowRef<echarts.ECharts | null>(null);
|
||
|
||
const babyData = ref<SummaryItem[]>([
|
||
{ title: '总宝贝数', value: 0, icon: 'fa-solid fa-baby', color: '#67C23A', percentage: 0, isUp: false },
|
||
{ title: '男宝宝数', value: 0, icon: 'fa-solid fa-person', color: '#409EFF', percentage: 0, isUp: false },
|
||
{ title: '女宝宝数', value: 0, icon: 'fa-solid fa-person-dress', color: '#F56C6C', percentage: 0, isUp: false }
|
||
]);
|
||
|
||
const userData = ref<SummaryItem[]>([
|
||
{ title: '总用户数', value: 0, icon: 'fa-solid fa-users', color: '#3973FF', percentage: 0, isUp: false },
|
||
{ title: '父亲数', value: 0, icon: 'fa-solid fa-person', color: '#409EFF', percentage: 0, isUp: false },
|
||
{ title: '母亲数', value: 0, icon: 'fa-solid fa-person-dress', color: '#F56C6C', percentage: 0, isUp: false }
|
||
]);
|
||
|
||
// 调用宝贝统计接口
|
||
async function fetchGetBabyDatas() {
|
||
const res = await getBabyCounts();
|
||
|
||
if (res.code === 200 && res.data) {
|
||
const { total, male, female } = res.data;
|
||
|
||
// 更新 babyData
|
||
babyData.value = [
|
||
{ title: '总宝贝数', value: total, icon: 'fa-solid fa-baby', color: '#67C23A', percentage: 0, isUp: false },
|
||
{ title: '男宝宝数', value: male !== undefined ? male : 0, icon: 'fa-solid fa-person', color: '#409EFF', percentage: 0, isUp: false },
|
||
{ title: '女宝宝数', value: female !== undefined ? female : 0, icon: 'fa-solid fa-person-dress', color: '#F56C6C', percentage: 0, isUp: false }
|
||
];
|
||
|
||
// 更新宝贝柱状图
|
||
updateBabyChart(total, male, female);
|
||
}
|
||
}
|
||
|
||
// 调用用户统计接口
|
||
async function fetchGetUserDatas() {
|
||
const res = await getUserCounts();
|
||
|
||
if (res.code === 200 && res.data) {
|
||
const { total, father, mother } = res.data;
|
||
|
||
// 更新 userData
|
||
userData.value = [
|
||
{ title: '总用户数', value: total, icon: 'fa-solid fa-users', color: '#3973FF', percentage: 0, isUp: false },
|
||
{ title: '父亲数', value: father !== undefined ? father : 0, icon: 'fa-solid fa-person', color: '#409EFF', percentage: 0, isUp: false },
|
||
{ title: '母亲数', value: mother !== undefined ? mother : 0, icon: 'fa-solid fa-person-dress', color: '#F56C6C', percentage: 0, isUp: false }
|
||
];
|
||
|
||
// 更新用户柱状图
|
||
updateUserChart(total, father, mother);
|
||
}
|
||
}
|
||
|
||
// 初始化宝贝柱状图
|
||
const initBabyChart = () => {
|
||
if (babyChartRef.value) {
|
||
babyChartInstance.value = echarts.init(babyChartRef.value);
|
||
babyChartInstance.value.setOption({
|
||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: ['总宝贝数', '男宝宝数', '女宝宝数'],
|
||
axisTick: { alignWithLabel: true }
|
||
},
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{
|
||
name: '数量',
|
||
type: 'bar',
|
||
barWidth: '60%',
|
||
data: [
|
||
{ value: 0, itemStyle: { color: '#67C23A' } },
|
||
{ value: 0, itemStyle: { color: '#409EFF' } },
|
||
{ value: 0, itemStyle: { color: '#F56C6C' } }
|
||
]
|
||
}
|
||
]
|
||
});
|
||
}
|
||
};
|
||
|
||
// 初始化用户柱状图
|
||
const initUserChart = () => {
|
||
if (userChartRef.value) {
|
||
userChartInstance.value = echarts.init(userChartRef.value);
|
||
userChartInstance.value.setOption({
|
||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: ['总用户数', '父亲数', '母亲数'],
|
||
axisTick: { alignWithLabel: true }
|
||
},
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{
|
||
name: '数量',
|
||
type: 'bar',
|
||
barWidth: '60%',
|
||
data: [
|
||
{ value: 0, itemStyle: { color: '#3973FF' } },
|
||
{ value: 0, itemStyle: { color: '#409EFF' } },
|
||
{ value: 0, itemStyle: { color: '#F56C6C' } }
|
||
]
|
||
}
|
||
]
|
||
});
|
||
}
|
||
};
|
||
|
||
// 更新宝贝柱状图数据
|
||
const updateBabyChart = (total: number, male: number, female: number) => {
|
||
if (babyChartInstance.value) {
|
||
babyChartInstance.value.setOption({
|
||
series: [{
|
||
data: [
|
||
{ value: total, itemStyle: { color: '#67C23A' } },
|
||
{ value: male, itemStyle: { color: '#409EFF' } },
|
||
{ value: female, itemStyle: { color: '#F56C6C' } }
|
||
]
|
||
}]
|
||
});
|
||
}
|
||
};
|
||
|
||
// 更新用户柱状图数据
|
||
const updateUserChart = (total: number, father: number, mother: number) => {
|
||
if (userChartInstance.value) {
|
||
userChartInstance.value.setOption({
|
||
series: [{
|
||
data: [
|
||
{ value: total, itemStyle: { color: '#3973FF' } },
|
||
{ value: father, itemStyle: { color: '#409EFF' } },
|
||
{ value: mother, itemStyle: { color: '#F56C6C' } }
|
||
]
|
||
}]
|
||
});
|
||
}
|
||
};
|
||
|
||
// 生命周期与自适应
|
||
const handleResize = () => {
|
||
babyChartInstance.value?.resize();
|
||
userChartInstance.value?.resize();
|
||
};
|
||
|
||
onMounted(() => {
|
||
initBabyChart();
|
||
initUserChart();
|
||
fetchGetBabyDatas();
|
||
fetchGetUserDatas();
|
||
window.addEventListener('resize', handleResize);
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener('resize', handleResize);
|
||
babyChartInstance.value?.dispose();
|
||
userChartInstance.value?.dispose();
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.statistics-container {
|
||
padding: 20px;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.data-overview {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.data-card {
|
||
.card-content {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.icon-box {
|
||
width: 56px;
|
||
height: 56px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 15px;
|
||
color: #fff;
|
||
font-size: 24px;
|
||
}
|
||
}
|
||
|
||
.text-box {
|
||
.title {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
}
|
||
|
||
.value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin: 4px 0;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
}
|
||
|
||
.charts-row {
|
||
margin-top: 20px;
|
||
|
||
.chart-box {
|
||
height: 350px;
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|