182 lines
4.3 KiB
Vue
182 lines
4.3 KiB
Vue
<template>
|
|
<div class="stats-page">
|
|
<van-nav-bar
|
|
title="数据统计"
|
|
left-arrow
|
|
@click-left="onClickLeft"
|
|
fixed
|
|
placeholder
|
|
/>
|
|
|
|
<div v-if="stats" class="stats-content">
|
|
<div class="header-card">
|
|
<h3>{{ stats.activityTitle }}</h3>
|
|
<van-grid :column-num="3" :border="false">
|
|
<van-grid-item>
|
|
<div class="stat-num">{{ stats.registeredCount }}</div>
|
|
<div class="stat-label">报名人数</div>
|
|
</van-grid-item>
|
|
<van-grid-item>
|
|
<div class="stat-num">{{ stats.checkedInCount }}</div>
|
|
<div class="stat-label">签到人数</div>
|
|
</van-grid-item>
|
|
<van-grid-item>
|
|
<div class="stat-num">{{ (stats.checkInRate * 100).toFixed(1) }}%</div>
|
|
<div class="stat-label">签到率</div>
|
|
</van-grid-item>
|
|
</van-grid>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<h4>评分分布</h4>
|
|
<div class="rating-dist">
|
|
<div class="rate-row" v-for="score in [5,4,3,2,1]" :key="score">
|
|
<span class="label">{{ score }}星</span>
|
|
<van-progress
|
|
:percentage="getPercentage(score)"
|
|
:show-pivot="true"
|
|
color="#ffd21e"
|
|
stroke-width="8"
|
|
/>
|
|
<span class="count">{{ stats.ratingDistribution[score] || 0 }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="avg-rating">
|
|
平均分: <strong>{{ stats.averageRating.toFixed(1) }}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<van-button icon="down" block type="success" @click="handleExport">导出数据报表</van-button>
|
|
</div>
|
|
</div>
|
|
|
|
<van-loading v-else class="loading" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import { getActivityStats, exportActivityStats } from '@/services/stats';
|
|
import type { ActivityStats } from '@/services/stats';
|
|
import { showToast } from 'vant';
|
|
|
|
const route = useRoute();
|
|
const activityId = Number(route.params.id);
|
|
const stats = ref<ActivityStats | null>(null);
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const res = await getActivityStats(activityId);
|
|
stats.value = res;
|
|
} catch (error) {
|
|
showToast('加载统计数据失败');
|
|
}
|
|
});
|
|
|
|
const onClickLeft = () => history.back();
|
|
|
|
const getPercentage = (score: number) => {
|
|
if (!stats.value || !stats.value.reviewCount) return 0;
|
|
const count = stats.value.ratingDistribution[score] || 0;
|
|
return Math.round((count / stats.value.reviewCount) * 100);
|
|
};
|
|
|
|
const handleExport = async () => {
|
|
try {
|
|
const blob = await exportActivityStats(activityId);
|
|
// Download logic
|
|
const url = window.URL.createObjectURL(new Blob([blob as any]));
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.setAttribute('download', `activity-${activityId}-stats.xlsx`);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
showToast('导出成功');
|
|
} catch (error) {
|
|
showToast('导出失败');
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.stats-page {
|
|
min-height: 100vh;
|
|
background-color: #f7f8fa;
|
|
|
|
.stats-content {
|
|
padding: 16px;
|
|
}
|
|
|
|
.header-card, .chart-card {
|
|
background: #fff;
|
|
padding: 16px;
|
|
border-radius: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.stat-num {
|
|
font-size: 20px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 12px;
|
|
color: #999;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.chart-card {
|
|
h4 {
|
|
margin: 0 0 16px;
|
|
}
|
|
|
|
.rating-dist {
|
|
.rate-row {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
|
|
.label {
|
|
width: 30px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.van-progress {
|
|
flex: 1;
|
|
margin: 0 10px;
|
|
}
|
|
|
|
.count {
|
|
width: 20px;
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
|
|
.avg-rating {
|
|
text-align: center;
|
|
margin-top: 16px;
|
|
font-size: 16px;
|
|
|
|
strong {
|
|
font-size: 24px;
|
|
color: #ffd21e;
|
|
}
|
|
}
|
|
}
|
|
|
|
.actions {
|
|
margin-top: 32px;
|
|
}
|
|
|
|
.loading {
|
|
margin-top: 100px;
|
|
text-align: center;
|
|
}
|
|
}
|
|
</style>
|