321 lines
6.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>
<view class="page">
<!-- 背景装饰 -->
<view class="bg-decoration"></view>
<!-- 头部 -->
<view class="header">
<view class="back-btn" @tap="goBack">
<text class="back-icon"></text>
</view>
<text class="title">动物扫雷大作战</text>
</view>
<!-- 主内容 -->
<view class="content">
<!-- 游戏图标 -->
<view class="game-icon-box fadeInUp">
<text class="game-icon">💣</text>
<view class="game-glow"></view>
</view>
<!-- 游戏介绍 -->
<view class="glass-card intro-card fadeInUp" style="animation-delay: 0.1s;">
<text class="intro-title">多人对战扫雷</text>
<text class="intro-desc">和好友一起挑战获胜赢取精美奖品</text>
</view>
<!-- 资格显示 -->
<view class="glass-card ticket-card fadeInUp" v-if="!loading" style="animation-delay: 0.2s;">
<view class="ticket-row">
<text class="ticket-label">我的游戏资格</text>
<view class="ticket-count-box">
<text class="ticket-count">{{ ticketCount }}</text>
<text class="ticket-unit"></text>
</view>
</view>
<view class="divider"></view>
<text class="ticket-tip">{{ ticketCount > 0 ? '每次进入消耗1次资格' : '完成任务可获得游戏资格哦~' }}</text>
</view>
<!-- 加载中 -->
<view v-else class="loading-box">
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="footer">
<view
class="btn-primary"
:class="{ disabled: ticketCount <= 0 || entering }"
@tap="enterGame"
>
<text class="enter-btn-text">{{ entering ? '正在进入...' : (ticketCount > 0 ? '立即开局' : '资格不足') }}</text>
</view>
</view>
</view>
</template>
<script>
import { authRequest } from '../../../utils/request.js'
export default {
data() {
return {
loading: true,
ticketCount: 0,
entering: false,
gameCode: 'minesweeper'
}
},
onShow() {
this.loadTickets()
},
methods: {
goBack() {
uni.navigateBack()
},
async loadTickets() {
this.loading = true
try {
const userInfo = uni.getStorageSync('user_info') || {}
const userId = userInfo.id || userInfo.user_id
if (!userId) {
this.ticketCount = 0
return
}
const res = await authRequest({
url: `/api/app/users/${userId}/game_tickets`
})
this.ticketCount = res[this.gameCode] || 0
} catch (e) {
console.error('加载游戏资格失败', e)
this.ticketCount = 0
} finally {
this.loading = false
}
},
async enterGame() {
if (this.ticketCount <= 0 || this.entering) return
this.entering = true
try {
const res = await authRequest({
url: '/api/app/games/enter',
method: 'POST',
data: {
game_code: this.gameCode
}
})
const gameToken = encodeURIComponent(res.game_token)
const nakamaServer = encodeURIComponent(res.nakama_server)
const nakamaKey = encodeURIComponent(res.nakama_key)
uni.navigateTo({
url: `/pages-game/game/minesweeper/play?game_token=${gameToken}&nakama_server=${nakamaServer}&nakama_key=${nakamaKey}`
})
} catch (e) {
uni.showToast({
title: e.message || '进入游戏失败',
icon: 'none'
})
} finally {
this.entering = false
this.loadTickets()
}
}
}
}
</script>
<style lang="scss">
@import '@/uni.scss';
.page {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: $bg-page;
position: relative;
overflow: hidden;
}
.header {
position: relative;
z-index: 10;
display: flex;
align-items: center;
padding: 24rpx 32rpx;
padding-top: calc(80rpx + env(safe-area-inset-top));
}
.back-btn {
width: 80rpx;
height: 80rpx;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-sm;
&:active {
transform: scale(0.95);
}
}
.back-icon {
font-size: 40rpx;
color: $text-main;
}
.title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 800;
color: $text-main;
margin-right: 80rpx;
}
.content {
flex: 1;
position: relative;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 40rpx;
}
.game-icon-box {
position: relative;
margin: 60rpx 0;
display: flex;
justify-content: center;
align-items: center;
}
.game-icon {
font-size: 180rpx;
animation: float 4s ease-in-out infinite;
z-index: 2;
}
.game-glow {
position: absolute;
width: 280rpx;
height: 280rpx;
background: radial-gradient(circle, rgba($brand-primary, 0.25) 0%, transparent 70%);
filter: blur(20rpx);
animation: pulse 2s ease-in-out infinite;
}
.intro-card {
width: 100%;
padding: 48rpx;
text-align: center;
margin-bottom: 32rpx;
}
.intro-title {
font-size: 44rpx;
font-weight: 900;
color: $brand-primary;
display: block;
margin-bottom: 16rpx;
}
.intro-desc {
font-size: 28rpx;
color: $text-sub;
line-height: 1.5;
}
.ticket-card {
width: 100%;
padding: 40rpx;
}
.ticket-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.ticket-label {
font-size: 32rpx;
color: $text-main;
font-weight: 700;
}
.ticket-count-box {
display: flex;
align-items: baseline;
}
.ticket-count {
font-size: 64rpx;
font-weight: 900;
color: $brand-primary;
margin-right: 8rpx;
}
.ticket-unit {
font-size: 24rpx;
color: $text-sub;
}
.divider {
height: 1px;
background: rgba(0,0,0,0.05);
margin: 32rpx 0;
}
.ticket-tip {
font-size: 24rpx;
color: $text-sub;
display: block;
text-align: center;
}
.loading-box {
padding: 100rpx;
}
.loading-text {
font-size: 28rpx;
color: $text-sub;
}
.footer {
position: relative;
z-index: 10;
padding: 40rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
}
.btn-primary {
height: 110rpx;
width: 100%;
&.disabled {
background: $text-disabled;
box-shadow: none;
pointer-events: none;
}
}
.enter-btn-text {
font-size: 36rpx;
letter-spacing: 2rpx;
}
/* Animations from App.vue are global, but we use local ones if needed */
.fadeInUp {
animation: fadeInUp 0.6s ease-out both;
}
</style>