319 lines
7.5 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-titles-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="badge" size="xl" color="#8B5CF6" style="margin-right: 12px;" />
我的称号
</h1>
<p class="page-desc">选择一个称号展示在你的名字旁边</p>
</div>
</div>
<!-- 当前称号 -->
<div class="current-title-card">
<div class="card-label">当前佩戴</div>
<div class="current-title" v-if="activeTitle">
<span class="title-name" :style="{ color: activeTitle.titleColor }">
{{ activeTitle.titleName }}
</span>
<a-button type="link" size="small" @click="handleRemoveTitle">取消佩戴</a-button>
</div>
<div class="no-title" v-else>
<span>暂未佩戴称号</span>
</div>
</div>
<!-- 称号列表 -->
<div class="titles-section">
<h2 class="section-title">我的称号</h2>
<div class="titles-grid" v-if="!loading">
<div
v-for="title in myTitles"
:key="title.id"
:class="['title-card', { 'active': activeTitle?.id === title.id }]"
@click="handleSelectTitle(title)"
>
<div class="title-badge" :style="{ backgroundColor: title.titleColor + '20', borderColor: title.titleColor }">
<span class="title-name" :style="{ color: title.titleColor }">{{ title.titleName }}</span>
</div>
<p class="title-desc">{{ title.description }}</p>
<div class="title-meta">
<span class="title-time">获得于 {{ formatTime(title.createTime) }}</span>
<a-tag v-if="activeTitle?.id === title.id" color="purple">使用中</a-tag>
</div>
</div>
<div v-if="myTitles.length === 0" class="empty-titles">
暂无称号,完成任务获取称号吧!
</div>
</div>
<div v-else class="loading-container">
<a-spin size="large" />
</div>
</div>
<!-- 全部称号 -->
<div class="titles-section">
<h2 class="section-title">全部称号</h2>
<div class="titles-grid">
<div
v-for="title in allTitles"
:key="title.titleCode"
:class="['title-card', 'all', { 'unlocked': isUnlocked(title.titleCode) }]"
>
<div class="title-badge" :style="{
backgroundColor: isUnlocked(title.titleCode) ? title.titleColor + '20' : '#2A2A2A',
borderColor: isUnlocked(title.titleCode) ? title.titleColor : '#3A3A3A'
}">
<span class="title-name" :style="{ color: isUnlocked(title.titleCode) ? title.titleColor : '#6B6B6B' }">
{{ title.titleName }}
</span>
</div>
<p class="title-desc">{{ title.description }}</p>
<a-tag v-if="isUnlocked(title.titleCode)" color="green">已解锁</a-tag>
<a-tag v-else color="default">未解锁</a-tag>
</div>
</div>
</div>
</div>
</div>
</DefaultLayout>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import Icon from '@/components/Icon.vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import {
getMyTitles,
getAllTitles,
getMyActiveTitle,
setActiveTitle,
type UserTitle,
type TitleDefinition
} from '@/api/rewardRule'
const router = useRouter()
const loading = ref(false)
const myTitles = ref<UserTitle[]>([])
const allTitles = ref<TitleDefinition[]>([])
const activeTitle = ref<UserTitle | null>(null)
const isUnlocked = (titleCode: string) => {
return myTitles.value.some(t => t.titleCode === titleCode)
}
const formatTime = (time: string) => {
return dayjs(time).format('YYYY-MM-DD')
}
const handleSelectTitle = async (title: UserTitle) => {
try {
await setActiveTitle(title.id)
activeTitle.value = title
message.success('称号已更换')
} catch (error) {
message.error('更换失败')
}
}
const handleRemoveTitle = async () => {
try {
await setActiveTitle(undefined)
activeTitle.value = null
message.success('已取消佩戴')
} catch (error) {
message.error('操作失败')
}
}
const loadData = async () => {
loading.value = true
try {
const [titles, all, active] = await Promise.all([
getMyTitles(),
getAllTitles(),
getMyActiveTitle()
])
myTitles.value = titles
allTitles.value = all
activeTitle.value = active
} catch (error) {
console.error('加载称号数据失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
@import "@/styles/variables.scss";
.my-titles-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;
}
.current-title-card {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 1px solid $border-primary;
border-radius: 12px;
padding: 24px;
margin-bottom: 32px;
.card-label {
font-size: 12px;
color: $text-tertiary;
margin-bottom: 12px;
}
.current-title {
display: flex;
align-items: center;
justify-content: space-between;
.title-name {
font-size: 24px;
font-weight: 700;
}
}
.no-title {
color: $text-tertiary;
font-size: 14px;
}
}
.titles-section {
margin-bottom: 32px;
.section-title {
font-size: 18px;
font-weight: 600;
color: $text-primary;
margin-bottom: 16px;
}
}
.titles-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.title-card {
background: $bg-card;
border: 2px solid $border-primary;
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.3s;
&:hover {
border-color: #8B5CF6;
}
&.active {
border-color: #8B5CF6;
background: rgba(139, 92, 246, 0.1);
}
&.all {
cursor: default;
&:not(.unlocked) {
opacity: 0.5;
}
}
.title-badge {
display: inline-block;
padding: 8px 20px;
border-radius: 20px;
border: 2px solid;
margin-bottom: 12px;
}
.title-name {
font-weight: 600;
font-size: 16px;
}
.title-desc {
font-size: 13px;
color: $text-secondary;
margin-bottom: 12px;
}
.title-meta {
display: flex;
justify-content: space-between;
align-items: center;
.title-time {
font-size: 12px;
color: $text-tertiary;
}
}
}
.empty-titles {
grid-column: 1 / -1;
text-align: center;
padding: 48px;
color: $text-tertiary;
}
.loading-container {
display: flex;
justify-content: center;
padding: 48px;
}
</style>