Skip to Content
🎲 欢迎使用 RandBox - 功能强大的 JavaScript 随机数据生成库! 了解详情
📦 示例代码vueSlotMachine - 滚动抽奖

SlotMachine - 滚动抽奖

SlotMachine 是一个基于Canvas和Vue 3的老虎机风格滚动抽奖组件,提供多滚轴同步或异步滚动,支持自定义符号、权重配置和中奖规则。

📦 导入

<script setup> import { SlotMachine } from '@randbox/vue' // 或者导入类型 import type { SlotMachineProps, SlotMachineResult } from '@randbox/vue' </script>

🚀 基础用法

<template> <SlotMachine :reels="reels" @result="handleResult" /> </template> <script setup> import { ref } from 'vue' import { SlotMachine } from '@randbox/vue' const reels = ref([ ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '7️⃣'], ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '7️⃣'], ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '7️⃣'] ]) const handleResult = (result) => { console.log('老虎机结果:', result) if (result.isJackpot) { alert('🎉 恭喜中大奖!') } } </script>

🎯 高级用法

带权重的滚动抽奖

<template> <SlotMachine :reels="reels" :weights="weights" :animation-duration="4000" @result="handleResult" /> </template> <script setup> import { ref } from 'vue' const reels = ref([ ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '7️⃣'], ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '7️⃣'], ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '7️⃣'] ]) // 为每个滚轴设置不同的权重 const weights = ref([ [20, 18, 15, 12, 10, 8, 5, 2], // 第一个滚轴权重 [20, 18, 15, 12, 10, 8, 5, 2], // 第二个滚轴权重 [20, 18, 15, 12, 10, 8, 5, 2] // 第三个滚轴权重 ]) const handleResult = (result) => { console.log('权重抽奖结果:', result) if (result.combination === '💎💎💎') { alert('💎 钻石大奖!') } } </script>

数字老虎机

