271 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<DefaultLayout>
<div class="my-badges-page">
<div class="container">
<!-- 页面标题 -->
<div class="page-header">
<a-button type="text" @click="router.back()" class="back-btn">
<Icon name="chevronLeft" size="md" color="#AFAFAF" />
</a-button>
<div class="header-info">
<h1 class="page-title">
<Icon name="star" size="xl" color="#FFD700" style="margin-right: 12px;" />
我的徽章
</h1>
<p class="page-desc">收集徽章,展示你的成就</p>
</div>
</div>
<!-- 统计信息 -->
<div class="stats-bar">
<div class="stat-item">
<span class="stat-value">{{ myBadges.length }}</span>
<span class="stat-label">已获得</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ allBadges.length }}</span>
<span class="stat-label">全部徽章</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ progressPercent }}%</span>
<span class="stat-label">收集进度</span>
</div>
</div>
<!-- 徽章展示 -->
<div class="badges-section">
<h2 class="section-title">全部徽章</h2>
<div class="badges-grid" v-if="!loading">
<div
v-for="badge in allBadges"
:key="badge.badgeCode"
:class="['badge-card', { 'unlocked': isUnlocked(badge.badgeCode) }]"
>
<div class="badge-icon" :style="{ backgroundColor: isUnlocked(badge.badgeCode) ? badge.badgeColor : '#3A3A3A' }">
{{ badge.badgeName.charAt(0) }}
</div>
<div class="badge-info">
<h3 class="badge-name">{{ badge.badgeName }}</h3>
<p class="badge-desc">{{ badge.description }}</p>
<a-tag v-if="isUnlocked(badge.badgeCode)" color="green">已获得</a-tag>
<a-tag v-else color="default">未解锁</a-tag>
</div>
<div class="badge-time" v-if="getUnlockTime(badge.badgeCode)">
{{ formatTime(getUnlockTime(badge.badgeCode)!) }}
</div>
</div>
</div>
<div v-else class="loading-container">
<a-spin size="large" />
</div>
</div>
</div>
</div>
</DefaultLayout>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import Icon from '@/components/Icon.vue'
import dayjs from 'dayjs'
import {
getMyBadges,
getAllBadges,
type UserBadge,
type BadgeDefinition
} from '@/api/rewardRule'
const router = useRouter()
const loading = ref(false)
const myBadges = ref<UserBadge[]>([])
const allBadges = ref<BadgeDefinition[]>([])
const progressPercent = computed(() => {
if (allBadges.value.length === 0) return 0
return Math.round((myBadges.value.length / allBadges.value.length) * 100)
})
const isUnlocked = (badgeCode: string) => {
return myBadges.value.some(b => b.badgeCode === badgeCode)
}
const getUnlockTime = (badgeCode: string): string | undefined => {
const badge = myBadges.value.find(b => b.badgeCode === badgeCode)
return badge?.createTime
}
const formatTime = (time: string) => {
return dayjs(time).format('YYYY-MM-DD')
}
const loadData = async () => {
loading.value = true
try {
const [badges, all] = await Promise.all([
getMyBadges(),
getAllBadges()
])
myBadges.value = badges
allBadges.value = all
} catch (error) {
console.error('加载徽章数据失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
@import "@/styles/variables.scss";
.my-badges-page {
min-height: 100vh;
padding: 24px;
background: $bg-primary;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.page-header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 24px;
}
.back-btn {
margin-top: 4px;
}
.page-title {
font-size: 28px;
font-weight: 600;
color: $text-primary;
display: flex;
align-items: center;
margin-bottom: 8px;
}
.page-desc {
color: $text-secondary;
font-size: 14px;
}
.stats-bar {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: 12px;
padding: 20px;
display: flex;
justify-content: space-around;
margin-bottom: 32px;
}
.stat-item {
text-align: center;
.stat-value {
display: block;
font-size: 32px;
font-weight: 700;
color: #FFD700;
}
.stat-label {
display: block;
font-size: 13px;
color: $text-tertiary;
margin-top: 4px;
}
}
.badges-section {
.section-title {
font-size: 18px;
font-weight: 600;
color: $text-primary;
margin-bottom: 16px;
}
}
.badges-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.badge-card {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: 12px;
padding: 20px;
display: flex;
gap: 16px;
opacity: 0.5;
transition: all 0.3s;
&.unlocked {
opacity: 1;
border-color: #FFD700;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(255, 215, 0, 0.2);
}
}
.badge-icon {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 24px;
font-weight: 600;
flex-shrink: 0;
}
.badge-info {
flex: 1;
.badge-name {
font-size: 16px;
font-weight: 600;
color: $text-primary;
margin-bottom: 4px;
}
.badge-desc {
font-size: 13px;
color: $text-secondary;
margin-bottom: 8px;
}
}
.badge-time {
font-size: 12px;
color: $text-tertiary;
white-space: nowrap;
}
}
.loading-container {
display: flex;
justify-content: center;
padding: 48px;
}
</style>