- <template>
- <view class="page">
-
- <navbar leftClick
- color="#fff"
- bgColor="#667eea"
- @leftClick="$utils.navigateBack" />
- <view class="turntable-container">
-
- <!-- 头部标题 -->
- <view class="header">
- <text class="title">幸运大转盘</text>
- <text class="subtitle">转一转,好运来!</text>
- </view>
-
- <!-- 积分余额显示 -->
- <view class="points-display">
- <view class="points-info">
- <text class="points-label">当前积分:</text>
- <text class="points-value">{{ userPoints }}</text>
- </view>
- <view class="cost-info">
- <text class="cost-label">每次消耗:</text>
- <text class="cost-value">{{ drawCost }}积分</text>
- </view>
- </view>
-
- <!-- 转盘区域 -->
- <view class="turntable-wrapper" v-if="prizes.length > 0">
- <view class="turntable" :class="{ 'spinning': isSpinning }" :style="{ transform: `rotate(${rotateAngle}deg)` }">
- <!-- 使用纯CSS创建转盘 -->
- <view class="wheel-bg">
- <!-- 8个扇形区域 -->
- <view
- v-for="(prize, index) in prizes"
- :key="index"
- class="wheel-sector"
- :style="{
- backgroundColor: sectorColors[index % sectorColors.length],
- transform: `rotate(${index * sectorAngle}deg)`
- }"
- >
- </view>
- </view>
-
- <!-- 奖品文字覆盖层 -->
- <view class="prizes-overlay">
- <view
- v-for="(prize, index) in prizes"
- :key="index"
- class="prize-item"
- :style="prizeStyles[index]"
- >
- <view class="prize-content">
- <text class="prize-icon">{{ getIcon(prize.type) }}</text>
- <text class="prize-name">{{ prize.title }}</text>
- <text class="prize-value" v-if="prize.price">¥{{ prize.price }}</text>
- </view>
- </view>
- </view>
- </view>
-
- <!-- 中心指针 -->
- <view class="pointer">
- <view class="pointer-triangle"></view>
- </view>
-
- <!-- 中心按钮 -->
- <view class="center-button" @click="startSpin" :class="{ disabled: isSpinning || userPoints < drawCost }">
- <text class="button-text">{{ getButtonText() }}</text>
- </view>
- </view>
-
- <!-- 加载中状态 -->
- <view v-else class="loading-wrapper">
- <uv-loading-icon mode="spinner" color="#fff" size="60"></uv-loading-icon>
- <text class="loading-text">加载中...</text>
- </view>
-
- <!-- 抽奖结果弹窗 -->
- <view class="result-modal" v-if="showResult" @click="closeResult">
- <view class="modal-content" @click.stop>
- <text class="result-title">🎉 恭喜您 🎉</text>
- <view class="result-prize">
- <!-- <text class="result-icon">{{ getIcon(currentPrize.type) }}</text> -->
-
- <image :src="currentPrize.img" mode="widthFix" style="width: 100%;"></image>
-
- <text class="result-name">{{ currentPrize.title }}</text>
- <text class="result-value" v-if="currentPrize.price">¥{{ currentPrize.price }}</text>
- </view>
- <view class="points-change">
- <text class="change-text">{{ getPointsChangeText() }}</text>
- </view>
- <view class="result-actions">
- <button class="confirm-btn" @click="closeResult">确定</button>
- </view>
- </view>
- </view>
-
- <!-- 积分提示 -->
- <view class="spin-info">
- <text>消耗{{ drawCost }}积分即可抽奖,快来试试手气吧!</text>
- </view>
- </view>
- </view>
- </template>
-
- <script>
- import { mapState } from 'vuex'
- export default {
- computed: {
- ...mapState(['userInfo']),
- // 获取用户当前积分
- userPoints() {
- return this.userInfo?.integerPrice || 0
- }
- },
- data() {
- return {
- // 奖品配置(从接口获取)
- prizes: [],
- sectorColors: [
- '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
- '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F'
- ],
- isSpinning: false, // 是否正在旋转
- rotateAngle: 0, // 旋转角度
- showResult: false, // 显示结果弹窗
- currentPrize: null, // 当前中奖奖品
- sectorAngle: 0, // 每个扇形的角度
- drawCost: 5, // 抽奖消耗积分
- pointsChange: null, // 积分变化
- // 预计算奖品位置样式
- prizeStyles: []
- }
- },
- onLoad() {
- this.getLuckDrawList()
- },
- onShow() {
- // 刷新用户信息以获取最新积分
- if (uni.getStorageSync('token')) {
- this.$store.commit('getUserInfo')
- }
- },
- methods: {
- // 获取抽奖列表
- getLuckDrawList() {
- this.$api('getLuckDrawList', {}, res => {
- if (res.code == 200) {
- this.prizes = res.result || []
-
- // 计算每个扇形的角度
- if (this.prizes.length > 0) {
- this.sectorAngle = 360 / this.prizes.length
- this.calculatePrizeStyles()
- }
- } else {
- uni.showToast({
- title: res.message || '获取抽奖信息失败',
- icon: 'none'
- })
- }
- })
- },
-
- // 获取按钮文字
- getButtonText() {
- if (this.isSpinning) {
- return '抽奖中...'
- }
- if (this.userPoints < this.drawCost) {
- return '积分不足'
- }
- return '开始抽奖'
- },
-
- // 根据奖品类型获取图标
- getIcon(type) {
- const iconMap = {
- '0': '💰',
- 'points': '⭐',
- 'gift': '🎁',
- 'coupon': '🎫',
- 'thanks': '🤝'
- }
- return iconMap[type] || '🎁'
- },
-
- // 计算奖品位置样式
- calculatePrizeStyles() {
- this.prizeStyles = this.prizes.map((prize, index) => {
- // 计算奖品在圆形中的位置
- const angle = (index * this.sectorAngle + this.sectorAngle / 2) * Math.PI / 180; // 转换为弧度
- const radius = 150; // 奖品距离中心的距离
- const x = Math.cos(angle - Math.PI/2) * radius; // 减去90度,因为我们希望0度在顶部
- const y = Math.sin(angle - Math.PI/2) * radius;
-
- return `left: calc(50% + ${x}rpx); top: calc(50% + ${y}rpx); transform: translate(-50%, -50%);`
- })
- },
-
- // 开始抽奖
- startSpin() {
- // 检查是否可以抽奖
- if (this.isSpinning) {
- return
- }
-
- // 检查积分是否足够
- if (this.userPoints < this.drawCost) {
- uni.showModal({
- title: '积分不足',
- content: `抽奖需要消耗${this.drawCost}积分,您当前积分为${this.userPoints}`,
- showCancel: true,
- cancelText: '取消',
- confirmText: '去赚积分',
- success: (res) => {
- if (res.confirm) {
- // 跳转到积分获取页面或任务页面
- uni.navigateBack()
- }
- }
- })
- return
- }
-
- this.isSpinning = true
-
- // 调用抽奖接口
- this.$api('luckDraw', {}, res => {
- if (res.code == 200) {
- const result = res.result
- this.currentPrize = result.gift
- this.pointsChange = result.remainingIntegral || null
-
- // 更新用户信息(积分变化)
- this.$store.commit('getUserInfo')
-
- // 找到中奖奖品的索引
- const prizeIndex = this.prizes.findIndex(prize => prize.id == result.gift.id)
-
- if (prizeIndex !== -1) {
- // 计算旋转角度
- const targetAngle = 360 - (prizeIndex * this.sectorAngle + this.sectorAngle / 2)
- const spinRounds = 5 // 转5圈
- const finalAngle = this.rotateAngle + spinRounds * 360 + targetAngle
-
- this.rotateAngle = finalAngle
-
- // 动画结束后显示结果
- setTimeout(() => {
- this.isSpinning = false
- this.showResult = true
- this.handlePrizeResult()
- }, 3000)
- } else {
- this.isSpinning = false
- uni.showToast({
- title: '抽奖异常,请重试',
- icon: 'none'
- })
- }
- } else {
- this.isSpinning = false
- uni.showToast({
- title: res.message || '抽奖失败,请重试',
- icon: 'none'
- })
- }
- })
- },
-
- // 处理中奖结果
- handlePrizeResult() {
- if (this.currentPrize) {
- console.log(`中奖信息:`, this.currentPrize)
- // 根据奖品类型显示不同提示
- if (this.currentPrize.type === 'money') {
- console.log(`获得现金奖励: ${this.currentPrize.prizeValue}`)
- } else if (this.currentPrize.type === 'points') {
- console.log(`获得积分奖励: ${this.currentPrize.prizeValue}`)
- } else if (this.currentPrize.type === 'gift') {
- console.log(`获得礼品: ${this.currentPrize.prizeName}`)
- }
- }
- },
-
- // 关闭结果弹窗
- closeResult() {
- this.showResult = false
- this.pointsChange = null
- // 刷新抽奖信息
- this.getLuckDrawList()
- },
-
- // 积分变化提示文字
- getPointsChangeText() {
- let text = `消耗 ${this.drawCost} 积分`
-
- if (this.currentPrize && this.currentPrize.type === 'points') {
- const gained = parseInt(this.currentPrize.prizeValue)
- const net = gained - this.drawCost
- if (net > 0) {
- text += `,获得 ${gained} 积分,净收益 +${net} 积分`
- } else if (net < 0) {
- text += `,获得 ${gained} 积分,净损失 ${Math.abs(net)} 积分`
- } else {
- text += `,获得 ${gained} 积分,收支平衡`
- }
- }
-
- return text
- }
- }
- }
- </script>
-
- <style scoped lang="scss">
- .turntable-container {
- min-height: 100vh;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- padding: 40rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .header {
- text-align: center;
- margin-bottom: 40rpx;
- color: white;
-
- .title {
- font-size: 48rpx;
- font-weight: bold;
- display: block;
- margin-bottom: 20rpx;
- }
-
- .subtitle {
- font-size: 28rpx;
- opacity: 0.9;
- }
- }
-
- .points-display {
- background: rgba(255, 255, 255, 0.2);
- border-radius: 20rpx;
- padding: 30rpx;
- margin-bottom: 40rpx;
- backdrop-filter: blur(10rpx);
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- max-width: 600rpx;
-
- .points-info, .cost-info {
- display: flex;
- align-items: center;
- color: white;
-
- .points-label, .cost-label {
- font-size: 28rpx;
- margin-right: 10rpx;
- }
-
- .points-value {
- font-size: 32rpx;
- font-weight: bold;
- color: #FFD700;
- }
-
- .cost-value {
- font-size: 28rpx;
- font-weight: bold;
- color: #FF6B6B;
- }
- }
- }
-
- .turntable-wrapper {
- position: relative;
- width: 600rpx;
- height: 600rpx;
- margin-bottom: 60rpx;
- }
-
- .loading-wrapper {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 600rpx;
-
- .loading-text {
- color: white;
- font-size: 28rpx;
- margin-top: 20rpx;
- }
- }
-
- .turntable {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- position: relative;
- transition: transform 3s cubic-bezier(0.23, 1, 0.32, 1);
- box-shadow: 0 0 40rpx rgba(0, 0, 0, 0.3);
- overflow: hidden;
-
- &.spinning {
- transition-duration: 3s;
- }
- }
-
- .wheel-bg {
- position: absolute;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- overflow: hidden;
- }
-
- .wheel-sector {
- position: absolute;
- width: 50%;
- height: 50%;
- top: 0%;
- left: 50%;
- transform-origin: 0% 100%;
-
- &::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: inherit;
- clip-path: polygon(0% 100%, 50% 0%, 100% 100%);
- }
-
- // 添加边框线
- &::after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- border-right: 2rpx solid rgba(255,255,255,0.3);
- transform-origin: 0% 100%;
- }
- }
-
- .prizes-overlay {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- z-index: 990;
- }
-
- .prize-item {
- position: absolute;
- width: 120rpx;
- height: 80rpx;
- z-index: 10;
- }
-
- .prize-content {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- text-align: center;
- color: white;
- text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.8);
-
- .prize-icon {
- font-size: 32rpx;
- display: block;
- margin-bottom: 4rpx;
- }
-
- .prize-name {
- font-size: 18rpx;
- display: block;
- font-weight: bold;
- margin-bottom: 2rpx;
- line-height: 1.2;
- }
-
- .prize-value {
- font-size: 20rpx;
- display: block;
- font-weight: bold;
- line-height: 1.2;
- }
- }
-
- .pointer {
- position: absolute;
- top: -20rpx;
- left: 50%;
- transform: translateX(-50%);
- z-index: 100;
-
- .pointer-triangle {
- width: 0;
- height: 0;
- border-left: 20rpx solid transparent;
- border-right: 20rpx solid transparent;
- border-top: 60rpx solid #FF4757;
- filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3));
- }
- }
-
- .center-button {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 160rpx;
- height: 160rpx;
- border-radius: 50%;
- background: linear-gradient(145deg, #FF6B6B, #FF4757);
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 8rpx 20rpx rgba(255, 71, 87, 0.4);
- z-index: 50;
- transition: transform 0.2s;
-
- &:active:not(.disabled) {
- transform: translate(-50%, -50%) scale(0.95);
- }
-
- &.disabled {
- opacity: 0.6;
- cursor: not-allowed;
- background: linear-gradient(145deg, #999, #777);
- }
-
- .button-text {
- color: white;
- font-size: 24rpx;
- font-weight: bold;
- text-align: center;
- }
- }
-
- .result-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(0, 0, 0, 0.7);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
-
- .modal-content {
- background: white;
- border-radius: 20rpx;
- padding: 60rpx 40rpx;
- text-align: center;
- box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.3);
- min-width: 500rpx;
-
- .result-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #FF6B6B;
- margin-bottom: 40rpx;
- }
-
- .result-prize {
- margin-bottom: 30rpx;
-
- .result-icon {
- font-size: 60rpx;
- display: block;
- margin-bottom: 20rpx;
- }
-
- .result-name {
- font-size: 32rpx;
- color: #333;
- display: block;
- margin-bottom: 10rpx;
- }
-
- .result-value {
- font-size: 36rpx;
- color: #FF6B6B;
- font-weight: bold;
- }
- }
-
- .points-change {
- margin-bottom: 40rpx;
- padding: 20rpx;
- background: #f5f5f5;
- border-radius: 10rpx;
-
- .change-text {
- font-size: 24rpx;
- color: #666;
- line-height: 1.4;
- }
- }
-
- .confirm-btn {
- background: linear-gradient(45deg, #FF6B6B, #FF4757);
- color: white;
- border: none;
- border-radius: 40rpx;
- padding: 20rpx 60rpx;
- font-size: 28rpx;
- font-weight: bold;
- }
- }
- }
-
- .spin-info {
- text-align: center;
- color: white;
- font-size: 28rpx;
- background: rgba(255, 255, 255, 0.2);
- padding: 20rpx 40rpx;
- border-radius: 40rpx;
- backdrop-filter: blur(10rpx);
- }
- </style>
|