Files
akmon/pages/admins/sports/stats.uvue
2026-01-20 08:04:15 +08:00

155 lines
3.4 KiB
Plaintext

<template>
<view class="sports-stats-page">
<view class="stats-controls">
<picker mode="date" :value="selectedDate" @change="onDateChange">
<view class="picker">
当前选择: {{ selectedDate }}
</view>
</picker>
<button @click="fetchStats">查询</button>
</view>
<view v-if="loading" class="loading-container">加载中...</view>
<view v-else-if="error" class="error-container">{{ error }}</view>
<view v-else class="stats-container">
<view class="stat-card">
<text class="stat-title">总步数</text>
<text class="stat-value">{{ summary.total_steps ?? 0 }}</text>
</view>
<view class="stat-card">
<text class="stat-title">总距离 (米)</text>
<text class="stat-value">{{ summary.total_distance?.toFixed(2) ?? 0 }}</text>
</view>
<view class="stat-card">
<text class="stat-title">平均心率</text>
<text class="stat-value">{{ summary.avg_heart_rate?.toFixed(1) ?? 0 }}</text>
</view>
<view class="stat-card">
<text class="stat-title">活跃用户</text>
<text class="stat-value">{{ summary.active_users ?? 0 }}</text>
</view>
</view>
<!-- Add charts here later -->
<view class="chart-container">
<text>图表区域</text>
</view>
</view>
</template>
<script lang="uts">
import { ref, reactive, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
type SportsSummary = {
total_steps: number
total_distance: number
avg_heart_rate: number
active_users: number
}
export default {
setup() {
const selectedDate = ref(new Date().toISOString().slice(0, 10))
const loading = ref(false)
const error = ref<string | null>(null)
const summary = reactive < SportsSummary > ({
total_steps: 0,
total_distance: 0,
avg_heart_rate: 0,
active_users: 0
})
const onDateChange = (e: any) => {
selectedDate.value = e.detail.value
}
const fetchStats = async () => {
loading.value = true
error.value = null
const date = selectedDate.value
const result = await supa.rpc('get_daily_sports_stats', { query_date: date })
if (result.error) {
error.value = `获取统计数据失败: ${result.error.message}`
console.error(result.error)
} else if (result.data) {
const stats = result.data[0]
summary.total_steps = stats.total_steps
summary.total_distance = stats.total_distance
summary.avg_heart_rate = stats.avg_heart_rate
summary.active_users = stats.active_users
}
loading.value = false
}
onMounted(() => {
fetchStats()
})
return {
selectedDate,
loading,
error,
summary,
onDateChange,
fetchStats
}
}
}
</script>
<style>
.sports-stats-page {
padding: 15px;
}
.stats-controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
align-items: center;
}
.stats-controls .picker {
border: 1px solid #ccc;
padding: 8px 12px;
border-radius: 5px;
}
.stats-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
}
.stat-card {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
text-align: center;
}
.stat-title {
font-size: 14px;
color: #666;
display: block;
margin-bottom: 10px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.chart-container {
margin-top: 30px;
padding: 20px;
background-color: #fff;
border-radius: 8px;
}
</style>