This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
.nut-turntable {
position: absolute;
top: 50%;
left: 50%;
overflow: hidden;
text-align: center;
transform: translate(-50%, -50%);
// transform: translateZ(0);
.pointer {
position: absolute;
top: 50%;
// left: calc(50% - 35px);
// top: calc(50% - 40px);
left: 50%;
z-index: 99;
transform: translate(-43.75%, -50%);
}
// stylelint-disable selector-class-pattern
.drawTable-name {
position: absolute;
top: 20px;
left: 10px;
width: calc(100% - 20px);
font-size: 12px;
color: #ff5722;
text-align: center;
}
.drawTable-img {
position: absolute;
top: 60px;
/* 要居中就要50% - 宽度 / 2 */
left: calc(50% - 30px / 2);
width: 30px;
height: 30px;
image {
display: inline-block;
width: 100%;
height: 100%;
}
}
.turntable {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
// stylelint-disable selector-id-pattern
#canvasWx {
width: 100%;
height: 100%;
}
.mlcanvas {
height: 50% !important;
margin-top: 25%;
canvas {
transform: scale(2);
}
}
}
.prize {
position: absolute;
top: 0;
left: 25%;
width: 50%;
height: 50%;
.item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: center bottom;
}
}
}

View File

@@ -0,0 +1,2 @@
export type * from './turntable'
export type * from './type'

View File

@@ -0,0 +1,95 @@
import type { ExtractPropTypes } from 'vue'
import { CLICK_EVENT } from '../_constants'
import { commonProps, makeNumberProp } from '../_utils'
export const turntableProps = {
...commonProps,
/**
* @description 转盘的宽度
*/
width: {
required: true,
default: '300px',
},
/**
* @description 转盘的高度
*/
height: {
required: true,
default: '300px',
},
/**
* 奖品列表
*/
prizeList: {
type: Array<any>,
required: true,
default: () => [],
},
/**
* 中奖奖品在列表的索引位置
*/
prizeIndex: makeNumberProp(-1),
/**
* @description 从开始转动到结束所用时间,单位秒
*/
turnsNumber: makeNumberProp(5),
/**
* @description 转盘中的样式,包括每个扇区的背景颜色(在每条数据中页可单独设置 prizeColor),扇区的边框颜色
*/
styleOpt: {
default: () => {
return {
// 每一块扇形的背景色,默认值,可通过父组件来改变
prizeBgColors: [
'rgb(255, 231, 149)',
'rgb(255, 247, 223)',
'rgb(255, 231, 149)',
'rgb(255, 247, 223)',
'rgb(255, 231, 149)',
'rgb(255, 247, 223)',
],
// 每一块扇形的外边框颜色,默认值,可通过父组件来改变
borderColor: '#ff9800',
}
},
},
/**
* @description 转动需要持续的时间(秒)
*/
turnsTime: makeNumberProp(5),
/**
* @description 抽奖间隔(秒)
*/
lockTime: makeNumberProp(0),
/**
* @description 转盘中指针的样式,包括背景图片、大小等
*/
pointerStyle: {
default: () => {
return {
width: '80px',
height: '80px',
backgroundImage:
'url("https://img11.360buyimg.com/imagetools/jfs/t1/89512/11/15244/137408/5e6f15edEf57fa3ff/cb57747119b3bf89.png")',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
}
},
},
}
export type TurnTableProps = ExtractPropTypes<typeof turntableProps>
export const turntableEmits = {
[CLICK_EVENT]: () => true,
startTurns: () => true,
endTurns: () => true,
lockTurns: () => true,
}
export type TurnTableEmits = typeof turntableEmits
export interface TurnTableInst {
rotateTurn: () => void
}

View File

