qiangdanxitong/frontend/src/views/IncentiveCenter.vue

508 lines
13 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="incentive-center">
<div class="container">
<!-- 页面标题 -->
<div class="page-header">
<h1 class="page-title">
<Icon name="sparkles" size="xl" color="#FFD700" style="margin-right: 12px;" />
激励中心
</h1>
<p class="page-desc">参与活动、达成目标、赢取奖励!</p>
</div>
<!-- 我的积分卡片 -->
<div class="score-card">
<div class="score-main">
<div class="score-label">我的积分</div>
<div class="score-value">{{ userScore }}</div>
</div>
<div class="score-actions">
<div class="score-stats">
<div class="stat">
<span class="stat-value">{{ continuousCheckIn }}</span>
<span class="stat-label">连续签到</span>
</div>
<div class="stat">
<span class="stat-value">{{ achievementCount }}</span>
<span class="stat-label">已获成就</span>
</div>
<div class="stat">
<span class="stat-value">{{ activeGoalCount }}</span>
<span class="stat-label">进行中目标</span>
</div>
</div>
</div>
</div>
<!-- 功能模块入口 -->
<div class="module-grid">
<!-- 每日签到 -->
<div class="module-card checkin" @click="router.push('/checkin')">
<div class="module-icon">
<Icon name="calendar" size="xl" color="#31f08b" />
</div>
<div class="module-info">
<h3 class="module-title">每日签到</h3>
<p class="module-desc">坚持签到,累计积分</p>
</div>
<div class="module-status" v-if="hasCheckedIn">
<a-tag color="green">今日已签</a-tag>
</div>
<div class="module-status" v-else>
<a-tag color="orange">待签到</a-tag>
</div>
<Icon name="chevronRight" size="md" color="#6B6B6B" class="module-arrow" />
</div>
<!-- 成就中心 -->
<div class="module-card achievement" @click="router.push('/achievements')">
<div class="module-icon">
<Icon name="trophy" size="xl" color="#FFD700" />
</div>
<div class="module-info">
<h3 class="module-title">成就中心</h3>
<p class="module-desc">解锁成就,展示荣誉</p>
</div>
<div class="module-badge" v-if="unclaimedAchievements > 0">
<span class="badge-count">{{ unclaimedAchievements }}</span>
</div>
<Icon name="chevronRight" size="md" color="#6B6B6B" class="module-arrow" />
</div>
<!-- 我的目标 -->
<div class="module-card goals" @click="router.push('/goals')">
<div class="module-icon">
<Icon name="flag" size="xl" color="#3B82F6" />
</div>
<div class="module-info">
<h3 class="module-title">我的目标</h3>
<p class="module-desc">设定目标,持续成长</p>
</div>
<div class="module-progress" v-if="activeGoalCount > 0">
<span>{{ activeGoalCount }} 个进行中</span>
</div>
<Icon name="chevronRight" size="md" color="#6B6B6B" class="module-arrow" />
</div>
<!-- 竞赛活动 -->
<div class="module-card contest" @click="router.push('/contests')">
<div class="module-icon">
<Icon name="fire" size="xl" color="#EF4444" />
</div>
<div class="module-info">
<h3 class="module-title">竞赛活动</h3>
<p class="module-desc">参与竞赛,争夺荣耀</p>
</div>
<div class="module-status" v-if="activeContestCount > 0">
<a-tag color="red">{{ activeContestCount }} 场进行中</a-tag>
</div>
<Icon name="chevronRight" size="md" color="#6B6B6B" class="module-arrow" />
</div>
<!-- 团队PK -->
<div class="module-card team-pk" @click="router.push('/team-pk')">
<div class="module-icon">
<Icon name="bolt" size="xl" color="#F59E0B" />
</div>
<div class="module-info">
<h3 class="module-title">团队 PK</h3>
<p class="module-desc">部门对战,为团队而战</p>
</div>
<div class="module-status" v-if="activePKCount > 0">
<a-tag color="orange">{{ activePKCount }} 场对战中</a-tag>
</div>
<Icon name="chevronRight" size="md" color="#6B6B6B" class="module-arrow" />
</div>
<!-- 排行榜 -->
<div class="module-card leaderboard" @click="router.push('/designer-hall')">
<div class="module-icon">
<Icon name="chartBar" size="xl" color="#8B5CF6" />
</div>
<div class="module-info">
<h3 class="module-title">排行榜</h3>
<p class="module-desc">查看榜单,了解排名</p>
</div>
<div class="module-rank" v-if="myRank > 0">
<span>当前 #{{ myRank }}</span>
</div>
<Icon name="chevronRight" size="md" color="#6B6B6B" class="module-arrow" />
</div>
</div>
<!-- 积分记录 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">
<Icon name="clock" size="lg" color="#AFAFAF" style="margin-right: 8px;" />
积分记录
</h2>
<a-button type="link" @click="viewAllRecords">查看全部</a-button>
</div>
<div class="record-list">
<div v-for="record in recentRecords" :key="record.id" class="record-item">
<div class="record-info">
<Icon :name="getRecordIcon(record.type)" size="md" :color="record.score > 0 ? '#31f08b' : '#EF4444'" />
<div class="record-detail">
<span class="record-title">{{ record.title }}</span>
<span class="record-time">{{ formatTime(record.time) }}</span>
</div>
</div>
<div :class="['record-score', record.score > 0 ? 'positive' : 'negative']">
{{ record.score > 0 ? '+' : '' }}{{ record.score }}
</div>
</div>
<div v-if="recentRecords.length === 0" class="empty-records">
暂无积分记录
</div>
</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 { useAuthStore } from '@/stores/auth'
import dayjs from 'dayjs'
import { hasCheckedInToday, getCheckInStats } from '@/api/incentive'
import { getMyGoals } from '@/api/goal'
import { getContestList } from '@/api/contest'
import { getTeamPKList } from '@/api/teamPK'
const router = useRouter()
const authStore = useAuthStore()
// 用户数据
const userScore = computed(() => authStore.userInfo?.score || 0)
const continuousCheckIn = ref(0)
const achievementCount = ref(0)
const activeGoalCount = ref(0)
const activeContestCount = ref(0)
const activePKCount = ref(0)
const myRank = ref(0)
const unclaimedAchievements = ref(0)
const hasCheckedIn = ref(false)
// 积分记录
const recentRecords = ref<Array<{
id: number
type: string
title: string
score: number
time: string
}>>([
{ id: 1, type: 'checkin', title: '每日签到奖励', score: 10, time: new Date().toISOString() },
{ id: 2, type: 'task', title: '完成任务获得积分', score: 50, time: new Date(Date.now() - 86400000).toISOString() },
{ id: 3, type: 'achievement', title: '解锁成就「初出茅庐」', score: 100, time: new Date(Date.now() - 172800000).toISOString() },
])
const loadData = async () => {
try {
// 加载签到状态
hasCheckedIn.value = await hasCheckedInToday()
// 加载签到统计
const stats = await getCheckInStats()
continuousCheckIn.value = stats.continuousDays || 0
// 加载活跃目标数
const goals = await getMyGoals('active')
activeGoalCount.value = goals.length
// 加载进行中的竞赛数
const contests = await getContestList({ status: 'active' })
activeContestCount.value = contests.records?.length || 0
// 加载进行中的PK数
const pks = await getTeamPKList({ status: 'active' })
activePKCount.value = pks.records?.length || 0
} catch (error) {
console.error('加载数据失败:', error)
}
}
const getRecordIcon = (type: string) => {
const iconMap: Record<string, string> = {
checkin: 'calendar',
task: 'document',
achievement: 'trophy',
contest: 'fire',
pk: 'bolt',
goal: 'flag'
}
return iconMap[type] || 'star'
}
const formatTime = (time: string) => {
return dayjs(time).format('MM-DD HH:mm')
}
const viewAllRecords = () => {
// TODO: 跳转到积分记录详情页
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
.incentive-center {
min-height: 100vh;
padding: 24px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.page-header {
margin-bottom: 24px;
}
.page-title {
font-size: 28px;
font-weight: 600;
color: #ECECEC;
display: flex;
align-items: center;
margin-bottom: 8px;
}
.page-desc {
color: #AFAFAF;
font-size: 14px;
}
.score-card {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 1px solid #3A3A3A;
border-radius: 16px;
padding: 32px;
margin-bottom: 32px;
display: flex;
justify-content: space-between;
align-items: center;
}
.score-main {
.score-label {
color: #AFAFAF;
font-size: 14px;
margin-bottom: 8px;
}
.score-value {
font-size: 48px;
font-weight: 700;
color: #FFD700;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
}
}
.score-stats {
display: flex;
gap: 40px;
}
.stat {
text-align: center;
.stat-value {
display: block;
font-size: 24px;
font-weight: 600;
color: #ECECEC;
}
.stat-label {
display: block;
font-size: 12px;
color: #6B6B6B;
margin-top: 4px;
}
}
.module-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 32px;
}
.module-card {
background: #2A2A2A;
border: 1px solid #3A3A3A;
border-radius: 12px;
padding: 20px;
display: flex;
align-items: center;
gap: 16px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
&:hover {
border-color: #31f08b;
transform: translateX(4px);
}
&.checkin:hover { border-color: #31f08b; }
&.achievement:hover { border-color: #FFD700; }
&.goals:hover { border-color: #3B82F6; }
&.contest:hover { border-color: #EF4444; }
&.team-pk:hover { border-color: #F59E0B; }
&.leaderboard:hover { border-color: #8B5CF6; }
}
.module-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
}
.module-info {
flex: 1;
}
.module-title {
font-size: 16px;
font-weight: 600;
color: #ECECEC;
margin-bottom: 4px;
}
.module-desc {
font-size: 13px;
color: #6B6B6B;
}
.module-status, .module-progress, .module-rank {
margin-right: 8px;
font-size: 13px;
color: #AFAFAF;
}
.module-badge {
position: absolute;
top: 12px;
right: 40px;
.badge-count {
background: #EF4444;
color: white;
font-size: 12px;
font-weight: 600;
padding: 2px 8px;
border-radius: 10px;
}
}
.module-arrow {
flex-shrink: 0;
}
.section {
margin-bottom: 32px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #ECECEC;
display: flex;
align-items: center;
}
.record-list {
background: #2A2A2A;
border: 1px solid #3A3A3A;
border-radius: 12px;
overflow: hidden;
}
.record-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #3A3A3A;
&:last-child {
border-bottom: none;
}
}
.record-info {
display: flex;
align-items: center;
gap: 12px;
}
.record-detail {
display: flex;
flex-direction: column;
}
.record-title {
color: #ECECEC;
font-size: 14px;
}
.record-time {
color: #6B6B6B;
font-size: 12px;
margin-top: 2px;
}
.record-score {
font-size: 16px;
font-weight: 600;
&.positive {
color: #31f08b;
}
&.negative {
color: #EF4444;
}
}
.empty-records {
padding: 32px;
text-align: center;
color: #6B6B6B;
}
@media (max-width: 768px) {
.module-grid {
grid-template-columns: 1fr;
}
.score-card {
flex-direction: column;
gap: 24px;
text-align: center;
}
}
</style>