Files
frontend-app/uni_modules/dengbo-lotteryWheel/components/dengbo-lotteryWheel/dengbo-lotteryWheel.vue
T
2025-08-05 15:25:23 +08:00

536 lines
13 KiB
Vue
Raw 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>
<!-- 幸运轮盘容器 -->
<view class="lucky-wheel-container" :style="containerStyle">
<!-- 轮盘背景 -->
<view class="lucky-wheel-bg">
<slot v-if="$slots && $slots.bg" name="bg" />
</view>
<!-- 轮盘主体 -->
<view class="lucky-wheel" :style="wheelStyle">
<view class="lucky-wheel-wrap">
<!-- 遍历奖项,创建每个扇形区域 -->
<view v-for="(item, index) in prizes" :key="index" class="prize-item"
:style="[getPrizeItemStyle(index)]">
<view class="prize-content" :style="[getCorrectAngle(index)]">
<!-- 扇形背景 -->
<view class="prize-content-bg">
<image v-if="getPrizeBgImage(index)" :src="getPrizeBgImage(index)"
style="width: 100%;height: 100%;" />
</view>
<slot :item="item" :index="index">
<text class="prize-name">{{ item.name }}</text>
<view class="prize-pro">
<image :src="item.iconUrl" style="width: 98rpx;height: 70rpx;"></image>
<!-- <view class="prize-pro-txt">{{item.parValue}}</view> -->
</view>
</slot>
</view>
</view>
</view>
</view>
<!-- 指针 -->
<view class="pointer">
<view v-if="$slots && $slots.pointer" class="pointer-heart">
<slot name="pointer" class="pointer-heart" />
</view>
<view v-else class="pointer-triangle pointer-heart" @click="$emit('run')"></view>
</view>
<!-- 添加闪光灯容器 -->
<view class="lights-container">
<view v-for="(light, index) in lights" :key="index" class="light" :style="[getLightStyle(index)]">
<image v-if="lightImgs.length > 0" :src="getCurrentImage(index)"
style="width: 100%;height: 100%;position: absolute;left: 0;right: 0;" />
</view>
</view>
</view>
</template>
<script>
/**
* LuckyWheel 组件属性定义
* @property {Array} prizes - 奖项列表
* @property {Number} [size=600] - 轮盘大小
* @property {String} [customStyle] - 轮盘自定义样式
* @property {Object} [styleOpt] - 样式选项
* @property {String} [innerStyle="width: 85%;height:85%"] - 内部样式
* @property {Number} [turns=10] - 旋转圈数
* @property {Number} [duration=5000] - 旋转持续时间(毫秒)
* @property {Number} [lightNormalSpeed=1000] - 正常闪光灯速度
* @property {Number} [lighFastSpeed=50] - 快速闪光灯速度
* @property {String} [lightSize="26rpx"] - 闪光灯大小
* @property {Array} [lightColors=['#FF0000', '#FFFF00']] - 闪光灯颜色列表
* @property {Array} [lightImgs=[]] - 闪光灯图片列表
* @property {Array} [prizeBgImages=[]] - 扇形背景图数组
*/
export default {
name: 'LuckyWheel',
props: {
prizes: {
type: Array,
required: true,
},
size: {
type: Number,
default: 600,
},
customStyle: {
type: String,
},
styleOpt: {
type: Object,
default: () => ({
prizeBgColors: ['#fff0a3', '#fffce6','#fdba64'],
borderColor: '#ffd752',
})
},
innerStyle: {
type: String,
default: `width: 85%;height:85%`
},
turns: {
type: Number,
default: 10
},
duration: {
type: Number,
default: 5000,
},
lightNormalSpeed: {
type: Number,
default: 1000
},
lighFastSpeed: {
type: Number,
default: 50
},
lightSize: {
type: String,
default: `26rpx`
},
lightColors: {
type: Array,
default: () => ['#FF0000', '#FFFF00']
},
lightImgs: {
type: Array,
default: () => []
},
prizeBgImages: {
type: Array,
default: () => []
},
},
data() {
return {
rotating: false, // 是否正在旋转
rotationAngle: 0, // 当前旋转角度
// 设置指针默认指向的位置,现在是默认指向2个扇形之间的边线上
rotateAngle: 0,
rotateTransition: '',
animationInterval: null,
lights: [], // 用于存储闪光灯信息
currentAnimationStep: 0,
lightConfig: {
animationSpeed: 1000, // 初始动画速度,单位毫秒
totalLights: 0, // 总灯数,将在 mounted 中设置
},
lotteryIndex:null,// 中奖下标
};
},
computed: {
// 计算轮盘样式
containerStyle() {
let {
size,
customStyle
} = this;
size = /\d$/.test(size) ? size + 'rpx' : size;
return `width: ${size}; height: ${size}; ${customStyle}`;
},
wheelStyle() {
return `
padding: ${this.styleOpt.padding};
transform: ${this.rotateAngle};
transition: ${this.rotateTransition};
${this.innerStyle}
`;
},
getCorrectAngle() {
return index => {
const style = {
transform: `skewY(${90 - 360 / this.prizes.length}deg) skewX(0deg) rotate(${180 / this.prizes.length}deg)`
}
if (this.prizes.length == 2) {
if (index == 0) {
style['transform'] = `rotate(90deg)`
style['bottom'] = 0
} else {
style['transform'] = `rotate(0deg)`
style['left'] = 0
style['bottom'] = '-50%'
}
}
return style
};
},
},
methods: {
// 计算每个奖项扇形的样式
getPrizeItemStyle(index) {
const style = {
transform: `rotate(${(360 / this.prizes.length) * index}deg) skewX(0deg) skewY(${360 / this.prizes.length - 90}deg)`,
}
if (this.prizes.length == 2) {
style['transform'] = index == 0 ? 0 : `rotate(270deg)`
style['top'] = 0
}
// 只有在没有背景图片时才设置背景色
if (!this.getPrizeBgImage(index)) {
style.backgroundColor = `${this.styleOpt.prizeBgColors[index % this.styleOpt.prizeBgColors.length]}`;
}
return style
},
// 开始旋转
startRotation(index) {
if (this.rotating) return;
const duration = this.duration;
const length = this.prizes.length;
// const index = 4;
const rotateAngle = this.rotationAngle + this.turns * 360 + 360 - (180 / length + (360 / length) * index) -
(this.rotationAngle % 360);
console.log("rotateAngle",rotateAngle);
this.rotationAngle = rotateAngle;
this.rotateAngle = `rotate(${rotateAngle}deg)`;
this.rotateTransition = `transform ${duration}ms cubic-bezier(0.250, 0.460, 0.455, 0.995)`;
this.rotating = true;
// 加速闪光灯动画
this.accelerateLightAnimation();
// console.log("this.lotteryIndex",this.lotteryIndex);
setTimeout(() => {
this.$emit('done');
this.rotating = false;
this.stopSpinningAnimation();
// 恢复正常速度
this.resetLightAnimation();
}, duration + 500);
},
accelerateLightAnimation() {
this.stopLightAnimation();
this.lightConfig.animationSpeed = this.lighFastSpeed;
this.startLightAnimation();
},
resetLightAnimation() {
this.stopLightAnimation();
this.lightConfig.animationSpeed = this.lightNormalSpeed;
this.startLightAnimation();
},
stopSpinningAnimation() {
clearInterval(this.animationInterval);
// cancelAnimationFrame(this.animationInterval);
},
// 对外暴露的启动方法
// run(index) {
// this.startRotation(index);
// },
run(index) {
this.startRotation(index);
},
// 设置次数
setCount(count){
console.log("count",count);
this.lotteryIndex = count;
},
// 初始化闪光灯
initLights() {
this.lightConfig.totalLights = this.prizes.length * 2;
this.lights = Array(this.lightConfig.totalLights).fill().map(() => ({}));
},
// 获取闪光灯样式
getLightStyle(index) {
const angle = (360 / this.lights.length) * index - 90;
const radius = 44.5; // 使用 50% 作为半径
const radian = angle * (Math.PI / 180);
const x = 50 + radius * Math.cos(radian);
const y = 50 + radius * Math.sin(radian);
const style = {
left: `${x}%`,
top: `${y}%`,
transform: 'translate(-50%, -50%)',
width: this.lightSize,
height: this.lightSize
};
if (this.lightImgs.length === 0) {
style.backgroundColor = this.getCurrentColor(index)
style.boxShadow = `0 0 5px ${this.getCurrentColor(index)}`
}
return style;
},
getCurrentColor(colorIndex) {
if (this.lightImgs.length > 0) return ''; // 如果有图片,不返回颜色
const totalSteps = this.lightColors.length;
const currentStep = this.currentAnimationStep % totalSteps;
const currentColorIndex = (colorIndex + currentStep) % totalSteps;
return this.lightColors[currentColorIndex];
},
getCurrentImage(colorIndex) {
if (this.lightImgs.length === 0) return ''; // 如果没有图片,返回空字符串
const totalSteps = this.lightImgs.length;
const currentStep = this.currentAnimationStep % totalSteps;
const currentImageIndex = (colorIndex + currentStep) % totalSteps;
return this.lightImgs[currentImageIndex];
},
startLightAnimation() {
this.animationInterval = setInterval(() => {
this.currentAnimationStep++;
// 强制更新视图
this.$forceUpdate();
}, this.lightConfig.animationSpeed);
},
stopLightAnimation() {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
},
// 新增:获取扇形背景图
getPrizeBgImage(index) {
if (this.prizeBgImages.length === 0) {
// 如果没有提供背景图,返回 null
return null;
}
return this.prizeBgImages[index % this.prizeBgImages.length];
},
},
mounted() {
this.initLights();
this.startLightAnimation();
},
beforeDestroy() {
this.stopSpinningAnimation();
this.stopLightAnimation();
},
};
</script>
<style scoped lang="scss">
/* 轮盘容器样式 */
.lucky-wheel-container {
position: relative;
display: flex;
}
.lucky-wheel-bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 0;
}
.lucky-wheel-d {
position: absolute;
width: 90%;
height: 90%;
top: 50%;
left: 50%;
z-index: 0;
transform: translate(-50%, -50%);
}
.wheel-image {
width: 100%;
height: 100%;
position: absolute;
left: 0;
right: 0;
}
.wheel-image-1 {
animation: blink-1 1000ms infinite linear;
}
.wheel-image-2 {
animation: blink-2 1000ms infinite linear;
}
@keyframes blink-1 {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
@keyframes blink-2 {
0%,
100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
/* 轮盘主体样式 */
.lucky-wheel {
width: 100%;
height: 100%;
position: relative;
background-repeat: no-repeat;
background-size: cover;
box-sizing: border-box;
border-radius: 50%;
overflow: hidden;
margin: auto;
transition-property: transform;
transition-timing-function: cubic-bezier(0.250, 0.460, 0.455, 0.995);
}
.lucky-wheel-wrap {
position: relative;
z-index: 1;
width: 100%;
height: 100%;
border-radius: 50%;
overflow: hidden;
box-sizing: border-box;
}
/* 奖项扇形样式 */
.prize-item {
overflow: hidden;
position: absolute;
top: -50%;
left: 50%;
width: 100%;
height: 100%;
transform-origin: 0 100%;
}
/* 奖项内容样式 */
.prize-content {
position: absolute;
padding-top: 16rpx;
box-sizing: border-box;
width: 100%;
height: 100%;
left: -50%;
bottom: -50%;
display: flex;
flex-direction: column;
align-items: center;
}
.prize-content-bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 0;
}
/* 奖项图片样式 */
.prize-image {
width: 40px;
height: 40px;
margin-bottom: 5px;
}
/* 奖项名称样式 */
.prize-name {
font-size: 12px;
color: #333;
text-align: center;
position: relative;
z-index: 9;
font-weight: 400;
font-size: 28rpx;
color: #DA402B;
// color: #FFFFFF;
}
.prize-pro {
position: relative;
}
.prize-pro-txt {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: 400;
font-size: 14rpx;
color: #E26241;
white-space: nowrap;
}
/* 指针样式 */
.pointer {
position: absolute;
// top: 50%;
top:47%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
cursor: pointer;
}
.pointer-triangle {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 40px solid #ff0000; /* 红色三角形 */
/* 删除 transform: rotate(180deg); 这一行 */
}
.pointer-heart {
animation: heart 1s infinite;
}
@keyframes heart {
0% {
transform: scale(1);
}
25% {
transform: scale(1.03);
}
to {
transform: scale(1);
}
}
.lights-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 2;
pointer-events: none;
}
.light {
position: absolute;
border-radius: 50%;
}
</style>