2026-01-07 09:53:56 +08:00

327 lines
7.3 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">
<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
class="btn-secondary"
style="margin-top: 24rpx; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 55rpx; height: 110rpx; display: flex; align-items: center; justify-content: center;"
@tap="goRoomList"
>
<text style="color: #94a3b8; font-size: 32rpx; font-weight: 600;">📡 对战列表 / 围观</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: {
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()
}
},
async goRoomList() {
// 获取配置以拿到 nakama 参数,虽然 room-list 也会尝试连接,但通过 URL 传参更稳妥
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/room-list?game_token=${gameToken}&nakama_server=${nakamaServer}&nakama_key=${nakamaKey}`
})
} catch (e) {
uni.showToast({ title: '无法获取对战列表', icon: 'none' })
}
}
}
}
</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));
}
.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>