代码:
<template>
<view class="lottery-page">
<!-- 背景装饰 -->
<view class="bg-decoration">
<view class="star" v-for="i in 20" :key="i" :style="getStarStyle(i)"></view>
</view>
<!-- 用户登录状态 -->
<view class="user-status" @click="handleUserClick">
<!-- 已登录:显示头像 -->
<view class="user-avatar" v-if="isLoggedIn">
<image v-if="userInfo.avatar" class="avatar-img" :src="$api.loadFile(userInfo.avatar)" mode="aspectFill"></image>
<view v-else class="avatar-default">
<text class="cuIcon-peoplefill"></text>
</view>
<view class="online-dot"></view>
</view>
<!-- 未登录:显示登录按钮 -->
<view class="login-btn" v-else>
<text class="login-icon">🔐</text>
<text class="login-text">登录</text>
</view>
</view>
<!-- 顶部信息栏 -->
<view class="header">
<view class="title">{{ activity.title || '幸运大转盘' }}</view>
<view class="subtitle">{{ activity.description || '转动转盘,赢取惊喜大奖!' }}</view>
<view class="remaining" v-if="activity.remainingTimes !== undefined">
剩余抽奖次数:<text class="count">{{ activity.remainingTimes }}</text> 次
</view>
</view>
<!-- 中奖滚动 -->
<view class="winner-marquee" v-if="winners.length > 0">
<view class="marquee-icon">🎉</view>
<swiper class="marquee-swiper" vertical autoplay circular :interval="2500" :duration="500">
<swiper-item v-for="(item, index) in winners" :key="index">
<view class="winner-item">
<text class="winner-name">{{ maskName(item.userName) }}</text>
<text class="winner-text">获得了</text>
<text class="winner-prize">{{ item.prizeName }}</text>
</view>
</swiper-item>
</swiper>
</view>
<!-- 大转盘 -->
<view class="wheel-container">
<!-- 外圈装饰灯 -->
<view class="wheel-lights">
<view class="light" v-for="i in 16" :key="i" :class="{ active: lightActive === i % 2 }"
:style="getLightStyle(i)">
</view>
</view>
<!-- 转盘主体 -->
<view class="wheel-wrapper">
<view class="wheel" :style="{ transform: `rotate(${wheelRotation}deg)` }">
<!-- 奖品扇形区域 -->
<view class="prize-sector" v-for="(prize, index) in prizes" :key="prize.id"
:style="getSectorStyle(index)">
<view class="prize-content" :style="getPrizeContentStyle(index)">
<image v-if="prize.image" class="prize-image" :src="$api.loadFile(prize.image)"
mode="aspectFit"></image>
<view v-else class="prize-icon">{{ getPrizeIcon(prize.prizeType) }}</view>
<text class="prize-name">{{ prize.name }}</text>
</view>
</view>
</view>
<!-- 中心抽奖按钮 -->
<view class="wheel-pointer" @click="handleDraw" :class="{ disabled: isSpinning }">
<view class="pointer-arrow"></view>
<view class="pointer-btn">
<text>{{ isSpinning ? '抽奖中' : '抽奖' }}</text>
</view>
</view>
</view>
</view>
<!-- 活动规则 -->
<view class="rules-section">
<view class="rules-title" @click="showRules = !showRules">
<text>📋 活动规则</text>
<text class="arrow">{{ showRules ? '▲' : '▼' }}</text>
</view>
<view class="rules-content" v-show="showRules">
<rich-text :nodes="activity.ruleText || defaultRules"></rich-text>
</view>
</view>
<!-- 我的奖品按钮 -->
<view class="my-prizes-btn" @click="showMyPrizes = true">
🎁 我的奖品
</view>
<!-- ========== 弹窗区域 ========== -->
<!-- 抽奖结果弹窗 -->
<view class="popup-mask" v-if="showResult" @click="closeResult">
<view class="result-popup" :class="resultData.isWin ? 'win' : 'lose'" @click.stop>
<view class="result-icon">{{ resultData.isWin ? '🎊' : '😢' }}</view>
<view class="result-title">{{ resultData.isWin ? '恭喜中奖!' : '很遗憾' }}</view>
<!-- 中奖内容展示 -->
<view class="result-prize" v-if="resultData.isWin">
<image v-if="resultData.prizeImage" class="prize-img"
:src="$api.loadFile(resultData.prizeImage)" mode="aspectFit"></image>
<!-- 现金红包特殊显示 -->
<view v-if="resultData.prizeType === 'Cash'" class="cash-prize">
<text class="cash-icon">🧧</text>
<text class="cash-amount">¥{{ resultData.cashAmount.toFixed(2) }}</text>
<text class="cash-label">现金红包</text>
</view>
<!-- 普通奖品 -->
<text v-else class="prize-text">{{ resultData.prizeName }}</text>
</view>
<!-- 现金红包提示消息 -->
<view class="cash-message" v-if="resultData.prizeType === 'Cash' && resultData.cashMessage">
{{ resultData.cashMessage }}
</view>
<view class="result-tip" v-if="!resultData.isWin">谢谢参与,下次加油!</view>
<!-- 实物奖品需要填地址 -->
<view class="address-form" v-if="resultData.isWin && isPhysicalPrize(resultData.prizeType)">
<view class="form-title">请填写收货信息</view>
<input class="form-input" v-model="addressForm.receiverName" placeholder="收货人姓名" />
<input class="form-input" v-model="addressForm.receiverPhone" placeholder="手机号码" type="tel" />
<textarea class="form-textarea" v-model="addressForm.receiverAddress"
placeholder="详细收货地址"></textarea>
<button class="submit-btn" @click="handleSubmitAddress">提交地址</button>
</view>
<!-- 现金红包:立即领取按钮 -->
<button class="receive-cash-btn" v-else-if="isCashPrizeNeedReceive" @click="handleReceiveCash">
💰 立即领取 ¥{{ resultData.cashAmount.toFixed(2) }}
</button>
<!-- 普通关闭按钮 -->
<button class="close-btn" @click="closeResult" v-else>
{{ resultData.isWin ? '太棒了' : '再试一次' }}
</button>
</view>
</view>
<!-- 我的奖品弹窗 -->
<view class="popup-mask" v-if="showMyPrizes" @click="showMyPrizes = false">
<view class="prizes-popup" @click.stop>
<view class="popup-header">
<text class="popup-title">🎁 我的奖品</text>
<text class="popup-close" @click="showMyPrizes = false">✕</text>
</view>
<scroll-view class="prizes-list" scroll-y>
<view class="prize-item" v-for="item in myPrizes" :key="item.id">
<image class="item-image" :src="$api.loadFile(item.prizeImage)" mode="aspectFit"></image>
<view class="item-info">
<text class="item-name">{{ item.prizeName }}</text>
<text class="item-time">{{ formatTime(item.addTime) }}</text>
<text class="item-status" :class="'status-' + item.deliveryStatus">
{{ item.deliveryStatusName || '待发货' }}
</text>
</view>
</view>
<view class="empty-tip" v-if="myPrizes.length === 0">
暂无中奖记录,快去抽奖吧!
</view>
</scroll-view>
</view>
</view>
<!-- 加载中 -->
<view class="loading-mask" v-if="loading">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
computed,
getCurrentInstance,
onMounted,
onUnmounted
} from 'vue'
import {
onLoad,
onShow
} from '@dcloudio/uni-app'
import {
getActivityDetail,
getWinners,
doDraw,
getLotteryLogs,
submitAddress,
PrizeType
} from '/common/api/lottery.js'
// ========== 配置 ==========
const ACTIVITY_ID = 1 // 活动ID
// ========== 获取全局属性 ==========
const {
proxy
} = getCurrentInstance()
// ========== 用户登录状态 ==========
const isLoggedIn = ref(false) // 是否已登录
const userInfo = ref({}) // 用户信息
/**
* 检查登录状态并获取用户信息
*/
const checkLoginStatus = () => {
const token = uni.getStorageSync('dtcms_client_token')
isLoggedIn.value = !!(token && token.accessToken)
// 已登录时获取用户信息
if (isLoggedIn.value) {
fetchUserInfo()
}
}
/**
* 获取用户信息(头像等)
*/
const fetchUserInfo = () => {
proxy.$api.request({
url: '/account/member/info',
success(res) {
userInfo.value = res.data || {}
}
})
}
/**
* 点击用户头像/登录按钮
*/
const handleUserClick = () => {
if (isLoggedIn.value) {
// 已登录:跳转会员中心
uni.navigateTo({
url: '/pages/account/index'
})
} else {
// 未登录:跳转登录页
uni.navigateTo({
url: '/pages/account/login'
})
}
}
// ========== 响应式数据 ==========
const loading = ref(true) // 加载状态
const activity = ref({}) // 活动详情
const prizes = ref([]) // 奖品列表
const winners = ref([]) // 中奖记录
const myPrizes = ref([]) // 我的奖品
const isSpinning = ref(false) // 是否正在抽奖
const wheelRotation = ref(0) // 转盘旋转角度
const lightActive = ref(0) // 灯光闪烁状态
const showResult = ref(false) // 显示结果弹窗
const showRules = ref(false) // 显示规则
const showMyPrizes = ref(false) // 显示我的奖品
// 抽奖结果数据
const resultData = ref({
isWin: false,
prizeName: '',
prizeImage: '',
prizeType: 0,
drawLogId: 0,
// ===== 现金红包相关 =====
cashAmount: 0, // 红包金额
cashStatus: 0, // 发放状态(1=已发放待领取)
cashMessage: '', // 提示消息
packageInfo: '' // 微信package_info(用于调起确认收款)
})
// 地址表单
const addressForm = reactive({
receiverName: '',
receiverPhone: '',
receiverAddress: ''
})
// 默认规则
const defaultRules = `
<p>1. 每位用户每天可抽奖3次</p>
<p>2. 中奖后请在7日内填写收货信息</p>
<p>3. 奖品以实际发放为准</p>
<p>4. 活动最终解释权归主办方所有</p>
`
// 灯光闪烁定时器
let lightTimer = null
// ========== 计算属性 ==========
// 每个奖品的角度
const anglePerPrize = computed(() => {
return prizes.value.length > 0 ? 360 / prizes.value.length : 45
})
// ========== 方法 ==========
/**
* 初始化数据
*/
const initData = async () => {
loading.value = true
try {
// 并行请求活动详情和中奖记录
const [activityData, winnersData] = await Promise.all([
getActivityDetail(proxy, ACTIVITY_ID),
getWinners(proxy, ACTIVITY_ID, 10)
])
activity.value = activityData || {}
prizes.value = activityData?.prizes || []
winners.value = winnersData || []
// 按position排序奖品
prizes.value.sort((a, b) => a.position - b.position)
console.log('活动详情:', activityData)
console.log('奖品列表:', prizes.value)
} catch (err) {
console.error('加载活动失败:', err)
uni.showToast({
title: '加载活动失败',
icon: 'none'
})
} finally {
loading.value = false
}
}
/**
* 加载我的奖品
*/
const loadMyPrizes = async () => {
try {
const data = await getLotteryLogs(proxy, ACTIVITY_ID, 1, 20)
// 只显示中奖记录
myPrizes.value = (data || []).filter(item => item.isWin)
} catch (err) {
console.error('加载奖品失败:', err)
}
}
/**
* 点击抽奖
*/
const handleDraw = async () => {
// 防止重复点击
if (isSpinning.value) return
// 检查剩余次数
if (activity.value.remainingTimes !== undefined && activity.value.remainingTimes <= 0) {
uni.showToast({
title: '抽奖次数不足',
icon: 'none'
})
return
}
isSpinning.value = true
try {
// 调用抽奖API
const result = await doDraw(proxy, ACTIVITY_ID)
console.log('抽奖结果:', result)
// 找到中奖奖品的位置
let targetIndex = 0
if (result && result.prizeId) {
targetIndex = prizes.value.findIndex(p => p.id === result.prizeId)
if (targetIndex < 0) targetIndex = 0
}
// 计算目标角度(转5圈 + 目标位置)
// 注意:转盘顺时针转,奖品逆时针排列
const baseRotation = 360 * 5 // 转5圈
const targetAngle = (prizes.value.length - targetIndex - 1) * anglePerPrize.value + anglePerPrize.value / 2
const finalRotation = wheelRotation.value + baseRotation + targetAngle - (wheelRotation.value % 360)
// 播放转盘动画
wheelRotation.value = finalRotation
// 等待动画结束(4秒)
setTimeout(() => {
isSpinning.value = false
// 设置结果数据
resultData.value = {
isWin: result?.isWin || false,
prizeName: result?.prizeName || '谢谢参与',
prizeImage: result?.prizeImage || '',
prizeType: result?.prizeType || 0,
drawLogId: result?.drawLogId || result?.id || 0,
// ===== 现金红包相关 =====
cashAmount: result?.cashAmount || 0,
cashStatus: result?.cashStatus || 0,
cashMessage: result?.cashMessage || '',
packageInfo: result?.packageInfo || ''
}
// 显示结果弹窗
showResult.value = true
// 更新剩余次数
if (activity.value.remainingTimes !== undefined) {
activity.value.remainingTimes = Math.max(0, activity.value.remainingTimes - 1)
}
}, 4000)
} catch (err) {
console.error('抽奖失败:', err)
isSpinning.value = false
uni.showToast({
title: err.message || '抽奖失败,请重试',
icon: 'none'
})
}
}
/**
* 提交收货地址
*/
const handleSubmitAddress = async () => {
// 表单验证
if (!addressForm.receiverName) {
uni.showToast({
title: '请输入收货人姓名',
icon: 'none'
})
return
}
if (!/^1[3-9]\d{9}$/.test(addressForm.receiverPhone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return
}
if (!addressForm.receiverAddress) {
uni.showToast({
title: '请输入收货地址',
icon: 'none'
})
return
}
try {
await submitAddress(proxy, {
drawLogId: resultData.value.drawLogId,
receiverName: addressForm.receiverName,
receiverPhone: addressForm.receiverPhone,
receiverAddress: addressForm.receiverAddress
})
uni.showToast({
title: '地址提交成功',
icon: 'success'
})
showResult.value = false
// 清空表单
addressForm.receiverName = ''
addressForm.receiverPhone = ''
addressForm.receiverAddress = ''
} catch (err) {
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
})
}
}
/**
* 关闭结果弹窗
*/
const closeResult = () => {
showResult.value = false
}
// ========== 现金红包领取功能 ==========
/**
* 判断是否为现金红包且需要领取
* 条件:中奖 + 现金红包类型 + 有packageInfo
*/
const isCashPrizeNeedReceive = computed(() => {
return resultData.value.isWin &&
resultData.value.prizeType === 'Cash' &&
resultData.value.packageInfo
})
/**
* 立即领取现金红包
* 微信公众号H5环境 - 严格按照官方示例
* 参考:https://pay.weixin.qq.com/doc/v3/merchant/4012716430
*/
const handleReceiveCash = () => {
const { packageInfo, cashAmount } = resultData.value
// 检查packageInfo是否存在
if (!packageInfo) {
uni.showToast({
title: '红包信息异常',
icon: 'none'
})
return
}
// 检查微信环境
if (typeof WeixinJSBridge === 'undefined') {
uni.showToast({
title: '请在微信中打开',
icon: 'none'
})
return
}
// 实际调用转账的函数
const doInvokeTransfer = () => {
console.log('调用 WeixinJSBridge.invoke requestMerchantTransfer')
WeixinJSBridge.invoke('requestMerchantTransfer', {
mchId: '131865', // 商户号
appId: 'wx0ererrf886b32b4349', // 公众号AppID
package: packageInfo // 后端返回的package信息
}, function(res) {
console.log('WeixinJSBridge 响应:', res)
// 官方示例使用 err_msg (下划线)
if (res.err_msg === 'requestMerchantTransfer:ok') {
uni.showToast({
title: `¥${cashAmount.toFixed(2)} 领取成功!`,
icon: 'success'
})
showResult.value = false
} else {
// 显示具体错误信息便于调试
const errMsg = res.err_msg || ''
console.error('领取失败:', errMsg)
let tip = '领取失败,请稍后重试'
if (errMsg.includes('offline verifying')) {
tip = '微信侧正在核验权限,请稍后再试'
} else if (errMsg.includes('cancel')) {
tip = '您取消了领取'
}
uni.showToast({
title: tip,
icon: 'none',
duration: 3000
})
}
})
}
// 官方标准流程:先检查API可用性
if (typeof wx !== 'undefined' && typeof wx.checkJsApi === 'function') {
wx.checkJsApi({
jsApiList: ['requestMerchantTransfer'],
success: function(res) {
console.log('checkJsApi 结果:', res)
if (res.checkResult && res.checkResult.requestMerchantTransfer) {
// API可用,执行调用
doInvokeTransfer()
} else {
uni.showToast({
title: '您的微信版本过低,请更新至最新版本',
icon: 'none',
duration: 3000
})
}
},
fail: function(err) {
console.warn('checkJsApi 失败,降级直接调用:', err)
// 降级:直接尝试调用
doInvokeTransfer()
}
})
} else {
// wx对象不存在或无checkJsApi方法,直接调用
console.log('wx.checkJsApi 不可用,直接调用')
doInvokeTransfer()
}
}
/**
* 获取扇形区域样式
*/
const getSectorStyle = (index) => {
const angle = anglePerPrize.value
const rotation = index * angle
const skewAngle = 90 - angle
return {
transform: `rotate(${rotation}deg) skewY(${skewAngle}deg)`,
backgroundColor: index % 2 === 0 ? '#FFE4B5' : '#FFF8DC'
}
}
/**
* 获取奖品内容样式
*/
const getPrizeContentStyle = (index) => {
const angle = anglePerPrize.value
const skewAngle = 90 - angle
return {
transform: `skewY(${-skewAngle}deg) rotate(${angle / 2}deg)`
}
}
/**
* 获取装饰灯样式
*/
const getLightStyle = (index) => {
const angle = (index - 1) * (360 / 16)
return {
transform: `rotate(${angle}deg) translateY(-140px)`
}
}
/**
* 获取星星装饰样式
*/
const getStarStyle = (index) => {
return {
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 2}s`,
fontSize: `${12 + Math.random() * 10}rpx`
}
}
/**
* 获取奖品图标(支持字符串和数字类型)
*/
const getPrizeIcon = (prizeType) => {
// 字符串类型映射
const stringIcons = {
'Consolation': '😢', // 谢谢参与
'Physical': '🎁', // 实物
'Points': '💰', // 积分
'Coupon': '🎫', // 优惠券
'Cash': '🧧', // 现金红包
'Virtual': '🎮' // 虚拟物品
}
// 数字类型映射(兼容旧版)
const numberIcons = {
0: '😢', 1: '🎁', 2: '💰', 3: '🎫', 4: '🧧', 5: '🎮'
}
return stringIcons[prizeType] || numberIcons[prizeType] || '🎁'
}
/**
* 判断是否为实物奖品(需要填写地址)
*/
const isPhysicalPrize = (prizeType) => {
return prizeType === 'Physical' || prizeType === 1
}
/**
* 脱敏用户名
*/
const maskName = (name) => {
if (!name) return '匿名用户'
if (name.length <= 2) return name[0] + '*'
return name[0] + '**' + name[name.length - 1]
}
/**
* 格式化时间
*/
const formatTime = (timeStr) => {
if (!timeStr) return ''
const date = new Date(timeStr)
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
}
/**
* 开启灯光闪烁
*/
const startLightFlash = () => {
lightTimer = setInterval(() => {
lightActive.value = lightActive.value === 0 ? 1 : 0
}, 500)
}
// ========== 生命周期 ==========
onLoad(() => {
checkLoginStatus() // 检查登录状态
initData()
startLightFlash()
})
onShow(() => {
// 页面显示时刷新登录状态(登录后返回时更新)
checkLoginStatus()
})
onMounted(() => {
// 打开我的奖品弹窗时加载数据
})
onUnmounted(() => {
if (lightTimer) {
clearInterval(lightTimer)
}
})
// 监听打开我的奖品
const openMyPrizes = () => {
loadMyPrizes()
showMyPrizes.value = true
}
</script>
<style lang="scss">
.lottery-page {
min-height: 100vh;
background: linear-gradient(180deg, #FF6B6B 0%, #C41E3A 50%, #8B0000 100%);
padding: 30rpx;
position: relative;
overflow: hidden;
}
/* 用户登录状态 */
.user-status {
position: absolute;
top: 40rpx;
left: 30rpx;
z-index: 100;
.user-avatar {
position: relative;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #FFF;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.25);
overflow: visible;
.avatar-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar-default {
width: 100%;
height: 100%;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 44rpx;
color: #FFF;
}
}
// 在线小绿点
.online-dot {
position: absolute;
bottom: 2rpx;
right: 2rpx;
width: 20rpx;
height: 20rpx;
background: #52C41A;
border-radius: 50%;
border: 3rpx solid #FFF;
}
}
.login-btn {
background: rgba(255, 255, 255, 0.95);
border-radius: 40rpx;
padding: 14rpx 28rpx;
display: flex;
align-items: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);
.login-icon {
font-size: 28rpx;
margin-right: 10rpx;
}
.login-text {
font-size: 26rpx;
color: #C41E3A;
font-weight: bold;
}
}
}
/* 背景装饰 */
.bg-decoration {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 0;
.star {
position: absolute;
color: rgba(255, 255, 255, 0.6);
animation: twinkle 2s ease-in-out infinite;
&::before {
content: '✦';
}
}
}
@keyframes twinkle {
0%,
100% {
opacity: 0.3;
}
50% {
opacity: 1;
}
}
/* 顶部信息 */
.header {
text-align: center;
padding: 40rpx 0;
position: relative;
z-index: 1;
.title {
font-size: 48rpx;
font-weight: bold;
color: #FFD700;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 28rpx;
color: #FFF;
margin-top: 16rpx;
opacity: 0.9;
}
.remaining {
margin-top: 24rpx;
font-size: 28rpx;
color: #FFF;
.count {
font-size: 40rpx;
font-weight: bold;
color: #FFD700;
}
}
}
/* 中奖滚动 */
.winner-marquee {
background: rgba(255, 255, 255, 0.15);
border-radius: 40rpx;
padding: 16rpx 30rpx;
margin: 20rpx 0;
display: flex;
align-items: center;
position: relative;
z-index: 1;
.marquee-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.marquee-swiper {
flex: 1;
height: 40rpx;
}
.winner-item {
font-size: 26rpx;
color: #FFF;
line-height: 40rpx;
.winner-name {
color: #FFD700;
}
.winner-prize {
color: #FFD700;
font-weight: bold;
}
}
}
/* 转盘容器 */
.wheel-container {
position: relative;
width: 600rpx;
height: 600rpx;
margin: 40rpx auto;
z-index: 1;
}
/* 装饰灯 */
.wheel-lights {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
.light {
position: absolute;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: #FFF;
left: 50%;
top: 50%;
margin-left: -10rpx;
margin-top: -10rpx;
transition: all 0.3s;
&.active {
background: #FFD700;
box-shadow: 0 0 10px #FFD700;
}
}
}
/* 转盘主体 */
.wheel-wrapper {
position: absolute;
width: 540rpx;
height: 540rpx;
top: 30rpx;
left: 30rpx;
border-radius: 50%;
background: #FFF;
box-shadow: 0 0 30rpx rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.wheel {
width: 100%;
height: 100%;
border-radius: 50%;
position: relative;
transition: transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99);
}
/* 奖品扇形 */
.prize-sector {
position: absolute;
width: 50%;
height: 50%;
top: 0;
right: 0;
transform-origin: bottom left;
overflow: hidden;
}
.prize-content {
position: absolute;
left: -100%;
width: 200%;
height: 200%;
text-align: center;
padding-top: 30rpx;
.prize-image {
width: 60rpx;
height: 60rpx;
}
.prize-icon {
font-size: 48rpx;
}
.prize-name {
display: block;
font-size: 20rpx;
color: #333;
margin-top: 8rpx;
max-width: 100rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
/* 中心按钮 */
.wheel-pointer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
&.disabled {
pointer-events: none;
opacity: 0.7;
}
.pointer-arrow {
width: 0;
height: 0;
border-left: 30rpx solid transparent;
border-right: 30rpx solid transparent;
border-bottom: 50rpx solid #C41E3A;
position: absolute;
top: -45rpx;
left: 50%;
transform: translateX(-50%);
}
.pointer-btn {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background: linear-gradient(180deg, #FFD700 0%, #FFA500 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
border: 6rpx solid #C41E3A;
text {
font-size: 28rpx;
font-weight: bold;
color: #C41E3A;
}
}
}
/* 活动规则 */
.rules-section {
background: rgba(255, 255, 255, 0.15);
border-radius: 20rpx;
margin-top: 40rpx;
overflow: hidden;
position: relative;
z-index: 1;
.rules-title {
padding: 24rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
color: #FFF;
font-size: 28rpx;
.arrow {
font-size: 24rpx;
}
}
.rules-content {
padding: 0 30rpx 24rpx;
color: rgba(255, 255, 255, 0.9);
font-size: 26rpx;
line-height: 1.8;
}
}
/* 我的奖品按钮 */
.my-prizes-btn {
position: fixed;
right: 0;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(90deg, #FFD700, #FFA500);
color: #8B0000;
font-size: 26rpx;
font-weight: bold;
padding: 20rpx 24rpx;
border-radius: 40rpx 0 0 40rpx;
box-shadow: -4rpx 0 20rpx rgba(0, 0, 0, 0.2);
z-index: 100;
}
/* 弹窗蒙层 */
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
/* 结果弹窗 */
.result-popup {
width: 600rpx;
background: #FFF;
border-radius: 30rpx;
padding: 50rpx 40rpx;
text-align: center;
&.win {
background: linear-gradient(180deg, #FFF9E6 0%, #FFF 100%);
}
.result-icon {
font-size: 100rpx;
}
.result-title {
font-size: 40rpx;
font-weight: bold;
color: #333;
margin: 20rpx 0;
}
.result-prize {
.prize-img {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
}
.prize-text {
display: block;
font-size: 32rpx;
color: #C41E3A;
font-weight: bold;
margin-top: 16rpx;
}
}
.result-tip {
font-size: 28rpx;
color: #666;
margin: 20rpx 0;
}
.close-btn {
margin-top: 30rpx;
background: linear-gradient(90deg, #FF6B6B, #C41E3A);
color: #FFF;
border: none;
border-radius: 40rpx;
font-size: 30rpx;
height: 80rpx;
line-height: 80rpx;
}
}
/* 现金红包样式 */
.cash-prize {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx 0;
.cash-icon {
font-size: 80rpx;
margin-bottom: 16rpx;
}
.cash-amount {
font-size: 56rpx;
font-weight: bold;
color: #C41E3A;
}
.cash-label {
font-size: 28rpx;
color: #666;
margin-top: 8rpx;
}
}
.cash-message {
font-size: 26rpx;
color: #FF6B6B;
text-align: center;
padding: 20rpx;
background: #FFF5F5;
border-radius: 12rpx;
margin: 20rpx 0;
}
/* 立即领取按钮 */
.receive-cash-btn {
margin-top: 30rpx;
background: linear-gradient(90deg, #FF4D4F, #C41E3A);
color: #FFF;
border: none;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: bold;
height: 88rpx;
line-height: 88rpx;
box-shadow: 0 8rpx 20rpx rgba(196, 30, 58, 0.4);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
}
/* 地址表单 */
.address-form {
margin-top: 30rpx;
text-align: left;
.form-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 20rpx;
text-align: center;
}
.form-input {
width: 100%;
height: 80rpx;
border: 2rpx solid #DDD;
border-radius: 12rpx;
padding: 0 24rpx;
margin-bottom: 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.form-textarea {
width: 100%;
height: 160rpx;
border: 2rpx solid #DDD;
border-radius: 12rpx;
padding: 20rpx 24rpx;
margin-bottom: 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.submit-btn {
background: linear-gradient(90deg, #FFD700, #FFA500);
color: #8B0000;
border: none;
border-radius: 40rpx;
font-size: 30rpx;
font-weight: bold;
height: 80rpx;
line-height: 80rpx;
}
}
/* 我的奖品弹窗 */
.prizes-popup {
width: 650rpx;
max-height: 80vh;
background: #FFF;
border-radius: 30rpx;
overflow: hidden;
.popup-header {
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2rpx solid #EEE;
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popup-close {
font-size: 36rpx;
color: #999;
}
}
.prizes-list {
max-height: 60vh;
padding: 20rpx;
}
.prize-item {
display: flex;
padding: 20rpx;
border-bottom: 2rpx solid #F5F5F5;
.item-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
background: #F5F5F5;
}
.item-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: center;
.item-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.item-time {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.item-status {
font-size: 24rpx;
margin-top: 8rpx;
&.status-0 {
color: #FFA500;
}
&.status-1 {
color: #1890FF;
}
&.status-2 {
color: #52C41A;
}
}
}
}
.empty-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
}
/* 加载中 */
.loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 2000;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 6rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #FFF;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
color: #FFF;
font-size: 28rpx;
margin-top: 20rpx;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
