qs_xinchun2026_h5/components/LuckyDraw.vue

630 lines
14 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="lucky-draw-container">
<!-- 背景图 -->
<div class="background">
<img
v-lazy="backgroundImage"
alt="抽奖背景"
@error="handleImageError"
/>
</div>
<!-- 抽奖标题 -->
<h2 class="title">新春抽奖活动</h2>
<!-- 抽奖轮盘 -->
<div class="lottery-wheel-container">
<div
class="lottery-wheel"
:class="{ 'rotating': spinning }"
:style="wheelStyle"
>
<div
v-for="(prize, index) in prizes"
:key="prize.id"
class="prize-segment"
:style="getSegmentStyle(index)"
>
<span class="prize-name">{{ prize.name }}</span>
<span class="prize-content">{{ prize.prize }}</span>
</div>
<div class="wheel-center">
<div
class="draw-button"
@click="handleDrawClick"
:disabled="spinning || !userInfoComplete"
>
<span class="draw-text">{{ spinning ? '抽奖中...' : '开始抽奖' }}</span>
</div>
</div>
</div>
</div>
<!-- 抽奖信息 -->
<div class="lottery-info">
<p class="remaining-draws">剩余抽奖次数: {{ remainingDraws }}</p>
<p class="rules-link" @click="showRules = !showRules">查看活动规则 ▼</p>
</div>
<!-- 活动规则弹窗 -->
<uni-popup v-model="showRules" position="center" :style="{ width: '90%' }">
<div class="rules-content">
<h3>活动规则</h3>
<div class="rules-list">
<p>1. 活动时间2026年2月1日-2026年2月15日</p>
<p>2. 每位用户仅有一次抽奖机会</p>
<p>3. 中奖后请在7天内领取奖品逾期失效</p>
<p>4. 实物奖品将在活动结束后7个工作日内发放</p>
<p>5. 本活动最终解释权归主办方所有</p>
</div>
<button class="primary-button" @click="showRules = false">我知道了</button>
</div>
</uni-popup>
<!-- 用户信息表单 -->
<uni-popup v-model="showInfoForm" position="center" :style="{ width: '90%' }">
<div class="form-content">
<h3>填写信息参与抽奖</h3>
<form @submit.prevent="onSubmit">
<div class="form-item">
<label class="form-label">姓名</label>
<input
v-model="userInfo.name"
name="name"
type="text"
placeholder="请输入姓名"
class="form-input"
required
/>
</div>
<div class="form-item">
<label class="form-label">手机号</label>
<input
v-model="userInfo.phone"
name="phone"
type="tel"
placeholder="请输入手机号"
class="form-input"
pattern="^1[3-9]\d{9}$"
required
/>
</div>
<div class="form-item">
<label class="form-label">收货地址</label>
<textarea
v-model="userInfo.address"
name="address"
placeholder="请输入收货地址"
class="form-textarea"
rows="3"
required
></textarea>
</div>
<div style="margin: 16px;">
<button type="submit" class="primary-button" block>提交</button>
</div>
</form>
</div>
</uni-popup>
<!-- 抽奖结果弹窗 -->
<uni-popup v-model="showResult" position="center" :style="{ width: '90%' }">
<div class="result-content">
<div v-if="lotteryResult.won" class="win-result">
<div class="success-icon">✓</div>
<h3 class="result-title">恭喜中奖!</h3>
<p class="result-prize">{{ lotteryResult.prize.name }}{{ lotteryResult.prize.prize }}</p>
<button class="primary-button" block @click="handleClaimPrize">领取奖品</button>
</div>
<div v-else class="lose-result">
<div class="cross-icon">✗</div>
<h3 class="result-title">很遗憾</h3>
<p class="result-message">未中奖,感谢参与!</p>
<button class="default-button" block @click="showResult = false">关闭</button>
</div>
</div>
</uni-popup>
<!-- 领取成功弹窗 -->
<uni-popup v-model="showClaimSuccess" position="center" :style="{ width: '90%' }">
<div class="success-content">
<div class="success-icon">✓</div>
<h3>领取成功!</h3>
<p>您的奖品将在7个工作日内发放请耐心等待。</p>
<button class="primary-button" block @click="showClaimSuccess = false">确定</button>
</div>
</uni-popup>
<!-- 加载动画 -->
<div v-if="loading" class="loading-overlay" @touchmove="handleTouchMove">
<div class="custom-loading"></div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue';
import { submitLotteryInfo, drawLottery, claimPrize } from '../api/lottery';
// 响应式数据
const spinning = ref(false);
const loading = ref(false);
const showRules = ref(false);
const showInfoForm = ref(false);
const showResult = ref(false);
const showClaimSuccess = ref(false);
const backgroundImage = ref('https://placeholder.pics/svg/640x1136/FDF5E6/CD853F/抽奖背景');
const remainingDraws = ref(1); // 默认每人1次抽奖机会
const rotationAngle = ref(0);
// 奖项设置
const prizes = ref([
{ id: 1, name: '一等奖', prize: 'iPhone 15' },
{ id: 2, name: '二等奖', prize: 'AirPods Pro' },
{ id: 3, name: '三等奖', prize: '京东购物卡200元' },
{ id: 4, name: '四等奖', prize: '品牌充电宝' },
{ id: 5, name: '参与奖', prize: '新春福袋' }
]);
// 用户信息
const userInfo = reactive({
name: '',
phone: '',
address: ''
});
// 抽奖结果
const lotteryResult = reactive({
won: false,
prize: null
});
// 计算属性
const userInfoComplete = computed(() => {
return userInfo.name && userInfo.phone && userInfo.address;
});
const wheelStyle = computed(() => {
return {
transform: `rotate(${rotationAngle.value}deg)`
};
});
// 方法
const getSegmentStyle = (index) => {
const segments = prizes.value.length;
const angle = 360 / segments;
const rotate = angle * index;
const isEven = index % 2 === 0;
return {
background: isEven ? '#ffd700' : '#ffed4e',
transform: `rotate(${rotate}deg)`
};
};
const handleDrawClick = () => {
if (!userInfoComplete.value) {
showInfoForm.value = true;
return;
}
if (remainingDraws.value <= 0) {
uni.uni.showToast({title: '抽奖次数已用完', duration: 2000});
return;
}
executeDraw();
};
const onSubmit = async () => {
loading.value = true;
try {
const result = await submitLotteryInfo(userInfo);
if (result.code === 200) {
uni.uni.showToast({title: '信息提交成功', duration: 2000});
showInfoForm.value = false;
executeDraw();
} else {
uni.uni.showToast({title: '信息提交失败,请重试', duration: 2000});
}
} catch (error) {
console.error('提交信息失败:', error);
uni.uni.showToast({title: '网络异常,请稍后重试', duration: 2000});
} finally {
loading.value = false;
}
};
const executeDraw = async () => {
spinning.value = true;
try {
// 先旋转几圈
const initialRotation = rotationAngle.value;
const extraRotations = 5;
const finalRotation = initialRotation + extraRotations * 360;
// 调用抽奖API
const result = await drawLottery('user123');
if (result.code === 200) {
if (result.data.won) {
// 计算中奖位置
const winningIndex = prizes.value.findIndex(p => p.id === result.data.prize.id);
const segments = prizes.value.length;
const anglePerSegment = 360 / segments;
const offsetAngle = 90 - (winningIndex * anglePerSegment) - (anglePerSegment / 2);
// 最终旋转角度
rotationAngle.value = finalRotation + offsetAngle;
} else {
// 未中奖,随机停止
const randomOffset = Math.random() * 360;
rotationAngle.value = finalRotation + randomOffset;
}
// 更新剩余抽奖次数
remainingDraws.value = result.data.remainingDraws;
// 更新抽奖结果
lotteryResult.won = result.data.won;
lotteryResult.prize = result.data.prize;
// 显示结果
setTimeout(() => {
spinning.value = false;
showResult.value = true;
}, 3000);
} else {
spinning.value = false;
uni.uni.showToast({title: '抽奖失败,请重试', duration: 2000});
}
} catch (error) {
console.error('抽奖失败:', error);
spinning.value = false;
uni.uni.showToast({title: '网络异常,请稍后重试', duration: 2000});
}
};
const handleClaimPrize = async () => {
if (!lotteryResult.prize) return;
loading.value = true;
try {
const result = await claimPrize(lotteryResult.prize.id);
if (result.code === 200) {
showResult.value = false;
showClaimSuccess.value = true;
} else {
uni.uni.showToast({title: '领取失败,请重试', duration: 2000});
}
} catch (error) {
console.error('领取奖品失败:', error);
uni.uni.showToast({title: '网络异常,请稍后重试', duration: 2000});
} finally {
loading.value = false;
}
};
const selectKeyword = (word) => {
keyword.value = word;
};
// 处理图片加载错误
const handleImageError = (event) => {
event.target.src = 'https://placeholder.pics/svg/640x1136/FDF5E6/CD853F/抽奖背景';
};
// 阻止 iOS 滚动穿透
const handleTouchMove = (e) => {
e.preventDefault();
};
</script>
<style scoped>
.lucky-draw-container {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
overflow: hidden;
}
.background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
.background img {
width: 100%;
height: 100%;
object-fit: cover;
}
.title {
font-size: 32px;
color: #e67e22;
margin-bottom: 30px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
font-weight: bold;
}
.lottery-wheel-container {
position: relative;
width: 300px;
height: 300px;
margin: 20px 0;
}
.lottery-wheel {
position: relative;
width: 100%;
height: 100%;
border-radius: 50%;
border: 5px solid #e67e22;
overflow: hidden;
transition: transform 3s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: center center;
}
.prize-segment {
position: absolute;
width: 50%;
height: 50%;
top: 0;
left: 50%;
transform-origin: left bottom;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
padding-left: 20px;
box-sizing: border-box;
}
.prize-name {
font-size: 14px;
font-weight: bold;
color: #8b4513;
margin-bottom: 2px;
}
.prize-content {
font-size: 12px;
color: #8b4513;
}
.wheel-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80px;
height: 80px;
border-radius: 50%;
background: #fff;
border: 3px solid #e67e22;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.draw-button {
width: 70px;
height: 70px;
border-radius: 50%;
background: linear-gradient(135deg, #e67e22, #d35400);
color: white;
border: none;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
}
.draw-button:hover:not(:disabled) {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(230, 126, 34, 0.4);
}
.draw-button:disabled {
background: #ddd;
cursor: not-allowed;
}
.lottery-info {
margin-top: 20px;
text-align: center;
}
.remaining-draws {
font-size: 18px;
color: #e67e22;
font-weight: bold;
margin-bottom: 10px;
}
.rules-link {
font-size: 14px;
color: #3498db;
cursor: pointer;
margin-bottom: 20px;
}
.rules-content {
padding: 20px;
}
.rules-content h3 {
text-align: center;
margin-bottom: 20px;
color: #e67e22;
}
.rules-list p {
margin-bottom: 10px;
font-size: 14px;
color: #666;
line-height: 1.5;
}
.form-content {
padding: 20px;
}
.form-content h3 {
text-align: center;
margin-bottom: 20px;
color: #e67e22;
}
.result-content {
padding: 30px;
text-align: center;
}
.result-title {
font-size: 24px;
margin: 20px 0;
color: #e67e22;
}
.result-prize {
font-size: 18px;
color: #333;
margin-bottom: 20px;
font-weight: bold;
}
.result-message {
font-size: 18px;
color: #999;
margin-bottom: 20px;
}
.success-content {
padding: 30px;
text-align: center;
}
.success-content h3 {
font-size: 24px;
margin: 20px 0;
color: #4CAF50;
}
.success-content p {
font-size: 16px;
color: #666;
margin-bottom: 20px;
line-height: 1.5;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
/* 自定义图标样式 */
.success-icon {
font-size: 64px;
color: #f39c12;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
}
.cross-icon {
font-size: 64px;
color: #999;
text-align: center;
margin-bottom: 20px;
}
/* 自定义按钮样式 */
.primary-button {
background-color: #07c160;
color: white;
border: none;
border-radius: 4px;
padding: 10px 0;
font-size: 16px;
font-weight: bold;
cursor: pointer;
width: 100%;
margin-top: 16px;
}
.default-button {
background-color: #f7f8fa;
color: #323233;
border: 1px solid #ebedf0;
border-radius: 4px;
padding: 10px 0;
font-size: 16px;
cursor: pointer;
width: 100%;
margin-top: 16px;
}
/* 自定义加载动画 */
.custom-loading {
width: 48px;
height: 48px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 响应式设计 */
@media (max-width: 480px) {
.title {
font-size: 28px;
}
.lottery-wheel-container {
width: 280px;
height: 280px;
}
.prize-segment {
padding-left: 15px;
}
.prize-name {
font-size: 12px;
}
.prize-content {
font-size: 10px;
}
}
</style>