feat: add Minesweeper game page and link from the index, including page registration.

This commit is contained in:
邹方成 2025-12-24 13:51:51 +08:00
parent f57ecfbaee
commit bfb7d7630f
3 changed files with 359 additions and 1 deletions

View File

@ -114,6 +114,13 @@
"navigationBarTitleText": "爬塔"
}
},
{
"path": "pages/game/minesweeper/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "扫雷游戏"
}
},
{
"path": "pages/game/webview",
"style": {

View File

@ -0,0 +1,351 @@
<template>
<view class="page">
<!-- 背景 -->
<view class="bg-gradient"></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">
<view class="game-icon">💣</view>
<view class="game-glow"></view>
</view>
<!-- 游戏介绍 -->
<view class="intro-card">
<text class="intro-title">多人对战扫雷</text>
<text class="intro-desc">和好友一起挑战获胜赢取精美奖品</text>
</view>
<!-- 资格显示 -->
<view class="ticket-card" v-if="!loading">
<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>
<text class="ticket-tip" v-if="ticketCount === 0">完成任务可获得游戏资格哦~</text>
</view>
<!-- 加载中 -->
<view v-else class="loading-box">
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="footer">
<view
class="enter-btn"
:class="{ disabled: ticketCount <= 0 || entering }"
@tap="enterGame"
>
<text class="enter-btn-text">{{ entering ? '进入中...' : '进入游戏' }}</text>
</view>
<text class="footer-tip">每次进入消耗1次资格</text>
</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
console.log('===== 调试游戏资格 =====')
console.log('userId:', userId)
if (!userId) {
console.log('未获取到用户ID返回0')
this.ticketCount = 0
return
}
const res = await authRequest({
url: `/api/app/users/${userId}/game_tickets`
})
console.log('API返回:', res)
// res : { minesweeper: 3, poker: 0, ... }
this.ticketCount = res[this.gameCode] || 0
console.log('gameCode:', this.gameCode, 'ticketCount:', this.ticketCount)
} 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
}
})
// URL
const gameBaseUrl = 'https://game.1024tool.vip'
const gameUrl = `${gameBaseUrl}?ticket=${res.ticket_token}`
// webview
uni.navigateTo({
url: `/pages/game/webview?url=${encodeURIComponent(gameUrl)}&ticket=${res.ticket_token}`
})
} catch (e) {
uni.showToast({
title: e.message || '进入游戏失败',
icon: 'none'
})
} finally {
this.entering = false
//
this.loadTickets()
}
}
}
}
</script>
<style lang="scss">
.page {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.bg-gradient {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, #E0C3FC 0%, #8EC5FC 50%, #E0E7FF 100%);
z-index: 0;
}
.header {
position: relative;
z-index: 1;
display: flex;
align-items: center;
padding: 24rpx 32rpx;
padding-top: calc(100rpx + env(safe-area-inset-top));
}
.back-btn {
width: 72rpx;
height: 72rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.back-icon {
font-size: 36rpx;
color: #333;
}
.title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 800;
color: #333;
margin-right: 72rpx; //
}
.content {
flex: 1;
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 48rpx;
}
.game-icon-box {
position: relative;
margin-bottom: 48rpx;
}
.game-icon {
font-size: 160rpx;
animation: bounce 2s ease-in-out infinite;
}
.game-glow {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200rpx;
height: 200rpx;
background: radial-gradient(circle, rgba(255,255,255,0.6) 0%, transparent 70%);
animation: pulse 2s ease-in-out infinite;
}
.intro-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 32rpx;
padding: 48rpx;
width: 100%;
text-align: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
margin-bottom: 32rpx;
}
.intro-title {
font-size: 40rpx;
font-weight: 800;
color: #333;
display: block;
margin-bottom: 16rpx;
}
.intro-desc {
font-size: 28rpx;
color: #666;
}
.ticket-card {
background: #fff;
border-radius: 24rpx;
padding: 32rpx 40rpx;
width: 100%;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
.ticket-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.ticket-label {
font-size: 30rpx;
color: #333;
font-weight: 600;
}
.ticket-count-box {
display: flex;
align-items: baseline;
}
.ticket-count {
font-size: 56rpx;
font-weight: 900;
color: #7C3AED;
}
.ticket-unit {
font-size: 28rpx;
color: #666;
margin-left: 8rpx;
}
.ticket-tip {
font-size: 24rpx;
color: #999;
margin-top: 16rpx;
display: block;
}
.loading-box {
padding: 60rpx;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
.footer {
position: relative;
z-index: 1;
padding: 32rpx 48rpx;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}
.enter-btn {
background: linear-gradient(135deg, #7C3AED 0%, #9F7AEA 100%);
border-radius: 48rpx;
padding: 32rpx;
text-align: center;
box-shadow: 0 8rpx 24rpx rgba(124, 58, 237, 0.4);
transition: all 0.2s;
}
.enter-btn:active {
transform: scale(0.98);
box-shadow: 0 4rpx 12rpx rgba(124, 58, 237, 0.3);
}
.enter-btn.disabled {
background: #CCC;
box-shadow: none;
}
.enter-btn-text {
font-size: 34rpx;
font-weight: 800;
color: #fff;
}
.footer-tip {
display: block;
text-align: center;
font-size: 24rpx;
color: #999;
margin-top: 16rpx;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20rpx); }
}
@keyframes pulse {
0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
}
</style>

View File

@ -77,7 +77,7 @@
<image class="card-icon-small" src="https://via.placeholder.com/80/FFB6C1/000000?text=Match" mode="aspectFit" />
</view>
<view class="game-card-small card-tower" @tap="navigateTo('/pages/activity/pata/index')">
<view class="game-card-small card-tower" @tap="navigateTo('/pages/game/minesweeper/index')">
<text class="card-title-small">扫雷</text>
<text class="card-subtitle-small">福利挑战</text>
<image class="card-icon-small" src="https://via.placeholder.com/80/9370DB/000000?text=Mine" mode="aspectFit" />