@@ -0,0 +1,249 @@
<script setup lang="ts">
import type { ComponentInternalInstance } from 'vue'
import { computed, defineComponent, getCurrentInstance, onMounted, reactive, ref, watch } from 'vue'
import { PREFIX } from '../_constants'
import { getMainClass, getMainStyle, isH5, isMpWeixin } from '../_utils'
import { turntableEmits, turntableProps } from './turntable'
import type { TPrizeItem } from './type'
const props = defineProps(turntableProps)
const emit = defineEmits(turntableEmits)
const instance = getCurrentInstance() as ComponentInternalInstance
defineExpose({ rotateTurn })
const {
width,
height,
turnsNumber,
styleOpt,
turnsTime,
pointerStyle,
lockTime,
} = reactive(props)
let prizeList: TPrizeItem[] = reactive(props?.prizeList)
const classes = computed(() => {
return getMainClass(props, componentName)
})
const styles = computed(() => {
return getMainStyle(props, { width, height })
})
// const getRandomColor = function () {
// const r = Math.floor(Math.random() * 256);
// const g = Math.floor(Math.random() * 256);
// const b = Math.floor(Math.random() * 256);
// return "rgb(" + r + "," + g + "," + b + ")";
// };
// 用来锁定转盘,避免同时多次点击转动
const lock = ref(false)
// 是否正在转动
const rorating = ref(false)
// 剩余抽奖次数
// const num = ref(5);
// 开始转动的角度
const startRotateDegree = ref(0)
// 设置指针默认指向的位置,现在是默认指向2个扇形之间的边线上
const rotateAngle = ref<string>('0')
const rotateTransition = ref('')
const turntableDom = ref(null)
const canvasDom = ref(null)
const canvasDomEle = ref()
const _rorateDeg = ref(360 / prizeList.length)
// 根据index计算每一格要旋转的角度的样式
function getRotateAngle(index: number, flag?: string) {
const angle = (360 / prizeList.length) * index + 180 / prizeList.length
return {
transform: `rotate(${angle}deg)${flag === 'canvas' && isH5 ? ' scale(2)' : ''}`,
}
}
// 初始化圆形转盘canvas
function init() {
const data = styleOpt
const prizeNum = prizeList.length
const { prizeBgColors } = data
// 开始绘画
// const canvas = canvasDom.value;
// const luckdraw = turntableDom.value;
// const ctx = canvas.getContext('2d');
const ctx = uni.createCanvasContext('canvasWx', instance.proxy)
// const canvasW = Number(width.replace(/px/g, "")); // 画板的高度
// const canvasH =
// Number(height.replace(/px/g, "")) / (envApp.value == "WEAPP" ? 1 : 2); // 画板的宽度
const canvasW = Number.parseFloat(width) // 画板的高度
const radis = isMpWeixin ? 1 : 2
const canvasH = Number.parseFloat(height) / radis
if (isMpWeixin) {
// translate方法重新映射画布上的 (0,0) 位置
ctx.translate(0, canvasH)
// rotate方法旋转当前的绘图因为文字是和当前扇形中心线垂直的
ctx.rotate((-90 * Math.PI) / 180)
}
// 圆环的外圆的半径,可用来调整圆盘大小来适应外部盒子的大小
// const outRadius = canvasW / 2 - 1;
// 圆环的内圆的半径
// const innerRadius = 0;
const baseAngle = (Math.PI * 2) / prizeNum // 每个奖项所占角度数
// ctx.clearRect(0, 0, canvasW, canvasH); //去掉背景默认色
// ctx.strokeStyle = borderColor; // 设置画图线的颜色
for (let index = 0; index < prizeNum; index++) {
const startAngle = index * baseAngle
const endAngle = (index + 1) * baseAngle
ctx.beginPath()
ctx.moveTo(canvasW / 2, canvasH / 2)
ctx.arc(
canvasW / 2,
canvasH / 2,
canvasH / 2,
startAngle,
endAngle,
false,
)
/* 随机颜色 */
if (prizeList[index].prizeColor)
ctx.fillStyle = prizeList[index].prizeColor // 设置每个扇形区域的颜色,根据每条数据中单独设置的优先
else
ctx.fillStyle = prizeBgColors[index] // 设置每个扇形区域的颜色
ctx.fill()
}
ctx.draw()
}
// 判断是否可以转动
function canBeRotated() {
// if (num.value <= 0) {
// return false;
// }
if (lock.value) {
if (!rorating.value)
emit('lockTurns')
return false
}
return true
}
function startTurns() {
// 如果还不可以转动
if (!canBeRotated())
return false
emit('startTurns')
}
// 转动起来
function changeLock() {
setTimeout(() => {
lock.value = false
}, lockTime * 1000)
}
function rotate(index: number) {
const turnsTimeNum = turnsTime
const rotateAngleValue
= startRotateDegree.value
+ turnsNumber * 360
+ 360
- (180 / prizeList.length + (360 / prizeList.length) * index)
- (startRotateDegree.value % 360)
startRotateDegree.value = rotateAngleValue
rotateAngle.value = `rotate(${rotateAngleValue}deg)`
rotateTransition.value = `transform ${turnsTimeNum}s cubic-bezier(0.250, 0.460, 0.455, 0.995)`
setTimeout(() => {
emit('endTurns')
rorating.value = false
changeLock()
}, turnsTimeNum * 1000 + 500)
}
function rotateTurn() {
// 开始转动
// 先上锁
lock.value = true
rorating.value = true
setTimeout(() => {
rotate(props.prizeIndex)
}, 300)
}
watch(
() => props.prizeList,
(list) => {
prizeList = list
init()
},
)
// watch(
// () => props.prizeIndex,
// (nIndex) => {
// rotate(nIndex);
// }
// );
onMounted(() => {
setTimeout(() => {
// #ifdef H5
const canvasDom: HTMLElement | null
= document.getElementById('canvasWx')
canvasDomEle.value = canvasDom
// #endif
init()
}, 800)
})
</script>
<script lang="ts">
const componentName = `${PREFIX}-turntable`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view ref="turntableDom" :class="classes" :style="styles">
<view class="turntable" :style="{ transform: rotateAngle, transition: rotateTransition }">
<canvas
id="canvasWx"
ref="canvasDom"
type="2d"
:class="isMpWeixin ? '' : 'mlcanvas'"
canvas-id="canvasWx"
:style="isMpWeixin ? '' : getRotateAngle(0, 'canvas')"
/>
<!-- <canvas id="canvasWx" canvas-id="canvasWx" ref="canvasDom" type="2d" :style="getRotateAngle(0)">
</canvas> -->
<view v-if="prizeList.length > 0" class="prize">
<view
v-for="(item, index) of prizeList"
:key="index"
class="item"
:style="getRotateAngle(index)"
>
<view class="drawTable-name">
{{ item.prizeName }}
</view>
<view class="drawTable-img">
<image :src="item.prizeImg" />
</view>
</view>
</view>
</view>
<view class="pointer" :style="pointerStyle" @click="startTurns" />
</view>
</template>
<style lang="scss">
@import './index';
</style>

View File

@@ -0,0 +1,6 @@
export interface TPrizeItem {
id: string | number
prizeName: string
prizeColor: string
prizeImg: string
}