536 lines
13 KiB
Vue
536 lines
13 KiB
Vue
<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> |