<template> <SlotMachine :reels="numberReels" :animation-duration="3000" @result="handleResult" /> </template> <script setup> import { ref } from 'vue' const numberReels = ref([ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] ]) const checkJackpot = (results) => { // 检查是否为顺子或三个相同数字 const [a, b, c] = results.map(r => parseInt(r)) return a === b && b === c // 三个相同数字 } const handleResult = (result) => { const isWin = checkJackpot(result.results) console.log('数字老虎机:', { ...result, isWin }) } </script>

五轴老虎机

<template> <SlotMachine :reels="reels" :weights="weights" :animation-duration="5000" @result="handleResult" /> </template> <script setup> import { ref, computed } from 'vue' const symbols = ['A', 'K', 'Q', 'J', '10', '9', '8', '7'] const reels = ref(Array(5).fill(symbols)) // 为五轴设置不同的权重分布 const weights = computed(() => reels.value.map((_, index) => { // 中间轴比较容易出好符号 const bonus = index === 2 ? 1.2 : 1.0 return symbols.map((_, i) => (symbols.length - i) * bonus) }) ) const checkWinningLines = (results) => { const lines = [ results, // 中间线 // 可以添加更多支付线逻辑 ] return lines.some(line => { const first = line[0] return line.every(symbol => symbol === first) }) } const handleResult = (result) => { const hasWin = checkWinningLines(result.results) console.log('五轴结果:', { ...result, hasWin }) } </script>

完整配置示例

<template> <div class="slot-machine-container"> <!-- 统计信息 --> <div class="stats-panel"> <div>总旋转次数: {{ totalSpins }} | 中奖次数: {{ wins }} | 中奖率: {{ winRate }}%</div> </div> <SlotMachine :reels="reels" :weights="weights" :animation-duration="3500" :button-text="isSpinning ? '旋转中...' : '🎰 开始旋转'" class="casino-slot" :style="{ border: '3px solid gold', borderRadius: '15px' }" :disabled="isSpinning" @game-start="handleGameStart" @game-end="handleGameEnd" @result="handleResult" /> <!-- 历史记录 --> <div v-if="history.length > 0" class="history"> <h3>最近旋转记录:</h3> <div v-for="(result, index) in history" :key="index" class="history-item" :class="{ 'jackpot': result.isJackpot }" > <strong>{{ result.combination }}</strong> <span v-if="result.isJackpot" class="jackpot-text">🎉 中奖!</span> </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' import { SlotMachine } from '@randbox/vue' const isSpinning = ref(false) const totalSpins = ref(0) const wins = ref(0) const history = ref([]) const reels = ref([ ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '⭐'], ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '⭐'], ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '⭐'] ]) const weights = ref([ [25, 20, 15, 12, 10, 8, 5, 5], [25, 20, 15, 12, 10, 8, 5, 5], [25, 20, 15, 12, 10, 8, 5, 5] ]) const winRate = computed(() => totalSpins.value > 0 ? ((wins.value / totalSpins.value) * 100).toFixed(1) : '0.0' ) const handleGameStart = () => { isSpinning.value = true console.log('开始旋转...') } const handleGameEnd = (result) => { isSpinning.value = false totalSpins.value++ if (result.isJackpot) { wins.value++ } history.value = [result, ...history.value.slice(0, 9)] console.log('旋转结束:', result) } const handleResult = (result) => { console.log('实时结果:', result) } </script> <style scoped> .slot-machine-container { padding: 20px; } .stats-panel { margin-bottom: 20px; text-align: center; padding: 15px; background-color: #f8f9fa; border-radius: 10px; } .casino-slot { background: linear-gradient(145deg, #2c3e50, #34495e); border: 3px solid #f39c12; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.3), inset 0 2px 10px rgba(255,255,255,0.1); } .casino-slot:hover { transform: scale(1.02); transition: transform 0.3s ease; } .history { margin-top: 20px; } .history-item { padding: 8px; margin: 5px 0; border-radius: 5px; border: 1px solid #dee2e6; background-color: #f8f9fa; } .history-item.jackpot { background-color: #fff3cd; border: 2px solid #ffc107; } .jackpot-text { color: #d4a853; margin-left: 10px; } </style>

渐进式奖池

<template> <div> <div class="jackpot-info"> <h2>累积奖池: ${{ jackpotAmount.toLocaleString() }}</h2> <div v-if="lastWin" class="last-win"> 上次中奖: {{ lastWin.type }} - ${{ lastWin.amount }} </div> </div> <SlotMachine :reels="reels" @result="handleResult" /> </div> </template> <script setup> import { ref } from 'vue' const jackpotAmount = ref(1000) const lastWin = ref(null) const reels = ref([ ['💎', '⭐', '🔔', '🍒', '🍇', '🍊', '🍎'], ['💎', '⭐', '🔔', '🍒', '🍇', '🍊', '🍎'], ['💎', '⭐', '🔔', '🍒', '🍇', '🍊', '🍎'] ]) const handleResult = (result) => { // 每次游戏增加奖池 jackpotAmount.value += 10 if (result.combination === '💎💎💎') { // 中超级大奖 lastWin.value = { type: 'jackpot', amount: jackpotAmount.value } jackpotAmount.value = 1000 // 重置奖池 alert(`🎉 超级大奖!奖金: $${lastWin.value.amount}`) } else if (result.isJackpot) { const amount = 100 lastWin.value = { type: 'normal', amount } alert(`🎉 中奖!奖金: $${amount}`) } } </script> <style scoped> .jackpot-info { text-align: center; margin-bottom: 20px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; } .last-win { color: #90EE90; margin-top: 10px; } </style>

多支付线老虎机

<template> <SlotMachine :reels="reels" @result="handleResult" /> <div v-if="winInfo.length > 0" class="win-info"> <h3>中奖信息:</h3> <div v-for="(win, index) in winInfo" :key="index" class="win-line" > 支付线{{ win.line }}: {{ win.symbol }} x{{ win.count }} = {{ win.payout }}分 </div> <div class="total-payout">总奖金: {{ totalPayout }}分</div> </div> </template> <script setup> import { ref, computed } from 'vue' const symbols = ['A', 'K', 'Q', 'J', '10', '9'] const reels = ref(Array(5).fill(symbols)) const winInfo = ref([]) // 定义支付线 const paylines = [ [1, 1, 1, 1, 1], // 中线 [0, 0, 0, 0, 0], // 上线 [2, 2, 2, 2, 2], // 下线 [0, 1, 2, 1, 0], // V形 [2, 1, 0, 1, 2], // 倒V形 ] const totalPayout = computed(() => winInfo.value.reduce((sum, win) => sum + win.payout, 0) ) const calculatePayout = (symbol, count) => { const payouts = { 'A': [0, 0, 50, 200, 1000], 'K': [0, 0, 25, 100, 500], 'Q': [0, 0, 20, 80, 400], 'J': [0, 0, 15, 60, 300], '10': [0, 0, 10, 40, 200], '9': [0, 0, 5, 20, 100], } return payouts[symbol]?.[count] || 0 } const checkPaylines = (results) => { const wins = [] paylines.forEach((line, lineIndex) => { const lineSymbols = line.map((row, col) => { return results[col] // 简化版本 }) // 检查支付线是否中奖 const firstSymbol = lineSymbols[0] let matchCount = 1 for (let i = 1; i < lineSymbols.length; i++) { if (lineSymbols[i] === firstSymbol) { matchCount++ } else { break } } if (matchCount >= 3) { wins.push({ line: lineIndex + 1, symbol: firstSymbol, count: matchCount, payout: calculatePayout(firstSymbol, matchCount) }) } }) return wins } const handleResult = (result) => { const wins = checkPaylines(result.results) winInfo.value = wins if (totalPayout.value > 0) { alert(`中奖!总奖金: ${totalPayout.value}`) } } </script> <style scoped> .win-info { margin-top: 20px; padding: 15px; background-color: #d4edda; border-radius: 8px; } .win-line { margin: 5px 0; padding: 5px; background-color: #c3e6cb; border-radius: 4px; } .total-payout { margin-top: 10px; font-weight: bold; font-size: 1.2em; color: #155724; } </style>

📋 API参考

Props

属性类型默认值描述
reelsstring[][]必需滚轴配置,每个子数组代表一个滚轴的符号
weightsnumber[][]undefined权重配置,控制每个滚轴上符号的出现概率
animationDurationnumber3000动画持续时间(毫秒)
buttonTextstring'开始旋转'操作按钮文字
classstring''CSS类名
styleRecord<string, any>{}内联样式
disabledbooleanfalse是否禁用

Events

事件名参数描述
result(result: SlotMachineResult)结果回调
game-start()游戏开始回调
game-end(result: SlotMachineResult)游戏结束回调

SlotMachineResult

interface SlotMachineResult { results: string[] // 每个滚轴的结果 isJackpot: boolean // 是否中大奖 combination: string // 组合字符串(如 "🍎🍎🍎") }

🎨 样式定制

霓虹灯效果

<style scoped> .neon-slot { border: 2px solid #ff6b6b; border-radius: 15px; box-shadow: 0 0 20px #ff6b6b, inset 0 0 20px rgba(255, 107, 107, 0.1); animation: neonGlow 2s ease-in-out infinite alternate; } @keyframes neonGlow { from { box-shadow: 0 0 20px #ff6b6b, inset 0 0 20px rgba(255, 107, 107, 0.1); } to { box-shadow: 0 0 30px #ff6b6b, 0 0 40px #ff6b6b, inset 0 0 30px rgba(255, 107, 107, 0.2); } } </style>

响应式布局

<template> <div class="responsive-container"> <SlotMachine :reels="reels" class="responsive-slot" @result="handleResult" /> </div> </template> <style scoped> .responsive-container { max-width: 600px; margin: 0 auto; padding: 20px; } .responsive-slot { width: 100%; height: auto; } @media (max-width: 768px) { .responsive-container { padding: 10px; } .responsive-slot { font-size: 0.8em; } } </style>

🔧 高级功能

中奖规则定制

<script setup> import { ref } from 'vue' const symbols = ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '⭐'] const reels = ref([symbols, symbols, symbols]) // 自定义中奖规则 const checkWinning = (results) => { const [a, b, c] = results // 规则1: 三个相同 if (a === b && b === c) return { type: 'triple', multiplier: 10 } // 规则2: 两个相同 if (a === b || b === c || a === c) return { type: 'double', multiplier: 2 } // 规则3: 特殊组合 if (results.includes('💎') && results.includes('⭐')) { return { type: 'special', multiplier: 5 } } return { type: 'none', multiplier: 0 } } const handleResult = (result) => { const winInfo = checkWinning(result.results) console.log('中奖信息:', winInfo) if (winInfo.multiplier > 0) { alert(`中奖类型: ${winInfo.type}, 倍数: ${winInfo.multiplier}x`) } } </script>

与Vuex/Pinia状态管理集成

<template> <SlotMachine :reels="gameStore.reels" :disabled="gameStore.isSpinning" @result="gameStore.recordResult" /> <div class="game-stats"> <div>总旋转: {{ gameStore.totalSpins }}</div> <div>中奖率: {{ gameStore.winRate }}%</div> </div> </template> <script setup> import { useGameStore } from '@/stores/game' const gameStore = useGameStore() </script>
// stores/game.ts import { defineStore } from 'pinia' export const useGameStore = defineStore('slotGame', { state: () => ({ reels: [ ['🍎', '🍊', '🍋'], ['🍎', '🍊', '🍋'], ['🍎', '🍊', '🍋'] ], totalSpins: 0, totalWins: 0, isSpinning: false, results: [] }), getters: { winRate: (state) => state.totalSpins > 0 ? ((state.totalWins / state.totalSpins) * 100).toFixed(1) : '0' }, actions: { recordResult(result) { this.totalSpins++ if (result.isJackpot) { this.totalWins++ } this.results.unshift(result) } } })

🎯 最佳实践

1. 符号设计建议

<script setup> // 使用易区分的符号 const goodSymbols = ['🍎', '🍊', '🍋', '🍒', '🍇', '🔔', '💎', '⭐'] // 避免相似符号 const badSymbols = ['😀', '😃', '😄', '😁'] // 太相似,难以区分 </script>

2. 权重平衡

<script setup> // 平衡的权重分配 const balancedWeights = ref([ [30, 25, 20, 15, 5, 3, 1, 1], // 常见符号权重高,稀有符号权重低 [30, 25, 20, 15, 5, 3, 1, 1], [30, 25, 20, 15, 5, 3, 1, 1] ]) </script>

3. 性能优化

<script setup> import { ref, computed, shallowRef } from 'vue' // 使用shallowRef优化大数组 const reels = shallowRef([...]) // 使用computed缓存复杂计算 const processedReels = computed(() => generateReels(complexity.value) ) </script>

🐛 常见问题

Q: 如何实现真正的随机性?

A: 组件使用RandBox的Mersenne Twister算法,提供高质量的随机数生成。

Q: 滚轴数量有限制吗?

A: 理论上没有限制,但建议不超过7个滚轴以保证性能和用户体验。

Q: 可以实现异步滚动吗?

A: 是的,组件内部已实现异步滚动效果,每个滚轴会在不同时间停止。

Q: 如何调整中奖概率?

A: 通过weights属性调整每个符号的权重,权重越高出现概率越大。

Q: 如何与Vue Router集成?

A: 可以在路由组件中正常使用,或者通过路由参数动态配置滚轴。

🔗 相关链接

最后更新于: