271 lines
6.0 KiB
Vue
271 lines
6.0 KiB
Vue
<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>
|
||
|