209 lines
6.2 KiB
Vue
209 lines
6.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, computed } from 'vue'
|
|
import { statsAPI } from '../services/api'
|
|
import { Bar, Pie, Doughnut } from 'vue-chartjs'
|
|
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, ArcElement } from 'chart.js'
|
|
|
|
// Register ChartJS components
|
|
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, ArcElement)
|
|
|
|
// Stats data
|
|
const stats = ref({
|
|
user_count: 0,
|
|
prizes_awarded: 0,
|
|
prizes_shipped: 0,
|
|
cards_collected: 0,
|
|
prize_distribution: [] as { name: string, count: number }[]
|
|
})
|
|
|
|
// Load stats
|
|
const loadStats = async () => {
|
|
try {
|
|
const data = await statsAPI.getStats()
|
|
stats.value = data
|
|
} catch (error) {
|
|
console.error('Failed to load stats:', error)
|
|
}
|
|
}
|
|
|
|
// Chart data
|
|
const prizeDistributionData = computed(() => {
|
|
return {
|
|
labels: stats.value.prize_distribution.map(item => item.name),
|
|
datasets: [
|
|
{
|
|
label: '奖品分布',
|
|
backgroundColor: [
|
|
'#6A5ACD', // primary
|
|
'#FF6B6B', // secondary
|
|
'#4ECDC4', // accent
|
|
'#2ECC71', // success
|
|
'#FFD700', // warning
|
|
],
|
|
data: stats.value.prize_distribution.map(item => item.count),
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
const prizesStatusData = computed(() => {
|
|
return {
|
|
labels: ['已发放', '待发放'],
|
|
datasets: [
|
|
{
|
|
backgroundColor: ['#2ECC71', '#FFD700'],
|
|
data: [stats.value.prizes_shipped, stats.value.prizes_awarded - stats.value.prizes_shipped],
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
const userActivityData = computed(() => {
|
|
return {
|
|
labels: ['用户数', '收集卡片数'],
|
|
datasets: [
|
|
{
|
|
label: '用户活动统计',
|
|
backgroundColor: ['#6A5ACD', '#4ECDC4'],
|
|
data: [stats.value.user_count, stats.value.cards_collected],
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
// Chart options
|
|
const chartOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: '抽奖系统数据分析'
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
onMounted(() => {
|
|
loadStats()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="dashboard">
|
|
<h1 class="text-2xl font-bold text-primary mb-6">系统数据看板</h1>
|
|
|
|
<!-- Stat cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
|
<div class="card bg-primary/10">
|
|
<div class="flex items-center">
|
|
<div class="rounded-full bg-primary w-12 h-12 flex items-center justify-center text-white">
|
|
<i class="fas fa-users"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm text-gray-500">用户总数</div>
|
|
<div class="text-2xl font-bold">{{ stats.user_count }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-secondary/10">
|
|
<div class="flex items-center">
|
|
<div class="rounded-full bg-secondary w-12 h-12 flex items-center justify-center text-white">
|
|
<i class="fas fa-gift"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm text-gray-500">奖品发放</div>
|
|
<div class="text-2xl font-bold">{{ stats.prizes_awarded }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-success/10">
|
|
<div class="flex items-center">
|
|
<div class="rounded-full bg-success w-12 h-12 flex items-center justify-center text-white">
|
|
<i class="fas fa-shipping-fast"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm text-gray-500">已发货</div>
|
|
<div class="text-2xl font-bold">{{ stats.prizes_shipped }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-accent/10">
|
|
<div class="flex items-center">
|
|
<div class="rounded-full bg-accent w-12 h-12 flex items-center justify-center text-white">
|
|
<i class="fas fa-layer-group"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm text-gray-500">卡片收集</div>
|
|
<div class="text-2xl font-bold">{{ stats.cards_collected }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
<div class="card">
|
|
<h2 class="text-lg font-bold mb-4">奖品分布情况</h2>
|
|
<div class="h-80">
|
|
<Pie
|
|
:data="prizeDistributionData"
|
|
:options="chartOptions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 class="text-lg font-bold mb-4">奖品发放状态</h2>
|
|
<div class="h-80">
|
|
<Doughnut
|
|
:data="prizesStatusData"
|
|
:options="chartOptions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 class="text-lg font-bold mb-4">用户活动统计</h2>
|
|
<div class="h-80">
|
|
<Bar
|
|
:data="userActivityData"
|
|
:options="chartOptions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Prize distribution table -->
|
|
<div class="card">
|
|
<h2 class="text-lg font-bold mb-4">奖品发放明细</h2>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">奖品名称</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">发放数量</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">百分比</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<tr v-for="item in stats.prize_distribution" :key="item.name">
|
|
<td class="px-6 py-4 whitespace-nowrap">{{ item.name }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">{{ item.count }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
{{ stats.prizes_awarded > 0 ? Math.round((item.count / stats.prizes_awarded) * 100) : 0 }}%
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|