|
@@ -0,0 +1,260 @@
|
|
|
|
+<template>
|
|
|
|
+ <view class="l-dialer" :style="rootStyles">
|
|
|
|
+ <view class="l-dialer__inner" :style="innerStyle">
|
|
|
|
+ <view class="l-dialer__inner-border" v-if="$slots['border'] != null">
|
|
|
|
+ <slot name="border" />
|
|
|
|
+ </view>
|
|
|
|
+ <view class="l-dialer__inner-wrap" ref="drawbleRef" :style="wrapStyle">
|
|
|
|
+ <view class="l-dialer__inner-item" v-for="(item, index) in prizeList" :key="index"
|
|
|
|
+ :style="itemStyle(index)">
|
|
|
|
+ <view class="l-dialer__inner-content" :style="contentStyle(index)">
|
|
|
|
+ <slot v-if="$slots['prize'] != null" name="prize" :item="item" :even="index % 2"></slot>
|
|
|
|
+ <template v-else>
|
|
|
|
+ <text class="l-dialer__inner-name" :style="nameStyle">{{ item['name'] }}</text>
|
|
|
|
+ <image class="l-dialer__inner-img" :src="item['img']"></image>
|
|
|
|
+ </template>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ <view class="l-dialer__pointer" :style="pointerStyle">
|
|
|
|
+ <slot v-if="$slots['pointer'] != null" name="pointer" />
|
|
|
|
+ <image v-else :class="!isTurnIng ? 'heart' : ''" src="/uni_modules/lime-dialer/static/turnable_btn.png"
|
|
|
|
+ style="width: 100%" mode="widthFix" @tap="$emit('click')" />
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+</template>
|
|
|
|
+<script lang="uts" setup>
|
|
|
|
+ const emits = defineEmits(['click', 'done'])
|
|
|
|
+ const slots = defineSlots<{
|
|
|
|
+ prize : {
|
|
|
|
+ item : UTSJSONObject,
|
|
|
|
+ even : number
|
|
|
|
+ }
|
|
|
|
+ }>()
|
|
|
|
+ const props = defineProps({
|
|
|
|
+ size: {
|
|
|
|
+ // #ifdef APP-ANDROID
|
|
|
|
+ type: Object,
|
|
|
|
+ // #endif
|
|
|
|
+ // #ifndef APP-ANDROID
|
|
|
|
+ type: [String, Number],
|
|
|
|
+ // #endif
|
|
|
|
+ default: 300
|
|
|
|
+ },
|
|
|
|
+ prizeList: {
|
|
|
|
+ type: Array as PropType<UTSJSONObject[]>,
|
|
|
|
+ default: () : UTSJSONObject[] => []
|
|
|
|
+ },
|
|
|
|
+ turns: {
|
|
|
|
+ type: Number,
|
|
|
|
+ default: 10
|
|
|
|
+ },
|
|
|
|
+ duration: {
|
|
|
|
+ type: Number,
|
|
|
|
+ default: 3
|
|
|
|
+ },
|
|
|
|
+ styleOpt: {
|
|
|
|
+ type: Object as PropType<UTSJSONObject>,
|
|
|
|
+ default: () : UTSJSONObject => ({
|
|
|
|
+ // 每一块扇形的背景色,默认值,可通过父组件来改变
|
|
|
|
+ prizeBgColors: ['#fff0a3', '#fffce6'],
|
|
|
|
+ // 每一块扇形的外边框颜色,默认值,可通过父组件来改变
|
|
|
|
+ borderColor: '#ffd752',
|
|
|
|
+ } as UTSJSONObject)
|
|
|
|
+ },
|
|
|
|
+ customStyle: {
|
|
|
|
+ type: String,
|
|
|
|
+ },
|
|
|
|
+ dialStyle: {
|
|
|
|
+ type: String,
|
|
|
|
+ },
|
|
|
|
+ pointerStyle: {
|
|
|
|
+ type: String,
|
|
|
|
+ default: `width: 30%`
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const drawbleRef = ref<UniElement | null>(null)
|
|
|
|
+ const startRotateDegree = ref(0)
|
|
|
|
+ const rotateAngle = ref('rotate(0deg)')
|
|
|
|
+ const rotateTransition = ref('')
|
|
|
|
+ const isTurnIng = ref(false)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ const getStyleOpt = computed(() : UTSJSONObject => {
|
|
|
|
+ const style = {
|
|
|
|
+ // 每一块扇形的背景色,默认值,可通过父组件来改变
|
|
|
|
+ prizeBgColors: ['#fff0a3', '#fffce6'],
|
|
|
|
+ // 每一块扇形的外边框颜色,默认值,可通过父组件来改变
|
|
|
|
+ borderColor: '#ffd752',
|
|
|
|
+ }
|
|
|
|
+ return UTSJSONObject.assign(style, props.styleOpt)
|
|
|
|
+ })
|
|
|
|
+ const rootStyles = computed(() : string => {
|
|
|
|
+ const size = /\d$/.test(`${props.size}`) ? `${props.size}px` : props.size;
|
|
|
|
+ return `width: ${size}; height: ${size}; ${props.customStyle}`
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const innerStyle = computed(() : string => {
|
|
|
|
+ // const style = new Map<string, string>()
|
|
|
|
+ let style = ''
|
|
|
|
+ const padding = getStyleOpt.value['padding'] ?? 0
|
|
|
|
+
|
|
|
|
+ style += `padding: ${padding};`
|
|
|
|
+ style += `transform: ${rotateAngle.value};`
|
|
|
|
+ style += `${rotateTransition.value};`//`transition: ${rotateTransition.value};`
|
|
|
|
+ style += `${props.dialStyle};`
|
|
|
|
+
|
|
|
|
+ return style
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const wrapStyle = computed(() : string => {
|
|
|
|
+ const borderColor = getStyleOpt.value['borderColor']
|
|
|
|
+ if (borderColor != null) {
|
|
|
|
+ return `border: 1rpx solid ${borderColor}`
|
|
|
|
+ }
|
|
|
|
+ return ''
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const itemStyle = computed(() : ((index : number) => Map<string, any>) => {
|
|
|
|
+ return (index : number) : Map<string, any> => {
|
|
|
|
+ const length = props.prizeList.length;
|
|
|
|
+ const prizeBgColors : string[] = (getStyleOpt.value['prizeBgColors'] ?? [] as string[]) as string[]
|
|
|
|
+ const prizeBgColorsLength = prizeBgColors.length;
|
|
|
|
+ const borderColor = getStyleOpt.value['borderColor']
|
|
|
|
+ const style = new Map<string, any>();
|
|
|
|
+ // #ifndef APP
|
|
|
|
+ if (length == 2) {
|
|
|
|
+ // style['transform'] = index == 0 ? 0 : `rotate(270deg)`
|
|
|
|
+ style.set('transform', index == 0 ? `rotate(0deg)` : `rotate(270deg)`)
|
|
|
|
+ style.set('top', 0)
|
|
|
|
+ } else {
|
|
|
|
+ style.set('transform', `rotate(${(360 / length) * index}deg) skewX(0deg) skewY(${360 / length - 90}deg)`);
|
|
|
|
+ }
|
|
|
|
+ if (prizeBgColorsLength > 0) {
|
|
|
|
+ style.set('backgroundColor', `${prizeBgColors[index % prizeBgColorsLength]}`)
|
|
|
|
+ }
|
|
|
|
+ if (borderColor != null) {
|
|
|
|
+ style.set('border', `1rpx solid ${borderColor}`)
|
|
|
|
+ }
|
|
|
|
+ // #endif
|
|
|
|
+ // #ifdef APP
|
|
|
|
+ if (length == 2) {
|
|
|
|
+ style.set('backgroundColor', `${prizeBgColors[index % prizeBgColorsLength]}`)
|
|
|
|
+ style.set('transform', index == 0 ? `rotate(0deg)` : `rotate(270deg)`);
|
|
|
|
+ style.set('top', 0)
|
|
|
|
+ if (borderColor != null) {
|
|
|
|
+ style.set('border', `1rpx solid ${borderColor}`)
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ style.set('transform', `rotate(${(360 / length) * index}deg)`);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // #endif
|
|
|
|
+ return style
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const contentStyle = computed(() : ((index : number) => string) => {
|
|
|
|
+ return (index : number) : string => {
|
|
|
|
+ // #ifndef APP
|
|
|
|
+ if (props.prizeList.length != 2) {
|
|
|
|
+ return `transform: skewY(${90 - 360 / props.prizeList.length}deg) skewX(0deg) rotate(${180 / props.prizeList.length}deg)`
|
|
|
|
+ } else {
|
|
|
|
+ return index == 0
|
|
|
|
+ ? `transform: rotate(90deg); bottom: 0`
|
|
|
|
+ : `transform: rotate(0deg); bottom: -50%; left: 0`
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // #endif
|
|
|
|
+ // #ifdef APP
|
|
|
|
+ if (props.prizeList.length != 2) {
|
|
|
|
+ return `transform: rotate(${180 / props.prizeList.length}deg)`
|
|
|
|
+ } else {
|
|
|
|
+ return index == 0
|
|
|
|
+ ? `transform: rotate(90deg); bottom: 0`
|
|
|
|
+ : `transform: rotate(0deg); bottom: -50%; left: 0`
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // #endif
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const nameStyle = computed(() : Map<string, any> => {
|
|
|
|
+ const fontSize = getStyleOpt.value['fontSize']
|
|
|
|
+ const color = getStyleOpt.value['color']
|
|
|
|
+ const style = new Map<string, any>()
|
|
|
|
+
|
|
|
|
+ if (fontSize != null) {
|
|
|
|
+ style.set('fontSize', fontSize)
|
|
|
|
+ }
|
|
|
|
+ if (color != null) {
|
|
|
|
+ style.set('color', color)
|
|
|
|
+ }
|
|
|
|
+ return style
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ const run = (index : number) => {
|
|
|
|
+ if (isTurnIng.value) return
|
|
|
|
+ const duration = props.duration;
|
|
|
|
+ const length = props.prizeList.length;
|
|
|
|
+
|
|
|
|
+ const _rotateAngle = startRotateDegree.value + props.turns * 360 + 360 - (180 / length + (360 / length) * index) - (startRotateDegree.value % 360);
|
|
|
|
+ startRotateDegree.value = _rotateAngle;
|
|
|
|
+ rotateAngle.value = `rotate(${_rotateAngle}deg)`;
|
|
|
|
+ rotateTransition.value = `transition-duration: ${duration}s`;
|
|
|
|
+ isTurnIng.value = true
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ emits('done', index);
|
|
|
|
+ isTurnIng.value = false
|
|
|
|
+ }, duration * 1000 + 500);
|
|
|
|
+ }
|
|
|
|
+ // #ifdef APP
|
|
|
|
+ onMounted(() => {
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ if (drawbleRef.value == null) return;
|
|
|
|
+ const ctx = drawbleRef.value!.getDrawableContext()!;
|
|
|
|
+ const size = drawbleRef.value!.offsetWidth;
|
|
|
|
+ watch(props.prizeList, () => {
|
|
|
|
+ ctx.reset()
|
|
|
|
+ const length = props.prizeList.length;
|
|
|
|
+ if (length == 2) return
|
|
|
|
+ const prizeBgColors : string[] = (getStyleOpt.value['prizeBgColors'] ?? [] as string[]) as string[]
|
|
|
|
+ const prizeBgColorsLength = prizeBgColors.length;
|
|
|
|
+ const borderColor = getStyleOpt.value['borderColor'] as string | null
|
|
|
|
+
|
|
|
|
+ const centerX = size / 2;
|
|
|
|
+ const centerY = size / 2;
|
|
|
|
+ const radius = size / 2;
|
|
|
|
+
|
|
|
|
+ const angle = (2 * Math.PI) / length;
|
|
|
|
+
|
|
|
|
+ for (let i = 0; i < length; i++) {
|
|
|
|
+ ctx.beginPath();
|
|
|
|
+ ctx.moveTo(centerX, centerY);
|
|
|
|
+ ctx.arc(centerX, centerY, radius, i * angle, (i + 1) * angle);
|
|
|
|
+ ctx.lineTo(centerX, centerY);
|
|
|
|
+ ctx.closePath();
|
|
|
|
+ ctx.fillStyle = prizeBgColors[i % prizeBgColorsLength];
|
|
|
|
+ if (borderColor != null) {
|
|
|
|
+ ctx.lineWidth = 2
|
|
|
|
+ ctx.strokeStyle = borderColor;
|
|
|
|
+ ctx.stroke()
|
|
|
|
+ }
|
|
|
|
+ ctx.fill();
|
|
|
|
+ }
|
|
|
|
+ ctx.update()
|
|
|
|
+ }, { immediate: true })
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+ // #endif
|
|
|
|
+ defineExpose({
|
|
|
|
+ run
|
|
|
|
+ })
|
|
|
|
+</script>
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+ @import './index';
|
|
|
|
+</style>
|