- 集成WebSocket管理器,支持心跳、自动重连、消息队列 - 实现私信相关API接口,包括会话列表、历史消息、已读标记等 - 添加聊天页面和会话列表页面,支持实时消息收发 - 配置地图权限和腾讯地图SDK用于酒局定位功能 - 修改应用名称为"喝酒了么"并更新应用描述 - 在App.vue中初始化WebSocket连接和私信功能 - 添加详细的私信模块接口文档和Mock数据 - 优化单图片动态高度计算和预加载逻辑
1337 lines
38 KiB
Vue
1337 lines
38 KiB
Vue
<template>
|
||
<view :class="themeClass" class="page-container games-page">
|
||
<!-- 顶部导航 -->
|
||
<view class="games-nav" :style="{ paddingTop: navPaddingTop }">
|
||
<view class="nav-back" @click="goBack">
|
||
<text class="nav-back-icon">‹</text>
|
||
</view>
|
||
<text class="nav-title">{{ currentGame === 'hub' ? '酒桌小游戏' : gameMeta[currentGame].name }}</text>
|
||
<view class="nav-placeholder"></view>
|
||
</view>
|
||
|
||
<!-- ===== 游戏大厅 ===== -->
|
||
<view v-if="currentGame === 'hub'" class="game-hub">
|
||
<view class="hub-header">
|
||
<text class="hub-deco">🎲</text>
|
||
<view class="hub-title-wrap">
|
||
<text class="hub-title">今晚喝不喝?</text>
|
||
<text class="hub-sub">游戏说了算 · 愿赌服输</text>
|
||
</view>
|
||
</view>
|
||
<view class="hub-grid">
|
||
<view
|
||
class="hub-card"
|
||
v-for="(g, i) in gameList"
|
||
:key="g.id"
|
||
:class="'hub-card-' + g.id"
|
||
:style="'animation-delay:' + (i * 0.08) + 's'"
|
||
@click="enterGame(g.id)"
|
||
>
|
||
<view class="hub-card-glow"></view>
|
||
<text class="hub-card-emoji">{{ g.emoji }}</text>
|
||
<text class="hub-card-name">{{ g.name }}</text>
|
||
<text class="hub-card-desc">{{ g.desc }}</text>
|
||
<view class="hub-card-players">
|
||
<text>{{ g.players }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="hub-tip">
|
||
<text class="hub-tip-icon">🍺</text>
|
||
<text class="hub-tip-text">小酌怡情,微醺即止,酒后不开车</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ===== 数字炸弹 ===== -->
|
||
<view v-if="currentGame === 'bomb'" class="game-stage">
|
||
<view class="bomb-arena">
|
||
<view class="bomb-range-card" :class="{ 'bomb-boom': bombBoom }">
|
||
<text class="bomb-emoji">{{ bombBoom ? '💥' : '💣' }}</text>
|
||
<view class="bomb-range-row">
|
||
<text class="bomb-num text-num">{{ bombMin }}</text>
|
||
<view class="bomb-fuse">
|
||
<view class="bomb-fuse-line" :style="{ width: bombFuseWidth }"></view>
|
||
</view>
|
||
<text class="bomb-num text-num">{{ bombMax }}</text>
|
||
</view>
|
||
<text class="bomb-hint">{{ bombBoom ? 'boom!踩中炸弹了!' : '在范围内猜一个数字,踩中就喝' }}</text>
|
||
</view>
|
||
|
||
<view v-if="bombBoom" class="result-banner result-drink">
|
||
<text class="result-emoji">🍻</text>
|
||
<text class="result-text">喝酒!一杯闷!</text>
|
||
</view>
|
||
<view v-else-if="bombSafe" class="result-banner result-safe">
|
||
<text class="result-emoji">😎</text>
|
||
<text class="result-text">安全过关,下一位!</text>
|
||
</view>
|
||
|
||
<view class="bomb-history" v-if="bombGuesses.length">
|
||
<view class="bomb-guess-tag" v-for="(g, i) in bombGuesses" :key="i" :class="g.dir === 'high' ? 'tag-high' : 'tag-low'">
|
||
<text>{{ g.val }} {{ g.dir === 'high' ? '↓' : '↑' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="bomb-input-area" v-if="!bombBoom && !bombSafe">
|
||
<input
|
||
class="bomb-input text-num"
|
||
type="number"
|
||
v-model="bombInput"
|
||
:placeholder="bombMin + ' ~ ' + bombMax"
|
||
confirm-type="done"
|
||
@confirm="bombGuess"
|
||
/>
|
||
<view class="btn-amber bomb-go-btn" @click="bombGuess">
|
||
<text>拆弹</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="game-actions" v-if="bombBoom || bombSafe">
|
||
<view class="btn-amber game-restart-btn" @click="bombReset">
|
||
<text>再来一轮</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="game-rule-card">
|
||
<text class="rule-title">📖 玩法</text>
|
||
<text class="rule-text">系统随机埋一个 1~100 的数字炸弹,轮流猜数,范围不断缩小,踩中炸弹的人喝酒。范围越小心跳越快…</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ===== 猜拳大战 ===== -->
|
||
<view v-if="currentGame === 'rps'" class="game-stage">
|
||
<view class="rps-arena">
|
||
<view class="rps-score-row">
|
||
<view class="rps-score-item">
|
||
<text class="rps-score-label">你</text>
|
||
<text class="rps-score-num text-num">{{ rpsWin }}</text>
|
||
</view>
|
||
<text class="rps-vs">VS</text>
|
||
<view class="rps-score-item">
|
||
<text class="rps-score-label">酒神AI</text>
|
||
<text class="rps-score-num text-num">{{ rpsLose }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="rps-battle">
|
||
<view class="rps-hand rps-hand-left" :class="{ 'hand-shake': rpsShaking, 'hand-reveal': !rpsShaking && rpsRound > 0 }">
|
||
<text class="rps-hand-emoji">{{ rpsShaking ? '✊' : rpsPlayerHand }}</text>
|
||
</view>
|
||
<view class="rps-clash" v-if="rpsResult">
|
||
<text class="rps-clash-text">{{ rpsResult === 'win' ? '⚡' : rpsResult === 'lose' ? '💫' : '🤝' }}</text>
|
||
</view>
|
||
<view class="rps-hand rps-hand-right" :class="{ 'hand-shake': rpsShaking, 'hand-reveal': !rpsShaking && rpsRound > 0 }">
|
||
<text class="rps-hand-emoji">{{ rpsShaking ? '✊' : rpsAiHand }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="rpsResult === 'lose'" class="result-banner result-drink">
|
||
<text class="result-emoji">🍺</text>
|
||
<text class="result-text">输了!喝酒!</text>
|
||
</view>
|
||
<view v-else-if="rpsResult === 'win'" class="result-banner result-safe">
|
||
<text class="result-emoji">🎉</text>
|
||
<text class="result-text">赢了!对方喝!</text>
|
||
</view>
|
||
<view v-else-if="rpsResult === 'draw'" class="result-banner result-draw">
|
||
<text class="result-emoji">🔄</text>
|
||
<text class="result-text">平局,再来!</text>
|
||
</view>
|
||
|
||
<view class="rps-picker" v-if="!rpsShaking">
|
||
<view
|
||
class="rps-choice"
|
||
v-for="c in rpsChoices"
|
||
:key="c.key"
|
||
:class="{ 'choice-active': rpsLastPick === c.key }"
|
||
@click="rpsPlay(c.key)"
|
||
>
|
||
<text class="rps-choice-emoji">{{ c.emoji }}</text>
|
||
<text class="rps-choice-name">{{ c.name }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="rps-countdown" v-else>
|
||
<text class="rps-countdown-text">{{ rpsCountdown }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="game-rule-card">
|
||
<text class="rule-title">📖 玩法</text>
|
||
<text class="rule-text">和酒神AI猜拳,三局定胜负?不存在的——输一局喝一杯,赢一局对方喝,平局一起抿一口。</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ===== 骰子大话 ===== -->
|
||
<view v-if="currentGame === 'dice'" class="game-stage">
|
||
<view class="dice-arena">
|
||
<view class="dice-cup" :class="{ 'cup-shaking': diceShaking }" @click="diceRoll">
|
||
<view class="dice-pair">
|
||
<view class="die" :class="{ 'die-rolling': diceShaking }">
|
||
<view class="die-grid">
|
||
<view class="die-cell" v-for="p in 9" :key="p">
|
||
<view class="die-pip" v-if="dicePips(diceA).indexOf(p) > -1"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="die die-2" :class="{ 'die-rolling': diceShaking }">
|
||
<view class="die-grid">
|
||
<view class="die-cell" v-for="p in 9" :key="p">
|
||
<view class="die-pip" v-if="dicePips(diceB).indexOf(p) > -1"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<text class="dice-sum text-num" v-if="!diceShaking && diceRound > 0">{{ diceA + diceB }}</text>
|
||
</view>
|
||
|
||
<view v-if="diceRule && !diceShaking" class="dice-rule-result" :class="diceRule.type === 'drink' ? 'result-drink' : diceRule.type === 'safe' ? 'result-safe' : 'result-fun'">
|
||
<text class="dice-rule-emoji">{{ diceRule.emoji }}</text>
|
||
<view class="dice-rule-text-wrap">
|
||
<text class="dice-rule-name">{{ diceRule.name }}</text>
|
||
<text class="dice-rule-desc">{{ diceRule.desc }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="dice-history" v-if="diceHistory.length">
|
||
<view class="dice-history-item" v-for="(h, i) in diceHistory" :key="i">
|
||
<text class="dice-history-val text-num">{{ h.a }}+{{ h.b }}</text>
|
||
<text class="dice-history-rule">{{ h.rule }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="btn-amber dice-roll-btn" @click="diceRoll" :class="{ 'btn-disabled': diceShaking }">
|
||
<text>{{ diceShaking ? '摇骰中…' : '🎲 摇骰子' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="game-rule-card">
|
||
<text class="rule-title">📖 玩法</text>
|
||
<text class="rule-text">轮流摇两颗骰子:豹子全桌敬你一杯;7点安全过关;双数你喝一杯;单数指定一人喝;点数≤4 干杯!</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ===== 命运转盘(老虎机式) ===== -->
|
||
<view v-if="currentGame === 'wheel'" class="game-stage">
|
||
<view class="wheel-arena">
|
||
<view class="slot-machine">
|
||
<view class="slot-glow" :class="{ 'slot-glow-on': wheelSpinning }"></view>
|
||
<view class="slot-window">
|
||
<view class="slot-strip" :style="{ transform: 'translateY(' + slotOffset + 'rpx)', transition: slotTransition }">
|
||
<view
|
||
class="slot-item"
|
||
v-for="(s, i) in slotStrip"
|
||
:key="i"
|
||
:class="{ 'slot-item-active': !wheelSpinning && wheelResult && i === slotTarget }"
|
||
>
|
||
<view class="slot-item-emoji-box">
|
||
<text class="slot-item-emoji">{{ s.emoji }}</text>
|
||
</view>
|
||
<text class="slot-item-label">{{ s.label }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="slot-mask-top"></view>
|
||
<view class="slot-mask-bottom"></view>
|
||
<view class="slot-indicator">
|
||
<text class="slot-ind-arrow">▶</text>
|
||
<text class="slot-ind-arrow slot-ind-arrow-r">◀</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="wheelResult && !wheelSpinning" class="result-banner" :class="wheelResult.type === 'drink' ? 'result-drink' : wheelResult.type === 'safe' ? 'result-safe' : 'result-fun'">
|
||
<text class="result-emoji">{{ wheelResult.emoji }}</text>
|
||
<text class="result-text">{{ wheelResult.label }}!愿赌服输!</text>
|
||
</view>
|
||
|
||
<view class="btn-amber slot-spin-btn" :class="{ 'btn-disabled': wheelSpinning }" @click="wheelSpin">
|
||
<text>{{ wheelSpinning ? '命运转动中…' : '🎰 转一转' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="game-rule-card">
|
||
<text class="rule-title">📖 玩法</text>
|
||
<text class="rule-text">点击按钮转动命运滚轮,停下来时中间高亮的那格就是你的命运,不许耍赖!</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 返回大厅浮动按钮 -->
|
||
<view v-if="currentGame !== 'hub'" class="back-hub-btn" @click="currentGame = 'hub'">
|
||
<text>🏠 返回大厅</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import themeMixin from '../../common/theme-mixin'
|
||
|
||
export default {
|
||
mixins: [themeMixin],
|
||
data() {
|
||
let navPaddingTop = '20px'
|
||
try {
|
||
const menuBtn = uni.getMenuButtonBoundingClientRect()
|
||
navPaddingTop = menuBtn.top + 'px'
|
||
} catch (e) {}
|
||
return {
|
||
navPaddingTop,
|
||
currentGame: 'hub',
|
||
gameList: [
|
||
{ id: 'bomb', emoji: '💣', name: '数字炸弹', desc: '猜中即喝,心跳加速', players: '2人+' },
|
||
{ id: 'rps', emoji: '✊', name: '猜拳大战', desc: '挑战酒神AI,输了干杯', players: '1人+' },
|
||
{ id: 'dice', emoji: '🎲', name: '骰子大话', desc: '豹子通杀,七点保命', players: '2人+' },
|
||
{ id: 'wheel', emoji: '🎡', name: '命运转盘', desc: '转到什么喝什么', players: '3人+' }
|
||
],
|
||
gameMeta: {
|
||
bomb: { name: '💣 数字炸弹' },
|
||
rps: { name: '✊ 猜拳大战' },
|
||
dice: { name: '🎲 骰子大话' },
|
||
wheel: { name: '🎡 命运转盘' }
|
||
},
|
||
// --- 数字炸弹 ---
|
||
bombTarget: 0,
|
||
bombMin: 1,
|
||
bombMax: 100,
|
||
bombInput: '',
|
||
bombGuesses: [],
|
||
bombBoom: false,
|
||
bombSafe: false,
|
||
// --- 猜拳 ---
|
||
rpsChoices: [
|
||
{ key: 'rock', emoji: '✊', name: '石头' },
|
||
{ key: 'scissors', emoji: '✌️', name: '剪刀' },
|
||
{ key: 'paper', emoji: '🖐', name: '布' }
|
||
],
|
||
rpsPlayerHand: '❔',
|
||
rpsAiHand: '❔',
|
||
rpsShaking: false,
|
||
rpsCountdown: 3,
|
||
rpsResult: '',
|
||
rpsWin: 0,
|
||
rpsLose: 0,
|
||
rpsRound: 0,
|
||
rpsLastPick: '',
|
||
rpsTimer: null,
|
||
// --- 骰子 ---
|
||
diceA: 1,
|
||
diceB: 1,
|
||
diceShaking: false,
|
||
diceRound: 0,
|
||
diceRule: null,
|
||
diceHistory: [],
|
||
// --- 转盘(老虎机式) ---
|
||
wheelSegments: [
|
||
{ label: '喝一杯', emoji: '🍺', type: 'drink' },
|
||
{ label: '真心话', emoji: '💬', type: 'fun' },
|
||
{ label: '免一杯', emoji: '🛡️', type: 'safe' },
|
||
{ label: '大冒险', emoji: '🔥', type: 'fun' },
|
||
{ label: '干一杯', emoji: '🥂', type: 'drink' },
|
||
{ label: '指定喝', emoji: '👉', type: 'drink' },
|
||
{ label: '表演节目', emoji: '🎤', type: 'fun' },
|
||
{ label: '双倍快乐', emoji: '🍻', type: 'drink' }
|
||
],
|
||
slotOffset: -120,
|
||
slotTransition: 'none',
|
||
slotTarget: -1,
|
||
wheelSpinning: false,
|
||
wheelResult: null
|
||
}
|
||
},
|
||
computed: {
|
||
bombFuseWidth() {
|
||
const range = this.bombMax - this.bombMin
|
||
const pct = Math.max(8, Math.min(100, range))
|
||
return pct + '%'
|
||
},
|
||
// 老虎机滚动条:重复4轮方便滚动动画
|
||
slotStrip() {
|
||
const arr = []
|
||
for (let r = 0; r < 4; r++) {
|
||
this.wheelSegments.forEach(s => arr.push(s))
|
||
}
|
||
return arr
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.bombReset()
|
||
},
|
||
onUnload() {
|
||
if (this.rpsTimer) clearInterval(this.rpsTimer)
|
||
},
|
||
onShareAppMessage() {
|
||
return {
|
||
title: '酒桌小游戏 - 今晚谁喝?来 battle!',
|
||
path: '/pages/games/games'
|
||
}
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
if (this.currentGame !== 'hub') {
|
||
this.currentGame = 'hub'
|
||
} else {
|
||
uni.navigateBack({ delta: 1 })
|
||
}
|
||
},
|
||
enterGame(id) {
|
||
this.currentGame = id
|
||
if (id === 'bomb') this.bombReset()
|
||
if (id === 'rps') this.rpsResetRound()
|
||
if (id === 'dice') { this.diceRule = null; this.diceHistory = []; this.diceRound = 0 }
|
||
if (id === 'wheel') { this.wheelResult = null }
|
||
},
|
||
|
||
/* ========== 数字炸弹 ========== */
|
||
bombReset() {
|
||
this.bombTarget = Math.floor(Math.random() * 100) + 1
|
||
this.bombMin = 1
|
||
this.bombMax = 100
|
||
this.bombInput = ''
|
||
this.bombGuesses = []
|
||
this.bombBoom = false
|
||
this.bombSafe = false
|
||
},
|
||
bombGuess() {
|
||
const val = parseInt(this.bombInput)
|
||
if (isNaN(val) || val < this.bombMin || val > this.bombMax) {
|
||
uni.showToast({ title: `请输入 ${this.bombMin}~${this.bombMax} 的数字`, icon: 'none' })
|
||
return
|
||
}
|
||
if (val === this.bombTarget) {
|
||
this.bombBoom = true
|
||
uni.vibrateShort({ type: 'heavy' })
|
||
return
|
||
}
|
||
this.bombGuesses.push({ val, dir: val > this.bombTarget ? 'high' : 'low' })
|
||
if (val > this.bombTarget) {
|
||
this.bombMax = val - 1
|
||
} else {
|
||
this.bombMin = val + 1
|
||
}
|
||
this.bombInput = ''
|
||
uni.vibrateShort({ type: 'light' })
|
||
// 范围缩到1时安全过关
|
||
if (this.bombMin === this.bombMax && this.bombMin !== this.bombTarget) {
|
||
this.bombSafe = true
|
||
}
|
||
},
|
||
|
||
/* ========== 猜拳大战 ========== */
|
||
rpsResetRound() {
|
||
this.rpsPlayerHand = '❔'
|
||
this.rpsAiHand = '❔'
|
||
this.rpsResult = ''
|
||
this.rpsShaking = false
|
||
},
|
||
rpsPlay(key) {
|
||
if (this.rpsShaking) return
|
||
this.rpsLastPick = key
|
||
this.rpsResult = ''
|
||
this.rpsShaking = true
|
||
this.rpsCountdown = 3
|
||
this.rpsRound++
|
||
uni.vibrateShort({ type: 'light' })
|
||
|
||
this.rpsTimer = setInterval(() => {
|
||
this.rpsCountdown--
|
||
if (this.rpsCountdown <= 0) {
|
||
clearInterval(this.rpsTimer)
|
||
this.rpsTimer = null
|
||
this.rpsReveal(key)
|
||
}
|
||
}, 500)
|
||
},
|
||
rpsReveal(playerKey) {
|
||
const keys = ['rock', 'scissors', 'paper']
|
||
const emojis = { rock: '✊', scissors: '✌️', paper: '🖐' }
|
||
const aiKey = keys[Math.floor(Math.random() * 3)]
|
||
this.rpsPlayerHand = emojis[playerKey]
|
||
this.rpsAiHand = emojis[aiKey]
|
||
this.rpsShaking = false
|
||
|
||
if (playerKey === aiKey) {
|
||
this.rpsResult = 'draw'
|
||
} else if (
|
||
(playerKey === 'rock' && aiKey === 'scissors') ||
|
||
(playerKey === 'scissors' && aiKey === 'paper') ||
|
||
(playerKey === 'paper' && aiKey === 'rock')
|
||
) {
|
||
this.rpsResult = 'win'
|
||
this.rpsWin++
|
||
} else {
|
||
this.rpsResult = 'lose'
|
||
this.rpsLose++
|
||
uni.vibrateShort({ type: 'heavy' })
|
||
}
|
||
},
|
||
|
||
/* ========== 骰子大话 ========== */
|
||
// 骰子点数对应的 pip 位置(3x3 九宫格,1~9 从左到右、从上到下)
|
||
dicePips(val) {
|
||
const map = {
|
||
1: [5],
|
||
2: [1, 9],
|
||
3: [1, 5, 9],
|
||
4: [1, 3, 7, 9],
|
||
5: [1, 3, 5, 7, 9],
|
||
6: [1, 3, 4, 6, 7, 9]
|
||
}
|
||
return map[val] || []
|
||
},
|
||
diceRoll() {
|
||
if (this.diceShaking) return
|
||
this.diceShaking = true
|
||
this.diceRule = null
|
||
this.diceRound++
|
||
uni.vibrateShort({ type: 'medium' })
|
||
|
||
// 摇动期间快速随机跳面
|
||
const flicker = setInterval(() => {
|
||
this.diceA = Math.floor(Math.random() * 6) + 1
|
||
this.diceB = Math.floor(Math.random() * 6) + 1
|
||
}, 80)
|
||
|
||
setTimeout(() => {
|
||
clearInterval(flicker)
|
||
this.diceA = Math.floor(Math.random() * 6) + 1
|
||
this.diceB = Math.floor(Math.random() * 6) + 1
|
||
this.diceShaking = false
|
||
this.diceApplyRule()
|
||
uni.vibrateShort({ type: 'heavy' })
|
||
}, 1200)
|
||
},
|
||
diceApplyRule() {
|
||
const sum = this.diceA + this.diceB
|
||
let rule
|
||
if (this.diceA === this.diceB) {
|
||
rule = { emoji: '👑', name: '豹子!', desc: '天选之人!全桌敬你一杯', type: 'safe' }
|
||
} else if (sum === 7) {
|
||
rule = { emoji: '🛡️', name: '七点保命', desc: '幸运7!安全过关', type: 'safe' }
|
||
} else if (sum <= 4) {
|
||
rule = { emoji: '💀', name: '天崩开局', desc: '点数太小,直接干杯!', type: 'drink' }
|
||
} else if (sum % 2 === 0) {
|
||
rule = { emoji: '🍺', name: '双数喝一杯', desc: '愿赌服输,一杯下肚', type: 'drink' }
|
||
} else {
|
||
rule = { emoji: '👉', name: '单数点名', desc: '指定一人喝一杯,就你了!', type: 'fun' }
|
||
}
|
||
this.diceRule = rule
|
||
this.diceHistory.unshift({ a: this.diceA, b: this.diceB, rule: rule.name })
|
||
if (this.diceHistory.length > 5) this.diceHistory.pop()
|
||
},
|
||
|
||
/* ========== 命运转盘(老虎机式) ========== */
|
||
wheelSpin() {
|
||
if (this.wheelSpinning) return
|
||
this.wheelSpinning = true
|
||
this.wheelResult = null
|
||
this.slotTarget = -1
|
||
uni.vibrateShort({ type: 'medium' })
|
||
|
||
const count = this.wheelSegments.length
|
||
const segH = 120 // 每格高度 rpx
|
||
const targetIndex = Math.floor(Math.random() * count)
|
||
// 目标位置在第3轮(索引 2*count + targetIndex)
|
||
const target = count * 2 + targetIndex
|
||
this.slotTarget = target
|
||
|
||
// 先瞬间回到起始位
|
||
this.slotTransition = 'none'
|
||
this.slotOffset = -segH // 显示第2个在中间
|
||
|
||
setTimeout(() => {
|
||
// 滚动到目标,中间项对齐窗口中心
|
||
this.slotTransition = 'transform 3s cubic-bezier(0.12, 0.75, 0.18, 1)'
|
||
this.slotOffset = -(target * segH) + segH // 让 target 居中(窗口显示3格,中间格偏移=1格)
|
||
}, 50)
|
||
|
||
setTimeout(() => {
|
||
this.wheelSpinning = false
|
||
this.wheelResult = this.wheelSegments[targetIndex]
|
||
uni.vibrateShort({ type: 'heavy' })
|
||
}, 3200)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.games-page {
|
||
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
|
||
}
|
||
|
||
/* === 顶部导航 === */
|
||
.games-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 $sp-lg;
|
||
height: 88rpx;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 50;
|
||
background: $bg-base;
|
||
}
|
||
|
||
.nav-back {
|
||
width: 64rpx; height: 64rpx;
|
||
display: flex; align-items: center; justify-content: center;
|
||
border-radius: 50%;
|
||
background: $bg-elevated;
|
||
transition: transform $duration-fast $ease-out;
|
||
&:active { transform: scale(0.88); }
|
||
}
|
||
|
||
.nav-back-icon {
|
||
font-size: 44rpx;
|
||
color: $text-primary;
|
||
font-weight: $fw-bold;
|
||
margin-top: -6rpx;
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: $fs-lg;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
}
|
||
|
||
.nav-placeholder { width: 64rpx; }
|
||
|
||
/* === 游戏大厅 === */
|
||
.game-hub {
|
||
padding: $sp-xl $sp-lg 0;
|
||
}
|
||
|
||
.hub-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-lg;
|
||
margin-bottom: $sp-xl;
|
||
}
|
||
|
||
.hub-deco {
|
||
font-size: 88rpx;
|
||
animation: hub-float 3s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes hub-float {
|
||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||
50% { transform: translateY(-12rpx) rotate(8deg); }
|
||
}
|
||
|
||
.hub-title {
|
||
display: block;
|
||
font-size: $fs-2xl;
|
||
font-weight: $fw-black;
|
||
color: $text-primary;
|
||
letter-spacing: -1rpx;
|
||
}
|
||
|
||
.hub-sub {
|
||
display: block;
|
||
font-size: $fs-sm;
|
||
color: $text-tertiary;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.hub-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: $sp-md;
|
||
}
|
||
|
||
.hub-card {
|
||
position: relative;
|
||
padding: $sp-xl $sp-lg;
|
||
border-radius: $radius-xl;
|
||
background: $bg-card;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
overflow: hidden;
|
||
animation: card-enter 0.5s $ease-out both;
|
||
transition: transform $duration-fast $ease-out, box-shadow $duration-fast $ease-out;
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
box-shadow: $shadow-amber;
|
||
}
|
||
}
|
||
|
||
@keyframes card-enter {
|
||
from { opacity: 0; transform: translateY(40rpx) scale(0.92); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
|
||
.hub-card-glow {
|
||
position: absolute;
|
||
width: 180rpx; height: 180rpx;
|
||
top: -60rpx; right: -60rpx;
|
||
border-radius: 50%;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.hub-card-bomb .hub-card-glow { background: radial-gradient(circle, rgba(255,107,107,0.14) 0%, transparent 70%); }
|
||
.hub-card-rps .hub-card-glow { background: radial-gradient(circle, rgba(232,168,56,0.14) 0%, transparent 70%); }
|
||
.hub-card-dice .hub-card-glow { background: radial-gradient(circle, rgba(94,198,160,0.14) 0%, transparent 70%); }
|
||
.hub-card-wheel .hub-card-glow { background: radial-gradient(circle, rgba(139,133,184,0.16) 0%, transparent 70%); }
|
||
|
||
.hub-card-emoji {
|
||
display: block;
|
||
font-size: 72rpx;
|
||
margin-bottom: $sp-md;
|
||
}
|
||
|
||
.hub-card-name {
|
||
display: block;
|
||
font-size: $fs-lg;
|
||
font-weight: $fw-bold;
|
||
color: $text-primary;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.hub-card-desc {
|
||
display: block;
|
||
font-size: $fs-xs;
|
||
color: $text-tertiary;
|
||
line-height: $lh-normal;
|
||
}
|
||
|
||
.hub-card-players {
|
||
display: inline-flex;
|
||
margin-top: $sp-md;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: $radius-full;
|
||
background: $bg-elevated;
|
||
text {
|
||
font-size: 18rpx;
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
|
||
.hub-tip {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-sm;
|
||
margin-top: $sp-2xl;
|
||
padding: $sp-md;
|
||
border-radius: $radius-lg;
|
||
background: rgba(232,168,56,0.06);
|
||
border: 1rpx dashed var(--border-dashed, rgba(255,255,255,0.12));
|
||
}
|
||
|
||
.hub-tip-icon { font-size: 28rpx; }
|
||
.hub-tip-text { font-size: $fs-xs; color: $text-tertiary; }
|
||
|
||
/* === 游戏舞台通用 === */
|
||
.game-stage {
|
||
padding: $sp-xl $sp-lg 0;
|
||
animation: stage-in 0.4s $ease-out both;
|
||
}
|
||
|
||
@keyframes stage-in {
|
||
from { opacity: 0; transform: translateY(30rpx); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.game-rule-card {
|
||
margin-top: $sp-xl;
|
||
padding: $sp-lg;
|
||
border-radius: $radius-lg;
|
||
background: $bg-card;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
}
|
||
|
||
.rule-title {
|
||
display: block;
|
||
font-size: 30rpx;
|
||
font-weight: $fw-bold;
|
||
color: $text-secondary;
|
||
margin-bottom: $sp-sm;
|
||
}
|
||
|
||
.rule-text {
|
||
font-size: 30rpx;
|
||
color: $text-tertiary;
|
||
line-height: $lh-loose;
|
||
}
|
||
|
||
.result-banner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-sm;
|
||
padding: $sp-lg;
|
||
border-radius: $radius-lg;
|
||
margin-top: $sp-lg;
|
||
animation: result-pop 0.4s $ease-bounce both;
|
||
|
||
&.result-drink {
|
||
background: linear-gradient(135deg, rgba(255,107,107,0.14), rgba(232,168,56,0.10));
|
||
border: 1rpx solid rgba(255,107,107,0.25);
|
||
}
|
||
&.result-safe {
|
||
background: linear-gradient(135deg, rgba(94,198,160,0.14), rgba(94,198,160,0.05));
|
||
border: 1rpx solid rgba(94,198,160,0.25);
|
||
}
|
||
&.result-fun {
|
||
background: linear-gradient(135deg, rgba(139,133,184,0.16), rgba(139,133,184,0.05));
|
||
border: 1rpx solid rgba(139,133,184,0.28);
|
||
}
|
||
&.result-draw {
|
||
background: $bg-elevated;
|
||
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
|
||
}
|
||
}
|
||
|
||
@keyframes result-pop {
|
||
from { opacity: 0; transform: scale(0.7); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
.result-emoji { font-size: 44rpx; }
|
||
.result-text { font-size: $fs-lg; font-weight: $fw-bold; color: $text-primary; }
|
||
|
||
.game-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: $sp-xl;
|
||
}
|
||
|
||
.game-restart-btn {
|
||
min-width: 320rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.btn-amber {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: $sp-md $sp-xl;
|
||
border-radius: $radius-full;
|
||
background: linear-gradient(135deg, var(--amber, #E8A838), var(--amber-deep, #C47F17));
|
||
box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25));
|
||
transition: transform $duration-fast $ease-out, opacity $duration-fast;
|
||
text { color: var(--text-on-amber, #0B0B14); font-size:40rpx; font-weight: $fw-bold; }
|
||
&:active { transform: scale(0.94); }
|
||
&.btn-disabled { opacity: 0.5; pointer-events: none; }
|
||
}
|
||
|
||
/* === 数字炸弹 === */
|
||
.bomb-arena { padding-top: $sp-md; }
|
||
|
||
.bomb-range-card {
|
||
text-align: center;
|
||
padding: $sp-2xl $sp-xl;
|
||
border-radius: $radius-xl;
|
||
background: $bg-card;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
transition: all $duration-base;
|
||
|
||
&.bomb-boom {
|
||
border-color: rgba(255,107,107,0.4);
|
||
background: linear-gradient(160deg, rgba(255,107,107,0.10), $bg-card);
|
||
animation: boom-shake 0.5s ease-out;
|
||
}
|
||
}
|
||
|
||
@keyframes boom-shake {
|
||
0%, 100% { transform: translateX(0); }
|
||
20% { transform: translateX(-14rpx) rotate(-1deg); }
|
||
40% { transform: translateX(12rpx) rotate(1deg); }
|
||
60% { transform: translateX(-8rpx); }
|
||
80% { transform: translateX(6rpx); }
|
||
}
|
||
|
||
.bomb-emoji {
|
||
display: block;
|
||
font-size: 96rpx;
|
||
margin-bottom: $sp-lg;
|
||
animation: bomb-pulse 1.6s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes bomb-pulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.12); }
|
||
}
|
||
|
||
.bomb-range-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-lg;
|
||
}
|
||
|
||
.bomb-num {
|
||
font-size: $fs-hero;
|
||
font-weight: $fw-black;
|
||
color: $text-primary;
|
||
line-height: 1;
|
||
}
|
||
|
||
.bomb-fuse {
|
||
width: 160rpx;
|
||
height: 8rpx;
|
||
border-radius: 4rpx;
|
||
background: $bg-elevated;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.bomb-fuse-line {
|
||
height: 100%;
|
||
border-radius: 4rpx;
|
||
background: linear-gradient(90deg, var(--mint, #5EC6A0), var(--amber, #E8A838), var(--coral, #FF6B6B));
|
||
transition: width 0.4s $ease-out;
|
||
}
|
||
|
||
.bomb-hint {
|
||
display: block;
|
||
margin-top: $sp-lg;
|
||
font-size: $fs-sm;
|
||
color: $text-tertiary;
|
||
}
|
||
|
||
.bomb-history {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: $sp-sm;
|
||
margin-top: $sp-lg;
|
||
justify-content: center;
|
||
}
|
||
|
||
.bomb-guess-tag {
|
||
padding: 8rpx 20rpx;
|
||
border-radius: $radius-full;
|
||
animation: tag-in 0.3s $ease-bounce both;
|
||
text { font-size: $fs-sm; font-weight: $fw-medium; }
|
||
&.tag-high {
|
||
background: rgba(255,107,107,0.12);
|
||
text { color: var(--coral, #FF6B6B); }
|
||
}
|
||
&.tag-low {
|
||
background: rgba(94,198,160,0.12);
|
||
text { color: var(--mint, #5EC6A0); }
|
||
}
|
||
}
|
||
|
||
@keyframes tag-in {
|
||
from { opacity: 0; transform: scale(0.5); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
.bomb-input-area {
|
||
display: flex;
|
||
gap: $sp-md;
|
||
margin-top: $sp-xl;
|
||
}
|
||
|
||
.bomb-input {
|
||
flex: 1;
|
||
height: 96rpx;
|
||
padding: 0 $sp-xl;
|
||
border-radius: $radius-full;
|
||
background: var(--bg-input, rgba(255,255,255,0.08));
|
||
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
|
||
color: $text-primary;
|
||
font-size: $fs-xl;
|
||
text-align: center;
|
||
font-weight: $fw-bold;
|
||
}
|
||
|
||
.bomb-go-btn {
|
||
min-width: 180rpx;
|
||
height: 96rpx;
|
||
}
|
||
|
||
/* === 猜拳大战 === */
|
||
.rps-arena { padding-top: $sp-md; }
|
||
|
||
.rps-score-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-xl;
|
||
margin-bottom: $sp-xl;
|
||
}
|
||
|
||
.rps-score-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.rps-score-label { font-size: $fs-xs; color: $text-tertiary; }
|
||
.rps-score-num { font-size: $fs-2xl; font-weight: $fw-black; color: $amber; }
|
||
.rps-vs { font-size: $fs-sm; font-weight: $fw-black; color: $text-tertiary; letter-spacing: 2rpx; }
|
||
|
||
.rps-battle {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-xl;
|
||
padding: $sp-xl 0;
|
||
}
|
||
|
||
.rps-hand {
|
||
width: 180rpx; height: 180rpx;
|
||
display: flex; align-items: center; justify-content: center;
|
||
border-radius: 50%;
|
||
background: $bg-card;
|
||
border: 2rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
box-shadow: $shadow-md;
|
||
}
|
||
|
||
.rps-hand-emoji { font-size: 80rpx; }
|
||
|
||
.hand-shake {
|
||
animation: hand-shake 0.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes hand-shake {
|
||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||
25% { transform: translateY(-20rpx) rotate(-6deg); }
|
||
75% { transform: translateY(10rpx) rotate(4deg); }
|
||
}
|
||
|
||
.hand-reveal {
|
||
animation: hand-pop 0.35s $ease-bounce both;
|
||
}
|
||
|
||
@keyframes hand-pop {
|
||
from { transform: scale(0.6) rotate(-10deg); }
|
||
to { transform: scale(1) rotate(0deg); }
|
||
}
|
||
|
||
.rps-clash-text { font-size: 56rpx; animation: clash-flash 0.6s ease-out both; }
|
||
|
||
@keyframes clash-flash {
|
||
0% { opacity: 0; transform: scale(2.4); }
|
||
100% { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
.rps-picker {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: $sp-lg;
|
||
margin-top: $sp-lg;
|
||
}
|
||
|
||
.rps-choice {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: $sp-sm;
|
||
padding: $sp-lg $sp-xl;
|
||
border-radius: $radius-xl;
|
||
background: $bg-card;
|
||
border: 2rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
transition: all $duration-fast $ease-out;
|
||
|
||
&:active { transform: scale(0.9); }
|
||
&.choice-active {
|
||
border-color: rgba(232,168,56,0.5);
|
||
background: rgba(232,168,56,0.08);
|
||
box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25));
|
||
}
|
||
}
|
||
|
||
.rps-choice-emoji { font-size: 56rpx; }
|
||
.rps-choice-name { font-size: $fs-xs; color: $text-secondary; font-weight: $fw-medium; }
|
||
|
||
.rps-countdown {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: $sp-lg;
|
||
}
|
||
|
||
.rps-countdown-text {
|
||
font-size: $fs-hero;
|
||
font-weight: $fw-black;
|
||
color: $amber;
|
||
animation: count-zoom 0.5s $ease-out both;
|
||
}
|
||
|
||
@keyframes count-zoom {
|
||
from { opacity: 0; transform: scale(1.8); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
/* === 骰子大话 === */
|
||
.dice-arena {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding-top: $sp-md;
|
||
}
|
||
|
||
.dice-cup {
|
||
position: relative;
|
||
width: 400rpx; height: 320rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-lg;
|
||
border-radius: $radius-xl;
|
||
background: $bg-card;
|
||
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
|
||
box-shadow: $shadow-md;
|
||
transition: box-shadow $duration-base;
|
||
|
||
&.cup-shaking {
|
||
animation: cup-shake 0.15s linear infinite;
|
||
box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25));
|
||
}
|
||
}
|
||
|
||
@keyframes cup-shake {
|
||
0%, 100% { transform: rotate(0deg) translateX(0); }
|
||
25% { transform: rotate(-2deg) translateX(-6rpx); }
|
||
75% { transform: rotate(2deg) translateX(6rpx); }
|
||
}
|
||
|
||
.dice-pair {
|
||
display: flex;
|
||
gap: $sp-lg;
|
||
}
|
||
|
||
.die {
|
||
width: 132rpx; height: 132rpx;
|
||
display: flex;
|
||
align-items: center; justify-content: center;
|
||
border-radius: $radius-md;
|
||
background: linear-gradient(160deg, #FBF7EF, #EAE2D3);
|
||
box-shadow: inset 0 4rpx 8rpx rgba(255,255,255,0.7), inset 0 -6rpx 12rpx rgba(0,0,0,0.12), $shadow-sm;
|
||
transition: transform $duration-base $ease-bounce;
|
||
|
||
&.die-rolling { animation: die-spin 0.3s linear infinite; }
|
||
}
|
||
|
||
@keyframes die-spin {
|
||
0% { transform: rotate(0deg) scale(1); }
|
||
50% { transform: rotate(180deg) scale(0.85); }
|
||
100% { transform: rotate(360deg) scale(1); }
|
||
}
|
||
|
||
.die-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
}
|
||
|
||
.die-cell {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.die-pip {
|
||
width: 18rpx;
|
||
height: 18rpx;
|
||
border-radius: 50%;
|
||
background: #241B10;
|
||
box-shadow: inset 0 2rpx 3rpx rgba(0,0,0,0.5);
|
||
}
|
||
|
||
.dice-sum {
|
||
font-size: $fs-2xl;
|
||
font-weight: $fw-black;
|
||
color: $amber;
|
||
}
|
||
|
||
.dice-rule-result {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $sp-md;
|
||
width: 100%;
|
||
margin-top: $sp-lg;
|
||
padding: $sp-lg;
|
||
border-radius: $radius-lg;
|
||
animation: result-pop 0.4s $ease-bounce both;
|
||
|
||
&.result-drink {
|
||
background: linear-gradient(135deg, rgba(255,107,107,0.12), rgba(255,107,107,0.04));
|
||
border: 1rpx solid rgba(255,107,107,0.22);
|
||
}
|
||
&.result-safe {
|
||
background: linear-gradient(135deg, rgba(94,198,160,0.12), rgba(94,198,160,0.04));
|
||
border: 1rpx solid rgba(94,198,160,0.22);
|
||
}
|
||
&.result-fun {
|
||
background: linear-gradient(135deg, rgba(139,133,184,0.14), rgba(139,133,184,0.04));
|
||
border: 1rpx solid rgba(139,133,184,0.24);
|
||
}
|
||
}
|
||
|
||
.dice-rule-emoji { font-size: 48rpx; }
|
||
.dice-rule-name { display: block; font-size: $fs-base; font-weight: $fw-bold; color: $text-primary; }
|
||
.dice-rule-desc { display: block; font-size: $fs-xs; color: $text-tertiary; margin-top: 4rpx; }
|
||
|
||
.dice-history {
|
||
display: flex;
|
||
gap: $sp-sm;
|
||
margin-top: $sp-lg;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.dice-history-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
padding: 6rpx 16rpx;
|
||
border-radius: $radius-full;
|
||
background: $bg-elevated;
|
||
}
|
||
|
||
.dice-history-val { font-size: $fs-xs; font-weight: $fw-bold; color: $text-secondary; }
|
||
.dice-history-rule { font-size: 18rpx; color: $text-tertiary; }
|
||
|
||
.dice-roll-btn {
|
||
margin-top: $sp-xl;
|
||
min-width: 360rpx;
|
||
height: 96rpx;
|
||
}
|
||
|
||
/* === 命运转盘(老虎机) === */
|
||
.wheel-arena {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding-top: $sp-md;
|
||
}
|
||
|
||
.slot-machine {
|
||
position: relative;
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.slot-glow {
|
||
position: absolute;
|
||
top: -30rpx; left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 480rpx; height: 480rpx;
|
||
border-radius: 50%;
|
||
background: radial-gradient(circle, rgba(232,168,56,0.10) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
opacity: 0.5;
|
||
transition: opacity $duration-base;
|
||
|
||
&.slot-glow-on {
|
||
opacity: 1;
|
||
animation: glow-pulse 0.8s ease-in-out infinite;
|
||
}
|
||
}
|
||
|
||
@keyframes glow-pulse {
|
||
0%, 100% { transform: translateX(-50%) scale(1); }
|
||
50% { transform: translateX(-50%) scale(1.08); }
|
||
}
|
||
|
||
.slot-window {
|
||
position: relative;
|
||
width: 520rpx;
|
||
height: 360rpx;
|
||
border-radius: $radius-xl;
|
||
background: $bg-card;
|
||
border: 2rpx solid var(--border-subtle, rgba(255,255,255,0.10));
|
||
box-shadow: $shadow-lg;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.slot-strip {
|
||
position: absolute;
|
||
left: 0; right: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
will-change: transform;
|
||
}
|
||
|
||
.slot-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $sp-sm;
|
||
height: 120rpx;
|
||
border-bottom: 1rpx solid var(--border-micro, rgba(255,255,255,0.05));
|
||
transition: background $duration-base;
|
||
|
||
&.slot-item-active {
|
||
background: rgba(232,168,56,0.10);
|
||
}
|
||
}
|
||
|
||
.slot-item-emoji-box {
|
||
width: 52rpx;
|
||
height: 52rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.slot-item-emoji { font-size: 40rpx; line-height: 1; }
|
||
.slot-item-label { font-size: $fs-base; font-weight: $fw-bold; color: $text-primary; letter-spacing: 2rpx; }
|
||
|
||
.slot-mask-top, .slot-mask-bottom {
|
||
position: absolute;
|
||
left: 0; right: 0;
|
||
height: 120rpx;
|
||
pointer-events: none;
|
||
z-index: 5;
|
||
}
|
||
|
||
.slot-mask-top {
|
||
top: 0;
|
||
background: linear-gradient(to bottom, var(--bg-card, #1A1A28) 20%, transparent);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.slot-mask-bottom {
|
||
bottom: 0;
|
||
background: linear-gradient(to top, var(--bg-card, #1A1A28) 20%, transparent);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.slot-indicator {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 0; right: 0;
|
||
transform: translateY(-50%);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 0 $sp-sm;
|
||
pointer-events: none;
|
||
z-index: 10;
|
||
}
|
||
|
||
.slot-ind-arrow {
|
||
font-size: 28rpx;
|
||
color: var(--amber, #E8A838);
|
||
text-shadow: 0 0 12rpx rgba(232,168,56,0.6);
|
||
}
|
||
|
||
.slot-spin-btn {
|
||
margin-top: $sp-xl;
|
||
min-width: 360rpx;
|
||
height: 96rpx;
|
||
}
|
||
|
||
/* === 返回大厅按钮 === */
|
||
.back-hub-btn {
|
||
position: fixed;
|
||
bottom: calc(env(safe-area-inset-bottom) + 48rpx);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
padding: $sp-md $sp-2xl;
|
||
border-radius: $radius-full;
|
||
background: $bg-elevated;
|
||
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
|
||
box-shadow: $shadow-md;
|
||
z-index: 60;
|
||
transition: transform $duration-fast $ease-out;
|
||
&:active { transform: translateX(-50%) scale(0.92); }
|
||
text { font-size: $fs-sm; font-weight: $fw-medium; color: $text-secondary; }
|
||
}
|
||
</style>
|