Commit f048b3ec by lijiabin

【需求 20520】 wip: 加入转盘相关的第三方库、页面等

parent d9182421
......@@ -23,6 +23,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@lucky-canvas/vue": "^0.1.11",
"@vueuse/components": "^14.0.0",
"@vueuse/core": "^14.0.0",
"@wangeditor/editor": "^5.1.23",
......
......@@ -11,6 +11,9 @@ importers:
'@element-plus/icons-vue':
specifier: ^2.3.2
version: 2.3.2(vue@3.5.22(typescript@5.9.3))
'@lucky-canvas/vue':
specifier: ^0.1.11
version: 0.1.11(vue@3.5.22(typescript@5.9.3))
'@vueuse/components':
specifier: ^14.0.0
version: 14.0.0(vue@3.5.22(typescript@5.9.3))
......@@ -37,7 +40,7 @@ importers:
version: 1.11.19
element-plus:
specifier: ^2.11.5
version: 2.11.5(vue@3.5.22(typescript@5.9.3))
version: 2.11.5(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
inquirer:
specifier: ^13.0.2
version: 13.0.2(@types/node@22.18.12)
......@@ -691,6 +694,11 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@lucky-canvas/vue@0.1.11':
resolution: {integrity: sha512-5vm0txSKRBtMgrE/HZEvw1joSTx9NTdAkc8tBp/aX0LxyhQtiTVBLsRgdYUK/OiURCL8bo+046BTGnV+Q4JFlg==}
peerDependencies:
vue: ^2.0.0 || >=3.0.0-rc.0
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
......@@ -1168,6 +1176,11 @@ packages:
'@vue/compiler-ssr@3.5.22':
resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==}
'@vue/composition-api@1.7.2':
resolution: {integrity: sha512-M8jm9J/laYrYT02665HkZ5l2fWTK4dcVg3BsDHm/pfz+MjDYwX+9FUaZyGwEyXEDonQYRCo0H7aLgdklcIELjw==}
peerDependencies:
vue: '>= 2.5 < 2.7'
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
......@@ -2740,6 +2753,9 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucky-canvas@1.7.27:
resolution: {integrity: sha512-Ftz6qD+863bI7xijBmZg3dw3cNEc7odPr70EZQcGA14y3TgTAzH65HPosOCd6kKUlMwhntBaHMx3onoj9MtJRQ==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
......@@ -3910,6 +3926,16 @@ packages:
'@vue/composition-api':
optional: true
vue-demi@0.7.5:
resolution: {integrity: sha512-eFSQSvbQdY7C9ujOzvM6tn7XxwLjn0VQDXQsiYBLBwf28Na+2nTQR4BBBcomhmdP6mmHlBKAwarq6a0BPG87hQ==}
hasBin: true
peerDependencies:
'@vue/composition-api': ^1.0.0-beta.1
vue: ^2.6.0 || >=3.0.0-rc.1
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-eslint-parser@10.2.0:
resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
......@@ -4568,6 +4594,13 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@lucky-canvas/vue@0.1.11(vue@3.5.22(typescript@5.9.3))':
dependencies:
'@vue/composition-api': 1.7.2(vue@3.5.22(typescript@5.9.3))
lucky-canvas: 1.7.27
vue: 3.5.22(typescript@5.9.3)
vue-demi: 0.7.5(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
......@@ -5114,6 +5147,10 @@ snapshots:
'@vue/compiler-dom': 3.5.22
'@vue/shared': 3.5.22
'@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3))':
dependencies:
vue: 3.5.22(typescript@5.9.3)
'@vue/devtools-api@6.6.4': {}
'@vue/devtools-api@7.7.7':
......@@ -5236,12 +5273,12 @@ snapshots:
'@vueuse/shared': 14.0.0(vue@3.5.22(typescript@5.9.3))
vue: 3.5.22(typescript@5.9.3)
'@vueuse/core@9.13.0(vue@3.5.22(typescript@5.9.3))':
'@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))':
dependencies:
'@types/web-bluetooth': 0.0.16
'@vueuse/metadata': 9.13.0
'@vueuse/shared': 9.13.0(vue@3.5.22(typescript@5.9.3))
vue-demi: 0.14.10(vue@3.5.22(typescript@5.9.3))
'@vueuse/shared': 9.13.0(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
......@@ -5254,9 +5291,9 @@ snapshots:
dependencies:
vue: 3.5.22(typescript@5.9.3)
'@vueuse/shared@9.13.0(vue@3.5.22(typescript@5.9.3))':
'@vueuse/shared@9.13.0(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))':
dependencies:
vue-demi: 0.14.10(vue@3.5.22(typescript@5.9.3))
vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
......@@ -5900,7 +5937,7 @@ snapshots:
electron-to-chromium@1.5.240: {}
element-plus@2.11.5(vue@3.5.22(typescript@5.9.3)):
element-plus@2.11.5(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)):
dependencies:
'@ctrl/tinycolor': 3.6.1
'@element-plus/icons-vue': 2.3.2(vue@3.5.22(typescript@5.9.3))
......@@ -5908,7 +5945,7 @@ snapshots:
'@popperjs/core': '@sxzz/popperjs-es@2.11.7'
'@types/lodash': 4.17.20
'@types/lodash-es': 4.17.12
'@vueuse/core': 9.13.0(vue@3.5.22(typescript@5.9.3))
'@vueuse/core': 9.13.0(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
async-validator: 4.2.5
dayjs: 1.11.19
lodash: 4.17.21
......@@ -6819,6 +6856,8 @@ snapshots:
dependencies:
yallist: 3.1.1
lucky-canvas@1.7.27: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
......@@ -8099,9 +8138,17 @@ snapshots:
vscode-uri@3.1.0: {}
vue-demi@0.14.10(vue@3.5.22(typescript@5.9.3)):
vue-demi@0.14.10(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)):
dependencies:
vue: 3.5.22(typescript@5.9.3)
optionalDependencies:
'@vue/composition-api': 1.7.2(vue@3.5.22(typescript@5.9.3))
vue-demi@0.7.5(@vue/composition-api@1.7.2(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)):
dependencies:
vue: 3.5.22(typescript@5.9.3)
optionalDependencies:
'@vue/composition-api': 1.7.2(vue@3.5.22(typescript@5.9.3))
vue-eslint-parser@10.2.0(eslint@9.38.0(jiti@2.6.1)):
dependencies:
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<defs>
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#5a4bd8"/>
<stop offset="50%" stop-color="#6858ec"/>
<stop offset="100%" stop-color="#7b6fef"/>
</linearGradient>
</defs>
<circle cx="200" cy="200" r="186" fill="none" stroke="url(#ringGrad)" stroke-width="26"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(11.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(22.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(33.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(45 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(56.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(67.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(78.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(90 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(101.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(112.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(123.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(135 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(146.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(157.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(168.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(180 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(191.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(202.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(213.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(225 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(236.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(247.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(258.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(270 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(281.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(292.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(303.75 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(315 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(326.25 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#ffd54f" transform="rotate(337.5 200 200)"/>
<circle cx="200" cy="14" r="4.5" fill="#f8bbd0" transform="rotate(348.75 200 200)"/>
</svg>
......@@ -419,7 +419,7 @@ const {
commentId = 0,
type,
} = defineProps<{
authorId?: string // 文章作者id
authorId?: string | number // 文章作者id
id: number // 文章ID
defaultSize?: number
isQuestion?: boolean // 如果是问题的话 展示有点不一样
......
<template>
<div class="lucky-wheel-wrapper">
<LuckyWheel
ref="myLucky"
width="260px"
height="260px"
:blocks="blocks"
:buttons="buttons"
:prizes="prizes"
:default-config="defaultConfig"
:default-style="defaultStyle"
@start="startCallback"
@end="endCallback"
/>
<!-- 自定义指针 -->
<div class="wheel-pointer">
<div class="pointer-pin" />
<div class="pointer-dot" />
</div>
<button
class="go-btn"
:class="{ 'is-spinning': isSpinning }"
:disabled="isSpinning"
@click="startCallback"
>
<span class="go-text">GO</span>
</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { LuckyWheel } from '@lucky-canvas/vue'
import ringTexture from '@/assets/img/lucky-wheel-outer-ring.svg'
const emit = defineEmits<{
(e: 'prize', prize: Record<string, unknown>): void
}>()
const myLucky = ref<InstanceType<typeof LuckyWheel>>()
const defaultConfig = {
offsetDegree: 0,
speed: 20,
accelerationTime: 2500,
decelerationTime: 2500,
}
const defaultStyle = {
fontColor: '#333',
fontSize: '12px',
fontWeight: 'bold',
lineHeight: '14px',
}
const blocks = [
{ padding: '2px', background: '#e8e4ff' },
{
padding: '16px',
background: '#6858ec',
imgs: [
{
src: ringTexture,
width: '100%',
height: '100%',
rotate: true,
},
],
},
{ padding: '3px', background: '#7b6fef' },
]
const prizes = [
{
background: '#ffffff',
fonts: [{ text: '谢谢参与', top: '12%', fontSize: '11px', fontColor: '#6858ec' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f60a.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#f3f0ff',
fonts: [{ text: '10个京豆', top: '12%', fontSize: '11px', fontColor: '#e5a012' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f61d.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#ffffff',
fonts: [{ text: '5个京豆', top: '12%', fontSize: '11px', fontColor: '#e5a012' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f929.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#f3f0ff',
fonts: [{ text: '1个京豆', top: '12%', fontSize: '11px', fontColor: '#e5a012' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f60e.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#ffffff',
fonts: [{ text: '谢谢参与', top: '12%', fontSize: '11px', fontColor: '#6858ec' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f385.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#f3f0ff',
fonts: [{ text: '10个京豆', top: '12%', fontSize: '11px', fontColor: '#e5a012' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f917.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#ffffff',
fonts: [{ text: '5个京豆', top: '12%', fontSize: '11px', fontColor: '#e5a012' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f618.png',
width: '30%',
top: '38%',
},
],
},
{
background: '#f3f0ff',
fonts: [{ text: '1个京豆', top: '12%', fontSize: '11px', fontColor: '#e5a012' }],
imgs: [
{
src: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f970.png',
width: '30%',
top: '38%',
},
],
},
]
const buttons = [
{ radius: '30%', background: '#fce4e4' },
{ radius: '25%', background: '#f75a5a' },
{
radius: '22%',
background: '#e63939',
pointer: false,
fonts: [{ text: '', top: '-14px' }],
},
]
const isSpinning = ref(false)
const startCallback = () => {
if (isSpinning.value) return
isSpinning.value = true
myLucky.value?.play()
setTimeout(() => {
const index = Math.floor(Math.random() * prizes.length)
myLucky.value?.stop(index)
}, 3000)
}
const endCallback = (prize: Record<string, unknown>) => {
isSpinning.value = false
emit('prize', prize)
}
</script>
<style scoped>
.lucky-wheel-wrapper {
position: relative;
display: inline-block;
width: 260px;
height: 260px;
border-radius: 50%;
box-shadow:
0 2px 8px rgba(104, 88, 236, 0.6),
0 0 16px 1px rgba(104, 88, 236, 0.08);
}
.go-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid #ff8a80;
background: linear-gradient(145deg, #ff5252, #d32f2f);
box-shadow:
0 4px 12px rgba(211, 47, 47, 0.5),
inset 0 2px 4px rgba(255, 255, 255, 0.3);
cursor: pointer;
z-index: 10;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.go-btn:hover:not(:disabled) {
transform: translate(-50%, -50%) scale(1.1);
box-shadow:
0 6px 20px rgba(211, 47, 47, 0.6),
inset 0 2px 4px rgba(255, 255, 255, 0.4);
background: linear-gradient(145deg, #ff6e6e, #e53935);
}
.go-btn:active:not(:disabled) {
transform: translate(-50%, -50%) scale(0.92);
box-shadow:
0 2px 6px rgba(211, 47, 47, 0.4),
inset 0 3px 6px rgba(0, 0, 0, 0.15);
background: linear-gradient(145deg, #d32f2f, #c62828);
}
.go-btn:disabled {
cursor: not-allowed;
opacity: 0.8;
}
.go-btn.is-spinning {
animation: pulse-spin 1.2s ease-in-out infinite;
}
.go-text {
font-size: 22px;
font-weight: 900;
color: #fff;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
letter-spacing: 2px;
user-select: none;
pointer-events: none;
}
@keyframes pulse-spin {
0%,
100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.8;
}
50% {
transform: translate(-50%, -50%) scale(1.06);
opacity: 1;
}
}
.wheel-pointer {
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
z-index: 20;
display: flex;
flex-direction: column;
align-items: center;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.pointer-pin {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 22px solid #ffd54f;
position: relative;
}
.pointer-pin::before {
content: '';
position: absolute;
top: -22px;
left: -8px;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 18px solid #ffe082;
}
.pointer-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: linear-gradient(145deg, #ffe082, #ffc107);
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
margin-top: -3px;
}
</style>
<template>
<!-- 转盘容器 -->
<Comp @prize="handlePrize" />
<!-- 🎉 中奖动画层 -->
<Teleport to="body">
<Transition name="result">
<div
v-if="showResult"
class="fixed inset-0 z-[9999] bg-black/70 backdrop-blur flex flex-col items-center justify-center"
>
<img :src="currentPrize?.img" class="w-32 h-32 animate-pop" />
<!-- 调整中奖图片尺寸 -->
<div class="mt-6 text-white text-3xl font-bold">恭喜获得 {{ currentPrize?.name }}</div>
<!-- 调整中奖文字尺寸 -->
<el-button class="mt-8" type="primary" @click="showResult = false"> 知道了 </el-button>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Comp from './Comp.vue'
interface Prize {
name: string
img: string
}
const prizes: Prize[] = [
{ name: '100元优惠券', img: 'https://cdn-icons-png.flaticon.com/512/1041/1041916.png' },
{ name: '谢谢参与', img: 'https://cdn-icons-png.flaticon.com/512/1828/1828843.png' },
// 修复了图片链接中的拼写错误 'flaticom' -> 'flaticon'
{ name: '实物奖品', img: 'https://cdn-icons-png.flaticon.com/512/15/15874.png' },
{ name: '红包', img: 'https://cdn-icons-png.flaticon.com/512/2331/2331970.png' },
{ name: '幸运奖', img: 'https://cdn-icons-png.flaticon.com/512/744/744465.png' },
{ name: '平板电脑', img: 'https://cdn-icons-png.flaticon.com/512/1828/1828919.png' },
]
const rotateDeg = ref(0)
const isDrawing = ref(false)
const showResult = ref(false)
const currentPrize = ref<Prize | null>(null)
function startDraw() {
if (isDrawing.value) return
isDrawing.value = true
const prizeIndex = Math.floor(Math.random() * prizes.length)
currentPrize.value = prizes[prizeIndex]
const perDeg = 360 / prizes.length
// 计算奖品中心点相对于转盘0度(3点钟方向)的绝对角度
const targetPrizeCenterAngle = prizeIndex * perDeg + perDeg / 2
// 指针位于12点钟方向(转盘的-90度或270度),计算需要旋转的度数,使奖品中心对齐指针
// 旋转度数 = (指针目标角度 - 奖品中心角度 + 360) % 360
// 这里指针目标角度我们定为270度 (相对于3点钟的0度)
const rotationToAlignWithPointer = (270 - targetPrizeCenterAngle + 360) % 360
// 增加多次完整旋转,使转盘转动效果更明显
const fullSpins = 5
const finalTargetDeg = fullSpins * 360 + rotationToAlignWithPointer
rotateDeg.value += finalTargetDeg
setTimeout(() => {
showResult.value = true
isDrawing.value = false
}, 4200)
}
/**
* 每个奖品扇区的容器样式,负责整体旋转。
* 它的子元素 (prizeSliceStyle 和 prizeContentStyle) 将在此旋转坐标系中定位。
*/
function prizeSectorWrapperStyle(index: number) {
const angle = 360 / prizes.length
return {
transform: `rotate(${angle * index}deg)`, // 旋转整个逻辑扇区区域
transformOrigin: '50% 50%', // 围绕转盘中心旋转
}
}
/**
* 实际的扇形区域样式,负责背景和clipPath。
* 它的旋转由 prizeSectorWrapperStyle 继承。
*/
function prizeSliceStyle(index: number) {
return {
background:
index % 2
? 'linear-gradient(135deg,#f43f5e,#fb7185)' // 调整渐变颜色,使其更协调
: 'linear-gradient(135deg,#f59e0b,#fbbf24)',
clipPath: 'polygon(50% 50%, 100% 0%, 100% 100%)', // 保持三角形裁剪,初始指向右侧
}
}
/**
* 奖品内容(图标和文字)的定位和旋转样式。
* 它在 prizeSectorWrapperStyle 已经旋转的坐标系中进行定位。
*/
function prizeContentStyle(index: number) {
const angle = 360 / prizes.length
const wheelDiameterRem = 48 // 转盘直径 48rem
const wheelRadiusRem = wheelDiameterRem / 2 // 转盘半径 24rem
// 将内容放置在距离中心约 65% 的半径处
const distanceFromCenterRem = wheelRadiusRem * 0.65
return {
// 将内容div的中心放置在转盘的中心
top: '50%',
left: '50%',
// 1. translate(-50%, -50%):将div自身的中心对齐到 (top:50%, left:50%)
// 2. rotate(${angle / 2}deg):将其旋转到扇区的中心角度 (相对于扇区初始的0度,即右侧)
// 3. translateY(-${distanceFromCenterRem}rem):沿着旋转后的Y轴(即径向方向)向外平移
transform: `
translate(-50%, -50%)
rotate(${angle / 2}deg)
translateY(-${distanceFromCenterRem}rem)
`,
zIndex: '1', // 确保内容显示在扇区背景之上
}
}
function handlePrize(prize: Prize) {
currentPrize.value = prize
showResult.value = true
}
</script>
<style>
/* 中奖弹层动画 */
.result-enter-active {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(1.1);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 礼物弹跳 */
.animate-pop {
animation: pop 0.8s ease-out;
}
@keyframes pop {
0% {
transform: scale(0.3);
opacity: 0;
}
60% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
</style>
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import LuckyCanvas from '@lucky-canvas/vue'
import './style/index.css'
......@@ -32,6 +32,7 @@ const app = createApp(App)
app.use(notivue)
app.use(createPinia())
app.use(router)
app.use(LuckyCanvas)
// 全局组件挂载
app.component('SvgIcon', SvgIcon)
......
......@@ -350,7 +350,7 @@
</div>
<!-- 大转盘 -->
<!-- <div class="lottery-container common-box rounded-lg bg-#F5F0FF">
<div class="lottery-container common-box rounded-lg bg-#F5F0FF">
<div class="flex items-center gap-2 mb-4">
<div class="w-1 h-4 bg-gradient-to-b from-violet-500 to-purple-500 rounded-full"></div>
<h1 class="text-sm sm:text-base font-bold">大转盘</h1>
......@@ -358,7 +358,7 @@
<div class="flex justify-center">
<LuckyWheel />
</div>
</div> -->
</div>
</div>
</div>
......@@ -399,7 +399,7 @@ import { useQuestionStore, useActivityStore, useYaBiStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { push } from 'notivue'
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'
// import LuckyWheel from '@/components/common/LuckyWheel/index.vue'
import LuckyWheel from '@/components/common/LuckyWheel/index.vue'
import { RewardButtonEnum } from '@/constants'
import RewardButton from '@/components/common/RewardButton/index.vue'
import { useTourStore } from '@/stores'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment