feat:小程序改造

This commit is contained in:
2026-07-23 14:00:03 +08:00
committed by charles
parent 3f335074ea
commit 553543b5b3
39 changed files with 1096 additions and 1231 deletions
@@ -0,0 +1,3 @@
## 1.0.0(2024-10-10)
1.0.0
1.0.0
@@ -0,0 +1,536 @@
<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>
@@ -0,0 +1,85 @@
{
"id": "dengbo-lotteryWheel",
"displayName": "uniapp抽奖-转盘",
"version": "1.0.0",
"description": "uniapp抽奖-转盘",
"keywords": [
"抽奖",
"转盘",
"闪光灯",
"跑马灯"
],
"repository": "",
"engines": {
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
@@ -0,0 +1,122 @@
# dengbo-lotteryWheel 组件
dengbo-lotteryWheel 是一个用于创建抽奖转盘的 uniapp 组件。它提供了一个可自定义的转盘界面,支持动画效果和奖品展示。
## 属性
| 属性名 | 类型 | 默认值 | 描述 |
|--------|------|--------|------|
| prizeList | Array | [] | 奖品列表,每个奖品对象应包含 `icon` 和 `name` 属性 |
| rotateTime | Number | 5 | 转盘旋转时间(秒) |
| rotateDeg | Number | 0 | 转盘旋转的角度 |
## 事件
| 事件名 | 描述 |
|--------|------|
| rotateEnd | 转盘停止旋转时触发 |
## 示例
## 使用方法
```
<template>
<view class="content">
<view class="lottery">
<dengbo-lotteryWheel :prizes="prizes" :size="626" :duration="5000" @run="onWheelClick"
@done="onWheelDone" ref="luckyWheel" :lightImgs="lightImages" customStyle="borderRadius: 50%;">
<template v-slot:bg>
<image src="/static/tzbj.png" style="width: 100%; height: 100%" />
</template>
<template v-slot:pointer>
<image src="/static/tan.png" style="width: 212rpx" mode="widthFix" @click="startWheel"/>
</template>
</dengbo-lotteryWheel>
</view>
</view>
</template>
<script>
export default {
data() {
return {
lightImages: ['/static/tpoint1.png', '/static/tpoint2.png'],
prizes: [{
name: '积分',
image: '/static/thb.png',
pro: '50积分',
},
{
name: '积分',
image: '/static/thb.png',
pro: '100积分',
},
{
name: '优惠券',
image: '/static/thb.png',
pro: '15元优惠券',
},
{
name: '家用电器',
image: '/static/thb.png',
pro: '微波炉',
},
{
name: '谢谢参与',
image: '/static/thb.png',
pro: '15元优惠券',
},
{
name: '办公用品',
image: '/static/thb.png',
pro: '机械键盘',
},
{
name: '生活用品',
image: '/static/thb.png',
pro: '化妆刷',
},
],
}
},
onLoad() {
},
methods: {
startWheel() {
this.$refs.luckyWheel.run(2);
},
onWheelClick() {
console.log('用户点击了转盘');
this.$refs.luckyWheel.run(2);
},
onWheelDone(prize) {
console.log('抽奖结束,中奖奖品:', prize);
},
}
}
</script>
<style>
.content{
display: flex;
flex-direction: column;
align-items: center;
}
.lottery {
position: relative;
display: inline-block;
width: 626rpx;
height: 626rpx;
margin-top: 100rpx;
}
</style>
```
## 注意事项
- 确保 `prizeList` 中的奖品数量与组件中的扇形数量匹配。
- `rotateDeg` 决定了转盘最终停止的位置,您可以根据需要计算这个值以控制中奖结果。
- 可以通过修改组件的 CSS 来自定义转盘的外观。