4b54f71902
- migrate MemoryGridMobile.vue (grid + upload + fullscreen preview + delete) - migrate StatsPanelMobile.vue (overview + points ranking) - migrate ProfileMobile.vue (user header + stats + status + entries + logout) - migrate SettingsMobile.vue (notify/sound toggles + desktop switch + about) - migrate ChangelogMobile.vue (timeline; data synced with uat PC v0.3.6 + mobile v2.1.0 entry) - GroupViewMobile: wire memories/stats tabs (replace placeholders) - router: wire Profile/Settings/Changelog mobile views - verified: memories/points APIs + Memory type + user store logout/setStatus match uat build verified: vue-tsc + vite build pass
110 lines
3.9 KiB
Vue
110 lines
3.9 KiB
Vue
<!-- src/components-mobile/stats/StatsPanelMobile.vue -->
|
||
<!-- 手机端统计简化版:积分排行 + 群组数据 -->
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { getGroupMemberRanking } from '@/api/points'
|
||
import { getGroupGames } from '@/api/games'
|
||
import { useGroupStore } from '@/stores/group'
|
||
|
||
const props = defineProps<{ groupId: string }>()
|
||
|
||
const groupStore = useGroupStore()
|
||
|
||
const ranking = ref<{ userId: string; points: number; name?: string }[]>([])
|
||
const gameCount = ref(0)
|
||
const loading = ref(false)
|
||
|
||
const group = computed(() => groupStore.currentGroup)
|
||
const members = computed(() => groupStore.currentMembers)
|
||
|
||
onMounted(async () => {
|
||
loading.value = true
|
||
try {
|
||
const [rank, games] = await Promise.all([
|
||
getGroupMemberRanking(props.groupId, 20),
|
||
getGroupGames(props.groupId, { limit: 1 })
|
||
])
|
||
ranking.value = rank
|
||
gameCount.value = games.total
|
||
} catch (e) {
|
||
console.error(e)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
})
|
||
|
||
// 排名样式
|
||
function rankColor(index: number): string {
|
||
if (index === 0) return '#f59e0b'
|
||
if (index === 1) return '#94a3b8'
|
||
if (index === 2) return '#d97706'
|
||
return 'var(--gg-text-muted)'
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="stats-mobile">
|
||
<div v-if="loading" class="loading-box">
|
||
<van-loading size="24px">加载中...</van-loading>
|
||
</div>
|
||
|
||
<template v-else>
|
||
<!-- 概览卡片 -->
|
||
<div class="overview-card">
|
||
<div class="overview-item">
|
||
<div class="overview-num">{{ members.length }}</div>
|
||
<div class="overview-label">成员</div>
|
||
</div>
|
||
<div class="overview-item">
|
||
<div class="overview-num">{{ gameCount }}</div>
|
||
<div class="overview-label">游戏</div>
|
||
</div>
|
||
<div class="overview-item">
|
||
<div class="overview-num">{{ group?.maxMembers || '-' }}</div>
|
||
<div class="overview-label">上限</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 积分排行 -->
|
||
<div class="section">
|
||
<div class="section-title">积分排行</div>
|
||
<div v-if="ranking.length === 0" class="empty-row">暂无数据</div>
|
||
<div class="rank-list">
|
||
<div
|
||
v-for="(item, idx) in ranking"
|
||
:key="item.userId"
|
||
class="rank-item"
|
||
>
|
||
<div class="rank-num" :style="{ color: rankColor(idx) }">{{ idx + 1 }}</div>
|
||
<div class="rank-info">
|
||
<div class="rank-name">{{ item.name || '玩家' }}</div>
|
||
</div>
|
||
<div class="rank-points">{{ item.points }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.stats-mobile { padding: 12px; }
|
||
.loading-box { display: flex; justify-content: center; padding: 40px; }
|
||
|
||
.overview-card { display: flex; background: var(--gg-bg-card); border-radius: var(--gg-radius-lg); padding: 20px; box-shadow: var(--gg-shadow); margin-bottom: 16px; }
|
||
.overview-item { flex: 1; display: flex; flex-direction: column; align-items: center; }
|
||
.overview-num { font-size: 24px; font-weight: 700; color: var(--gg-primary); }
|
||
.overview-label { font-size: 12px; color: var(--gg-text-muted); margin-top: 4px; }
|
||
|
||
.section { margin-bottom: 16px; }
|
||
.section-title { font-size: 15px; font-weight: 600; color: var(--gg-text); margin-bottom: 10px; }
|
||
.empty-row { text-align: center; padding: 20px; color: var(--gg-text-muted); font-size: 13px; }
|
||
|
||
.rank-list { display: flex; flex-direction: column; gap: 8px; }
|
||
.rank-item { display: flex; align-items: center; gap: 12px; background: var(--gg-bg-card); padding: 12px 14px; border-radius: var(--gg-radius-sm); box-shadow: var(--gg-shadow); }
|
||
.rank-num { font-size: 18px; font-weight: 700; width: 28px; text-align: center; }
|
||
.rank-info { flex: 1; min-width: 0; }
|
||
.rank-name { font-size: 14px; color: var(--gg-text); }
|
||
.rank-points { font-size: 15px; font-weight: 600; color: var(--gg-primary); }
|
||
</style>
|