Files
gamegroup2/frontend/src/views/Home.vue
T
congsh 83b7472594 refactor: replace all emoji icons with Element Plus SVG icons
Use @element-plus/icons-vue components for consistent, scalable vector
icons across sidebar navigation, section headers, ratings, and buttons.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 21:36:44 +08:00

445 lines
9.8 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.
<!-- src/views/Home.vue -->
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useGroupStore } from '@/stores/group'
import { useUserStore } from '@/stores/user'
import { getPopularGames } from '@/api/games'
import GameDetailDialog from '@/components/game/GameDetailDialog.vue'
import { UserStatusMap } from '@/types'
import type { Game } from '@/types'
import TeamSessionPanel from '@/components/team/TeamSessionPanel.vue'
import IdleMembersList from '@/components/team/IdleMembersList.vue'
import { User, Promotion, TrendCharts } from '@element-plus/icons-vue'
const router = useRouter()
const groupStore = useGroupStore()
const userStore = useUserStore()
const popularGames = ref<Game[]>([])
const loading = ref(false)
const selectedGame = ref<Game | null>(null)
const showGameDetail = ref(false)
const statusDotClass = computed(() => {
const s = userStore.userStatus
return `gg-status-dot gg-status-dot--${s}`
})
const statusText = computed(() => UserStatusMap[userStore.userStatus] || '未知')
onMounted(async () => {
await loadPopularGames()
})
async function loadPopularGames() {
try {
loading.value = true
popularGames.value = await getPopularGames(8)
} catch (error) {
console.error('加载热门游戏失败:', error)
} finally {
loading.value = false
}
}
function selectGroup(groupId: string) {
groupStore.setCurrentGroup(groupId)
router.push({ name: 'GroupView', params: { id: groupId } })
}
function openGameDetail(game: Game) {
selectedGame.value = game
showGameDetail.value = true
}
</script>
<template>
<div class="home-page">
<!-- 欢迎区 -->
<section class="welcome-section">
<h1 class="welcome-title">
欢迎回来, <span class="gg-text-gradient">{{ userStore.user?.username || '玩家' }}</span>!
</h1>
<div class="status-line">
<span :class="statusDotClass" />
<span class="status-text">{{ statusText }}</span>
<span v-if="userStore.user?.statusNote" class="status-note">{{ userStore.user.statusNote }}</span>
</div>
</section>
<!-- 主内容双栏 -->
<div class="content-grid">
<!-- 左列: 我的群组 -->
<section class="section groups-section">
<h2 class="section-title">
<el-icon class="section-icon"><User /></el-icon> 我的群组
</h2>
<div v-if="groupStore.groups.length === 0" class="empty-state">
<div class="empty-icon"><el-icon :size="40"><User /></el-icon></div>
<p class="empty-text">暂无群组</p>
<p class="empty-hint">创建或加入一个群组开始组队冒险吧</p>
</div>
<div v-else class="group-grid">
<div
v-for="group in groupStore.groups"
:key="group.id"
class="group-card"
@click="selectGroup(group.id)"
>
<div class="group-card-header">
<span class="group-name">{{ group.name }}</span>
<span class="member-badge">{{ group.members.length }} </span>
</div>
<p class="group-desc">{{ group.description || '暂无简介' }}</p>
</div>
</div>
</section>
<!-- 右列: 当前临时小组 -->
<section class="section session-section">
<h2 class="section-title">
<el-icon class="section-icon"><Promotion /></el-icon> 当前临时小组
</h2>
<div class="session-card">
<TeamSessionPanel />
</div>
<div class="idle-section">
<IdleMembersList status="idle" />
</div>
</section>
</div>
<!-- 热门游戏 -->
<section class="section games-section">
<h2 class="section-title">
<span class="section-icon"><el-icon><TrendCharts /></el-icon></span> 热门游戏
</h2>
<div v-if="loading" class="loading-state">
<div class="loading-spinner" />
<span>加载中...</span>
</div>
<div v-else-if="popularGames.length === 0" class="empty-state">
<div class="empty-icon"><el-icon :size="40"><TrendCharts /></el-icon></div>
<p class="empty-text">暂无热门游戏</p>
</div>
<div v-else class="games-scroll">
<div
v-for="game in popularGames"
:key="game.id"
class="game-card"
@click="openGameDetail(game)"
>
<div class="game-cover-wrap">
<img
:src="game.cover || '/game-placeholder.svg'"
:alt="game.name"
class="game-cover"
/>
</div>
<div class="game-info">
<span class="game-name">{{ game.name }}</span>
<span v-if="game.platform" class="game-platform-tag">{{ game.platform }}</span>
</div>
</div>
</div>
</section>
<GameDetailDialog v-model="showGameDetail" :game="selectedGame" @create-team="() => {}" />
</div>
</template>
<style scoped>
.home-page {
width: 100%;
display: flex;
flex-direction: column;
gap: 28px;
}
/* ── 欢迎区 ── */
.welcome-section {
padding: 28px 32px;
background: var(--gg-bg-card);
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-lg);
position: relative;
overflow: hidden;
}
.welcome-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--gg-gradient);
}
.welcome-title {
font-size: 28px;
font-weight: 700;
margin: 0 0 12px;
color: var(--gg-text);
}
.status-line {
display: flex;
align-items: center;
gap: 10px;
}
.status-text {
font-size: 14px;
color: var(--gg-text-secondary);
font-weight: 500;
}
.status-note {
font-size: 13px;
color: var(--gg-text-muted);
}
/* ── 通用 section ── */
.section {
display: flex;
flex-direction: column;
}
.section-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 16px;
display: flex;
align-items: center;
gap: 8px;
color: var(--gg-text);
}
.section-icon {
font-size: 20px;
}
/* ── 双栏内容 ── */
.content-grid {
display: grid;
grid-template-columns: 1fr 340px;
gap: 28px;
}
/* ── 群组区 ── */
.group-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
.group-card {
background: var(--gg-bg-card);
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-md);
padding: 18px 20px;
cursor: pointer;
transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s;
}
.group-card:hover {
border-color: var(--gg-primary);
box-shadow: 0 0 24px rgba(99, 102, 241, 0.18);
transform: translateY(-2px);
}
.group-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.group-name {
font-size: 15px;
font-weight: 600;
color: var(--gg-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.member-badge {
font-size: 12px;
padding: 3px 10px;
border-radius: 20px;
background: rgba(99, 102, 241, 0.15);
color: var(--gg-primary-light);
font-weight: 500;
flex-shrink: 0;
}
.group-desc {
font-size: 13px;
color: var(--gg-text-muted);
margin: 0;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ── 临时小组区 ── */
.session-section {
display: flex;
flex-direction: column;
gap: 16px;
}
.session-card,
.idle-section {
background: var(--gg-bg-card);
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-md);
overflow: hidden;
}
/* ── 热门游戏 ── */
.games-section {
margin-top: 0;
}
.games-scroll {
display: flex;
gap: 16px;
overflow-x: auto;
padding-bottom: 8px;
scroll-behavior: smooth;
}
.games-scroll::-webkit-scrollbar {
height: 4px;
}
.games-scroll::-webkit-scrollbar-thumb {
background: var(--gg-border);
border-radius: 2px;
}
.game-card {
flex-shrink: 0;
width: 160px;
cursor: pointer;
transition: transform 0.25s, box-shadow 0.25s;
}
.game-card:hover {
transform: translateY(-4px);
}
.game-card:hover .game-cover-wrap {
box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
}
.game-cover-wrap {
width: 160px;
height: 200px;
border-radius: var(--gg-radius-md);
overflow: hidden;
background: var(--gg-bg-elevated);
border: 1px solid var(--gg-border);
transition: box-shadow 0.25s;
}
.game-cover {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.game-info {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 10px;
}
.game-name {
font-size: 14px;
font-weight: 500;
color: var(--gg-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.game-platform-tag {
display: inline-block;
width: fit-content;
font-size: 11px;
padding: 2px 8px;
border-radius: 4px;
background: rgba(168, 85, 247, 0.15);
color: var(--gg-accent-light);
font-weight: 500;
}
/* ── 空状态 ── */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
background: var(--gg-bg-card);
border: 1px dashed var(--gg-border);
border-radius: var(--gg-radius-md);
}
.empty-icon {
font-size: 40px;
margin-bottom: 12px;
}
.empty-text {
font-size: 15px;
color: var(--gg-text-secondary);
margin: 0 0 4px;
font-weight: 500;
}
.empty-hint {
font-size: 13px;
color: var(--gg-text-muted);
margin: 0;
}
/* ── 加载状态 ── */
.loading-state {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 40px;
color: var(--gg-text-muted);
font-size: 14px;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 2px solid var(--gg-border);
border-top-color: var(--gg-primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>