Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporate-culture-qd
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
王立鹏
corporate-culture-qd
Commits
0519d87a
Commit
0519d87a
authored
Apr 02, 2026
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 20520】 feat: 完成前台大转盘功能
parent
feb96717
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
981 additions
and
216 deletions
+981
-216
LuckyWheel.vue
src/components/common/LuckyWheel/components/LuckyWheel.vue
+104
-109
index.vue
src/components/common/LuckyWheel/index.vue
+788
-99
index.vue
src/components/common/MessageBox/index.vue
+4
-1
index.vue
src/views/homePage/index.vue
+22
-4
selfActivity.vue
src/views/userPage/components/selfActivity.vue
+63
-3
No files found.
src/components/common/LuckyWheel/
Comp
.vue
→
src/components/common/LuckyWheel/
components/LuckyWheel
.vue
View file @
0519d87a
<
template
>
<
template
>
<div
class=
"lucky-wheel-wrapper"
>
<div
class=
"lucky-wheel-wrapper"
:class=
"
{ 'scale-80': smallerThanXl }"
>
<LuckyWheel
<LuckyWheel
ref=
"myLucky"
ref=
"myLucky"
width=
"260px"
width=
"260px"
height=
"260px"
height=
"260px"
:blocks=
"blocks"
:blocks=
"blocks"
:buttons=
"buttons"
:buttons=
"buttons"
:prizes=
"
p
rizes"
:prizes=
"
computedP
rizes"
:default-config=
"defaultConfig"
:default-config=
"defaultConfig"
:default-style=
"defaultStyle"
:default-style=
"defaultStyle"
@
start=
"startCallback"
@
end=
"endCallback"
@
end=
"endCallback"
/>
/>
<!-- 自定义指针 -->
<!-- 自定义指针 -->
...
@@ -21,7 +20,7 @@
...
@@ -21,7 +20,7 @@
class=
"go-btn"
class=
"go-btn"
:class=
"
{ 'is-spinning': isSpinning }"
:class=
"
{ 'is-spinning': isSpinning }"
:disabled="isSpinning"
:disabled="isSpinning"
@click="
startCallba
ck"
@click="
popCli
ck"
>
>
<span
class=
"go-text"
>
GO
</span>
<span
class=
"go-text"
>
GO
</span>
</button>
</button>
...
@@ -29,11 +28,22 @@
...
@@ -29,11 +28,22 @@
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
ref
}
from
'vue'
import
{
LuckyWheel
}
from
'@lucky-canvas/vue'
import
{
LuckyWheel
}
from
'@lucky-canvas/vue'
import
ringTexture
from
'@/assets/img/lucky-wheel-outer-ring.svg'
import
ringTexture
from
'@/assets/img/lucky-wheel-outer-ring.svg'
import
type
{
WheelPrizeItemDto
,
LuckWheelResultDto
}
from
'@/api'
import
{
participateLuckyWheel
,
getWheelPrizeList
}
from
'@/api'
import
{
breakpointsTailwind
,
useBreakpoints
}
from
'@vueuse/core'
import
{
useMessageBox
}
from
'@/hooks'
const
{
confirm
}
=
useMessageBox
()
const
breakpoints
=
useBreakpoints
(
breakpointsTailwind
)
const
smallerThanXl
=
breakpoints
.
smaller
(
'xl'
)
const
wheelPrizeList
=
ref
<
WheelPrizeItemDto
[]
>
([])
const
emit
=
defineEmits
<
{
const
emit
=
defineEmits
<
{
(
e
:
'prize'
,
prize
:
Record
<
string
,
unknown
>
):
void
handlePrizeResult
:
[
LuckWheelResultDto
]
}
>
()
}
>
()
const
myLucky
=
ref
<
InstanceType
<
typeof
LuckyWheel
>>
()
const
myLucky
=
ref
<
InstanceType
<
typeof
LuckyWheel
>>
()
...
@@ -47,9 +57,9 @@ const defaultConfig = {
...
@@ -47,9 +57,9 @@ const defaultConfig = {
const
defaultStyle
=
{
const
defaultStyle
=
{
fontColor
:
'#333'
,
fontColor
:
'#333'
,
fontSize
:
'12px'
,
fontWeight
:
'bold'
,
fontWeight
:
'bold'
,
lineHeight
:
'14px'
,
lineHeight
:
'14px'
,
lengthLimit
:
'60%'
,
}
}
const
blocks
=
[
const
blocks
=
[
...
@@ -69,102 +79,37 @@ const blocks = [
...
@@ -69,102 +79,37 @@ const blocks = [
{
padding
:
'3px'
,
background
:
'#7b6fef'
},
{
padding
:
'3px'
,
background
:
'#7b6fef'
},
]
]
const
prizes
=
[
const
computedPrizes
=
computed
(()
=>
{
{
const
width
=
wheelPrizeList
.
value
.
length
>=
6
?
'37%'
:
'30%'
background
:
'#ffffff'
,
return
wheelPrizeList
.
value
.
map
((
item
:
WheelPrizeItemDto
,
index
:
number
)
=>
{
fonts
:
[{
text
:
'谢谢参与'
,
top
:
'12%'
,
fontSize
:
'11px'
,
fontColor
:
'#6858ec'
}],
return
{
imgs
:
[
background
:
index
%
2
===
0
?
'#ffffff'
:
'#f3f0ff'
,
{
fonts
:
[
src
:
'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f60a.png'
,
{
width
:
'30%'
,
text
:
item
.
name
,
top
:
'38%'
,
top
:
'8%'
,
},
fontSize
:
'10px'
,
],
fontColor
:
index
%
2
===
0
?
'#6858ec'
:
'#e5a012'
,
},
lineClamp
:
1
,
{
},
background
:
'#f3f0ff'
,
],
fonts
:
[{
text
:
'10个京豆'
,
top
:
'12%'
,
fontSize
:
'11px'
,
fontColor
:
'#e5a012'
}],
imgs
:
[
imgs
:
[
{
{
src
:
item
.
imageUrl
,
src
:
'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f61d.png'
,
width
,
width
:
'30%'
,
top
:
'35%'
,
top
:
'38%'
,
},
},
],
],
id
:
item
.
id
,
},
}
{
})
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
=
[
const
buttons
=
[
{
radius
:
'
30
%'
,
background
:
'#fce4e4'
},
{
radius
:
'
25
%'
,
background
:
'#fce4e4'
},
{
radius
:
'2
5
%'
,
background
:
'#f75a5a'
},
{
radius
:
'2
0
%'
,
background
:
'#f75a5a'
},
{
{
radius
:
'
22
%'
,
radius
:
'
18
%'
,
background
:
'#e63939'
,
background
:
'#e63939'
,
pointer
:
false
,
pointer
:
false
,
fonts
:
[{
text
:
''
,
top
:
'-14px'
}],
fonts
:
[{
text
:
''
,
top
:
'-14px'
}],
...
@@ -173,20 +118,70 @@ const buttons = [
...
@@ -173,20 +118,70 @@ const buttons = [
const
isSpinning
=
ref
(
false
)
const
isSpinning
=
ref
(
false
)
const
startCallback
=
()
=>
{
let
resultPrize
:
null
|
LuckWheelResultDto
=
null
const
startCallback
=
async
()
=>
{
if
(
isSpinning
.
value
)
return
if
(
isSpinning
.
value
)
return
isSpinning
.
value
=
true
isSpinning
.
value
=
true
myLucky
.
value
?.
play
()
myLucky
.
value
?.
play
()
setTimeout
(
async
()
=>
{
const
{
data
}
=
await
participateLuckyWheel
()
const
idx
=
computedPrizes
.
value
.
findIndex
((
item
)
=>
item
.
id
===
data
.
prizeId
)
myLucky
.
value
?.
stop
(
idx
)
resultPrize
=
data
},
1000
)
}
// 获取最新的奖品列表 对比 是否更新了
const
popClick
=
async
()
=>
{
if
(
isSpinning
.
value
)
return
const
{
data
}
=
await
getWheelPrizeList
()
const
newWheelPrizeList
=
data
const
oldWheelPrizeList
=
wheelPrizeList
.
value
// 暂时只需要对比 1长度不一致 需要更新 2如果长度一样 如果有一组的名字或者图片 不一样 需要更新
let
shouldUpdate
=
false
if
(
newWheelPrizeList
.
length
!==
oldWheelPrizeList
.
length
)
{
shouldUpdate
=
true
}
else
{
newWheelPrizeList
.
forEach
((
item
:
WheelPrizeItemDto
,
index
:
number
)
=>
{
if
(
item
.
name
!==
oldWheelPrizeList
[
index
]?.
name
||
item
.
imageUrl
!==
oldWheelPrizeList
[
index
]?.
imageUrl
)
{
shouldUpdate
=
true
}
})
}
if
(
shouldUpdate
)
{
// 给用户提示 奖池更新了 请重新点击
await
confirm
({
title
:
'检测到后台奖池有更新'
,
message
:
'请重新点击按钮开始抽奖'
,
type
:
'warning'
,
showCancelButton
:
false
,
})
wheelPrizeList
.
value
=
newWheelPrizeList
}
else
{
startCallback
()
}
}
const
endCallback
=
()
=>
{
setTimeout
(()
=>
{
setTimeout
(()
=>
{
const
index
=
Math
.
floor
(
Math
.
random
()
*
prizes
.
length
)
isSpinning
.
value
=
false
myLucky
.
value
?.
stop
(
index
)
emit
(
'handlePrizeResult'
,
resultPrize
as
LuckWheelResultDto
)
},
30
00
)
},
5
00
)
}
}
const
endCallback
=
(
prize
:
Record
<
string
,
unknown
>
)
=>
{
const
initWheelPrizeList
=
async
(
)
=>
{
isSpinning
.
value
=
false
const
{
data
}
=
await
getWheelPrizeList
()
emit
(
'prize'
,
prize
)
wheelPrizeList
.
value
=
data
}
}
onMounted
(()
=>
{
initWheelPrizeList
()
})
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
...
@@ -206,8 +201,8 @@ const endCallback = (prize: Record<string, unknown>) => {
...
@@ -206,8 +201,8 @@ const endCallback = (prize: Record<string, unknown>) => {
top
:
50%
;
top
:
50%
;
left
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
transform
:
translate
(
-50%
,
-50%
);
width
:
6
0px
;
width
:
5
0px
;
height
:
6
0px
;
height
:
5
0px
;
border-radius
:
50%
;
border-radius
:
50%
;
border
:
3px
solid
#ff8a80
;
border
:
3px
solid
#ff8a80
;
background
:
linear-gradient
(
145deg
,
#ff5252
,
#d32f2f
);
background
:
linear-gradient
(
145deg
,
#ff5252
,
#d32f2f
);
...
...
src/components/common/LuckyWheel/index.vue
View file @
0519d87a
<
template
>
<
template
>
<!-- 转盘容器 -->
<Comp
@
handle-prize-result=
"handlePrizeResult"
/>
<Comp
@
prize=
"handlePrize"
/>
<el-button
@
click=
"testAnimation"
>
测试动画
</el-button>
<!-- 🎉 中奖动画层 -->
<Teleport
to=
"body"
>
<Teleport
to=
"body"
>
<Transition
name=
"result"
>
<Transition
name=
"result"
>
<div
<div
v-if=
"showResult"
v-if=
"showResult"
class=
"fixed inset-0 z-[9999] bg-black/70 backdrop-blur flex flex-col items-center justify-center"
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"
/>
<template
v-if=
"isThanksPrize"
>
<!-- 调整中奖图片尺寸 -->
<div
ref=
"blessingWrapRef"
class=
"blessing-wrap"
>
<div
class=
"mt-6 text-white text-3xl font-bold"
>
恭喜获得
{{
currentPrize
?.
name
}}
</div>
<div
ref=
"blessingGlowRef"
class=
"blessing-glow"
aria-hidden=
"true"
/>
<!-- 调整中奖文字尺寸 -->
<el-button
class=
"mt-8"
type=
"primary"
@
click=
"showResult = false"
>
知道了
</el-button>
<div
ref=
"blessingFanSceneRef"
class=
"blessing-fan-scene"
>
<div
ref=
"blessingTagSlotRef"
class=
"blessing-tag-slot"
aria-hidden=
"true"
/>
<div
ref=
"blessingFanRef"
class=
"blessing-fan"
aria-hidden=
"true"
>
<div
v-for=
"(text, index) in fanStackTexts"
:key=
"`$
{text}-${index}`"
:ref="setFanItemRef"
class="blessing-fan-item"
:style="fanItemStyle(index, fanStackTexts.length)"
:data-angle="fanItemMotion(index, fanStackTexts.length).angle"
:data-x="fanItemMotion(index, fanStackTexts.length).xOffset"
:data-y="fanItemMotion(index, fanStackTexts.length).yOffset"
:data-scale="fanItemMotion(index, fanStackTexts.length).scale"
>
<div
class=
"blessing-fan-face"
>
<span
class=
"blessing-fan-emoji"
>
{{
fanEmoji
(
index
)
}}
</span>
<div
class=
"blessing-fan-text"
>
<span
v-for=
"(char, charIndex) in text.split('')"
:key=
"`$
{char}-${charIndex}`"
class="blessing-fan-char"
>
{{
char
}}
</span>
</div>
</div>
</div>
</div>
<div
ref=
"blessingTagStageRef"
class=
"blessing-tag-stage"
>
<div
ref=
"blessingTagRef"
class=
"blessing-tag"
>
<div
ref=
"blessingTagTopRef"
class=
"blessing-tag-top"
>
<span
class=
"blessing-tag-hole"
/>
<span
class=
"blessing-tag-knot"
/>
</div>
<div
class=
"blessing-tag-headline"
>
LUCKY
</div>
<div
class=
"blessing-tag-divider"
/>
<div
class=
"blessing-text"
:class=
"
{ 'is-long-text': blessingChars.length > 10 }">
<span
v-for=
"(char, index) in blessingChars"
:key=
"`$
{char}-${index}`"
:ref="setBlessingCharRef"
>
{{
char
}}
</span>
</div>
<div
ref=
"blessingSealRef"
class=
"blessing-tag-seal"
>
GOOD DAY
</div>
</div>
</div>
</div>
</div>
<div
ref=
"blessingCaptionRef"
class=
"blessing-caption"
>
<p
class=
"blessing-caption-title"
>
今日签语
</p>
<p
class=
"blessing-caption-subtitle"
>
抽一支好运电子签,把今天过得亮一点
</p>
</div>
<el-button
class=
"mt-8"
type=
"primary"
@
click=
"showResult = false"
>
知道了
</el-button>
</
template
>
<
template
v-else
>
<img
:src=
"currentPrize?.prizeImageUrl"
class=
"w-60 h-60 animate-pop"
/>
<div
class=
"mt-6 text-white text-3xl font-bold"
>
恭喜获得
{{
currentPrize
?.
prizeName
}}
</div>
<el-button
class=
"mt-8"
type=
"primary"
@
click=
"showResult = false"
>
知道了
</el-button>
</
template
>
</div>
</div>
</Transition>
</Transition>
</Teleport>
</Teleport>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
import
type
{
ComponentPublicInstance
}
from
'vue'
import
Comp
from
'./Comp.vue'
import
gsap
from
'gsap'
interface
Prize
{
name
:
string
import
Comp
from
'./components/LuckyWheel.vue'
img
:
string
import
type
{
LuckWheelResultDto
}
from
'@/api'
}
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
showResult
=
ref
(
false
)
const
currentPrize
=
ref
<
Prize
|
null
>
(
null
)
const
currentPrize
=
ref
<
LuckWheelResultDto
|
null
>
(
null
)
function
startDraw
()
{
const
blessingWrapRef
=
ref
<
HTMLElement
|
null
>
(
null
)
if
(
isDrawing
.
value
)
return
const
blessingGlowRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingFanSceneRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingFanRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingTagSlotRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingTagStageRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingTagRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingTagTopRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingSealRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
blessingCaptionRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
fanItemRefs
=
ref
<
HTMLElement
[]
>
([])
const
blessingCharRefs
=
ref
<
HTMLElement
[]
>
([])
isDrawing
.
value
=
true
let
blessingTimeline
:
gsap
.
core
.
Timeline
|
null
=
null
const
prizeIndex
=
Math
.
floor
(
Math
.
random
()
*
prizes
.
length
)
let
floatTween
:
gsap
.
core
.
Tween
|
null
=
null
currentPrize
.
value
=
prizes
[
prizeIndex
]
const
perDeg
=
360
/
prizes
.
length
const
blessingEmojiList
=
[
'·ᴗ·'
,
'^_^'
,
'◕‿◕'
,
'✦ᴗ✦'
,
'˶ᵔ ᵕ ᵔ˶'
,
'•‿•'
,
'๑´ڡ`๑'
]
// 计算奖品中心点相对于转盘0度(3点钟方向)的绝对角度
const
isThanksPrize
=
computed
(()
=>
currentPrize
.
value
?.
prizeId
==
null
)
const
targetPrizeCenterAngle
=
prizeIndex
*
perDeg
+
perDeg
/
2
const
blessingChars
=
computed
(()
=>
{
const
text
=
(
currentPrize
.
value
?.
blessingText
||
''
).
trim
()
return
text
?
text
.
split
(
''
)
:
[
'好'
,
'运'
,
'常'
,
'在'
]
})
// 指针位于12点钟方向(转盘的-90度或270度),计算需要旋转的度数,使奖品中心对齐指针
const
fanPreviewTexts
=
[
// 旋转度数 = (指针目标角度 - 奖品中心角度 + 360) % 360
'钱包鼓鼓 烦恼全无'
,
// 这里指针目标角度我们定为270度 (相对于3点钟的0度)
'年年有鱼摸 岁岁越平安'
,
const
rotationToAlignWithPointer
=
(
270
-
targetPrizeCenterAngle
+
360
)
%
360
'干啥啥都顺 吃嘛嘛都香'
,
'狂吃不胖 熬夜不秃'
,
'凡是发生皆有利于我'
,
'内核强大 所向披靡'
,
'逆风如解意 税后十个亿'
,
'恶缘退散 好人靠近'
,
'发发发 发量爆棚'
,
'桃旺旺 钱财多多'
,
'拒绝精神内耗 本人配享太庙'
,
]
// 增加多次完整旋转,使转盘转动效果更明显
const
fanStackTexts
=
computed
(()
=>
{
const
fullSpins
=
5
const
texts
=
[...
fanPreviewTexts
]
const
finalTargetDeg
=
fullSpins
*
360
+
rotationToAlignWithPointer
const
selectedText
=
(
currentPrize
.
value
?.
blessingText
||
''
).
trim
()
rotateDeg
.
value
+=
finalTargetDeg
if
(
!
isThanksPrize
.
value
||
!
selectedText
)
{
return
texts
}
setTimeout
(()
=>
{
const
selectedIndex
=
texts
.
indexOf
(
selectedText
)
showResult
.
value
=
true
if
(
selectedIndex
===
-
1
)
{
isDrawing
.
value
=
false
return
texts
},
4200
)
}
}
const
[
selectedItem
]
=
texts
.
splice
(
selectedIndex
,
1
)
const
centerIndex
=
Math
.
floor
(
texts
.
length
/
2
)
texts
.
splice
(
centerIndex
,
0
,
selectedItem
as
string
)
return
texts
})
const
fanItemMotion
=
(
index
:
number
,
total
:
number
)
=>
{
const
center
=
(
total
-
1
)
/
2
const
offset
=
index
-
center
const
spreadRatio
=
center
===
0
?
0
:
offset
/
center
const
angle
=
spreadRatio
*
30
const
xOffset
=
offset
*
40
const
yOffset
=
Math
.
abs
(
offset
)
*
4
const
scale
=
1
-
Math
.
abs
(
spreadRatio
)
*
0.08
/**
* 每个奖品扇区的容器样式,负责整体旋转。
* 它的子元素 (prizeSliceStyle 和 prizeContentStyle) 将在此旋转坐标系中定位。
*/
function
prizeSectorWrapperStyle
(
index
:
number
)
{
const
angle
=
360
/
prizes
.
length
return
{
return
{
transform
:
`rotate(
${
angle
*
index
}
deg)`
,
// 旋转整个逻辑扇区区域
angle
,
transformOrigin
:
'50% 50%'
,
// 围绕转盘中心旋转
xOffset
,
yOffset
,
scale
,
}
}
}
}
/**
const
fanItemStyle
=
(
index
:
number
,
total
:
number
)
=>
{
* 实际的扇形区域样式,负责背景和clipPath。
const
{
angle
,
xOffset
,
yOffset
,
scale
}
=
fanItemMotion
(
index
,
total
)
* 它的旋转由 prizeSectorWrapperStyle 继承。
*/
function
prizeSliceStyle
(
index
:
number
)
{
return
{
return
{
background
:
'--angle'
:
`
${
angle
}
deg`
,
index
%
2
'--x-offset'
:
`
${
xOffset
}
px`
,
?
'linear-gradient(135deg,#f43f5e,#fb7185)'
// 调整渐变颜色,使其更协调
'--y-offset'
:
`
${
yOffset
}
px`
,
:
'linear-gradient(135deg,#f59e0b,#fbbf24)'
,
'--scale'
:
`
${
scale
}
`
,
clipPath
:
'polygon(50% 50%, 100% 0%, 100% 100%)'
,
// 保持三角形裁剪,初始指向右侧
}
}
}
}
/**
const
fanEmoji
=
(
index
:
number
)
=>
blessingEmojiList
[
index
%
blessingEmojiList
.
length
]
* 奖品内容(图标和文字)的定位和旋转样式。
* 它在 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
{
const
setFanItemRef
=
(
el
:
Element
|
ComponentPublicInstance
|
null
)
=>
{
// 将内容div的中心放置在转盘的中心
if
(
el
instanceof
HTMLElement
)
{
top
:
'50%'
,
fanItemRefs
.
value
.
push
(
el
)
left
:
'50%'
,
}
// 1. translate(-50%, -50%):将div自身的中心对齐到 (top:50%, left:50%)
}
// 2. rotate(${angle / 2}deg):将其旋转到扇区的中心角度 (相对于扇区初始的0度,即右侧)
// 3. translateY(-${distanceFromCenterRem}rem):沿着旋转后的Y轴(即径向方向)向外平移
const
setBlessingCharRef
=
(
el
:
Element
|
ComponentPublicInstance
|
null
)
=>
{
transform
:
`
if
(
el
instanceof
HTMLElement
)
{
translate(-50%, -50%)
blessingCharRefs
.
value
.
push
(
el
)
rotate(
${
angle
/
2
}
deg)
}
translateY(-
${
distanceFromCenterRem
}
rem)
}
`
,
zIndex
:
'1'
,
// 确保内容显示在扇区背景之上
const
clearBlessingAnimation
=
()
=>
{
}
blessingTimeline
?.
kill
()
}
blessingTimeline
=
null
floatTween
?.
kill
()
function
handlePrize
(
prize
:
Prize
)
{
floatTween
=
null
}
const
startFloating
=
()
=>
{
if
(
!
blessingTagRef
.
value
)
{
return
}
floatTween
?.
kill
()
floatTween
=
gsap
.
to
(
blessingTagRef
.
value
,
{
y
:
-
10
,
rotation
:
0.8
,
duration
:
2.6
,
repeat
:
-
1
,
yoyo
:
true
,
ease
:
'sine.inOut'
,
})
}
const
playBlessingAnimation
=
()
=>
{
const
wrap
=
blessingWrapRef
.
value
const
glow
=
blessingGlowRef
.
value
const
scene
=
blessingFanSceneRef
.
value
const
fan
=
blessingFanRef
.
value
const
slot
=
blessingTagSlotRef
.
value
const
tag
=
blessingTagRef
.
value
const
tagTop
=
blessingTagTopRef
.
value
const
seal
=
blessingSealRef
.
value
const
caption
=
blessingCaptionRef
.
value
const
fanItems
=
[...
fanItemRefs
.
value
]
const
chars
=
[...
blessingCharRefs
.
value
]
if
(
!
wrap
||
!
glow
||
!
scene
||
!
fan
||
!
slot
||
!
tag
||
!
tagTop
||
!
seal
||
!
caption
||
!
fanItems
.
length
)
{
return
}
clearBlessingAnimation
()
gsap
.
set
(
wrap
,
{
transformPerspective
:
1400
})
gsap
.
set
(
scene
,
{
y
:
20
,
opacity
:
0
})
gsap
.
set
(
glow
,
{
opacity
:
0
,
scale
:
0.78
})
gsap
.
set
(
slot
,
{
opacity
:
0
,
scaleX
:
0.72
,
transformOrigin
:
'50% 50%'
})
gsap
.
set
(
fan
,
{
y
:
34
,
opacity
:
0
})
gsap
.
set
(
blessingTagStageRef
.
value
,
{
opacity
:
0
,
y
:
26
,
filter
:
'blur(8px)'
,
transformOrigin
:
'50% 100%'
,
})
gsap
.
set
(
fanItems
,
{
opacity
:
0
,
y
:
78
,
rotate
:
(
_index
,
target
)
=>
Number
(
target
.
getAttribute
(
'data-angle'
)
||
0
)
*
0.4
,
scale
:
(
_index
,
target
)
=>
Number
(
target
.
getAttribute
(
'data-scale'
)
||
1
)
-
0.08
,
transformOrigin
:
'50% 100%'
,
})
gsap
.
set
(
tag
,
{
opacity
:
0
,
y
:
356
,
scale
:
0.94
,
rotation
:
0
,
transformOrigin
:
'50% 100%'
,
filter
:
'blur(10px)'
,
})
gsap
.
set
(
tagTop
,
{
opacity
:
0.65
})
gsap
.
set
(
chars
,
{
opacity
:
0
,
y
:
10
})
gsap
.
set
(
seal
,
{
opacity
:
0
,
y
:
8
})
gsap
.
set
(
caption
,
{
opacity
:
0
,
y
:
20
})
blessingTimeline
=
gsap
.
timeline
({
defaults
:
{
ease
:
'power2.out'
},
onComplete
:
()
=>
{
startFloating
()
},
})
blessingTimeline
.
to
(
scene
,
{
opacity
:
1
,
y
:
0
,
duration
:
0.45
})
.
to
(
glow
,
{
opacity
:
0.95
,
scale
:
1
,
duration
:
0.7
,
ease
:
'sine.out'
},
'<'
)
.
to
(
slot
,
{
opacity
:
1
,
scaleX
:
1
,
duration
:
0.52
},
'-=0.3'
)
.
to
(
fan
,
{
opacity
:
1
,
y
:
0
,
duration
:
0.4
},
'<'
)
.
to
(
fanItems
,
{
opacity
:
1
,
y
:
(
_index
,
target
)
=>
Number
(
target
.
getAttribute
(
'data-y'
)
||
0
),
rotate
:
(
_index
,
target
)
=>
Number
(
target
.
getAttribute
(
'data-angle'
)
||
0
),
scale
:
(
_index
,
target
)
=>
Number
(
target
.
getAttribute
(
'data-scale'
)
||
1
),
duration
:
0.9
,
ease
:
'back.out(1.08)'
,
stagger
:
{
each
:
0.055
,
from
:
'center'
,
},
},
'+=0.02'
,
)
.
to
(
fanItems
,
{
x
:
(
_index
,
target
)
=>
Number
(
target
.
getAttribute
(
'data-x'
)
||
0
)
*
0.1
,
duration
:
0.32
,
ease
:
'power1.inOut'
,
stagger
:
{
each
:
0.02
,
from
:
'edges'
,
},
yoyo
:
true
,
repeat
:
1
,
},
'+=0.26'
,
)
.
to
(
blessingTagStageRef
.
value
,
{
opacity
:
1
,
y
:
0
,
filter
:
'blur(0px)'
,
duration
:
0.48
,
ease
:
'power2.out'
,
},
'+=0.02'
,
)
.
to
(
tag
,
{
y
:
272
,
opacity
:
0.5
,
scale
:
0.97
,
filter
:
'blur(6px)'
,
duration
:
0.42
,
ease
:
'power2.out'
,
},
'<'
,
)
.
to
(
tag
,
{
y
:
44
,
opacity
:
1
,
scale
:
1.01
,
filter
:
'blur(1px)'
,
duration
:
1.08
,
ease
:
'power3.out'
,
},
'+=0.1'
,
)
.
to
(
tag
,
{
y
:
18
,
rotation
:
-
1.4
,
scale
:
1.015
,
filter
:
'blur(0px)'
,
duration
:
0.3
,
ease
:
'power2.out'
,
},
'-=0.18'
,
)
.
to
(
tag
,
{
y
:
24
,
rotation
:
0.4
,
scale
:
1
,
filter
:
'blur(0px)'
,
duration
:
0.32
,
ease
:
'back.out(1.5)'
,
},
'+=0.02'
,
)
.
to
(
tagTop
,
{
opacity
:
1
,
duration
:
0.22
},
'<'
)
.
to
(
chars
,
{
opacity
:
1
,
y
:
0
,
duration
:
0.34
,
stagger
:
0.05
,
},
'-=0.08'
,
)
.
to
(
seal
,
{
opacity
:
1
,
y
:
0
,
duration
:
0.28
,
},
'-=0.12'
,
)
.
to
(
caption
,
{
opacity
:
1
,
y
:
0
,
duration
:
0.42
,
},
'-=0.02'
,
)
}
watch
(
()
=>
showResult
.
value
,
async
(
visible
)
=>
{
if
(
!
visible
)
{
clearBlessingAnimation
()
return
}
if
(
!
isThanksPrize
.
value
)
{
clearBlessingAnimation
()
return
}
await
nextTick
()
playBlessingAnimation
()
},
)
onBeforeUpdate
(()
=>
{
fanItemRefs
.
value
=
[]
blessingCharRefs
.
value
=
[]
})
onBeforeUnmount
(()
=>
{
clearBlessingAnimation
()
})
const
handlePrizeResult
=
(
prize
:
LuckWheelResultDto
)
=>
{
currentPrize
.
value
=
prize
currentPrize
.
value
=
prize
showResult
.
value
=
true
showResult
.
value
=
true
}
}
const
testAnimation
=
()
=>
{
showResult
.
value
=
true
currentPrize
.
value
=
{
prizeId
:
null
,
prizeImageUrl
:
''
,
prizeName
:
'谢谢参与'
,
blessingText
:
'拒绝精神内耗 本人配享太庙'
,
coinCost
:
0
,
isWin
:
false
,
}
}
</
script
>
</
script
>
<
style
>
<
style
>
/* 中奖弹层动画 */
.result-enter-active
{
.result-enter-active
{
animation
:
fadeIn
0.3
s
ease
;
animation
:
resultFadeIn
0.42
s
ease
;
}
}
@keyframes
fadeIn
{
.result-leave-active
{
animation
:
resultFadeOut
0.24s
ease
forwards
;
}
@keyframes
resultFadeIn
{
from
{
from
{
opacity
:
0
;
opacity
:
0
;
transform
:
scale
(
1.
1
);
transform
:
scale
(
1.
03
);
}
}
to
{
to
{
opacity
:
1
;
opacity
:
1
;
transform
:
scale
(
1
);
transform
:
scale
(
1
);
}
}
}
}
/* 礼物弹跳 */
@keyframes
resultFadeOut
{
from
{
opacity
:
1
;
transform
:
scale
(
1
);
}
to
{
opacity
:
0
;
transform
:
scale
(
0.985
);
}
}
.animate-pop
{
.animate-pop
{
animation
:
pop
0.8s
ease-out
;
animation
:
pop
0.8s
ease-out
;
}
}
@keyframes
pop
{
@keyframes
pop
{
0
%
{
0
%
{
transform
:
scale
(
0.3
);
transform
:
scale
(
0.3
);
...
@@ -161,4 +503,351 @@ function handlePrize(prize: Prize) {
...
@@ -161,4 +503,351 @@ function handlePrize(prize: Prize) {
transform
:
scale
(
1
);
transform
:
scale
(
1
);
}
}
}
}
.blessing-wrap
{
position
:
relative
;
width
:
min
(
92vw
,
520px
);
min-height
:
min
(
84vh
,
680px
);
display
:
flex
;
align-items
:
flex-end
;
justify-content
:
center
;
isolation
:
isolate
;
}
.blessing-glow
{
position
:
absolute
;
left
:
50%
;
bottom
:
120px
;
width
:
390px
;
height
:
390px
;
border-radius
:
50%
;
background
:
radial-gradient
(
circle
,
rgba
(
255
,
223
,
119
,
0.44
)
0%
,
rgba
(
255
,
204
,
63
,
0.14
)
45%
,
rgba
(
255
,
204
,
63
,
0
)
76%
);
transform
:
translateX
(
-50%
);
filter
:
blur
(
18px
);
z-index
:
0
;
pointer-events
:
none
;
}
.blessing-fan-scene
{
position
:
relative
;
width
:
min
(
92vw
,
520px
);
height
:
510px
;
display
:
flex
;
align-items
:
flex-end
;
justify-content
:
center
;
z-index
:
1
;
}
.blessing-fan
{
position
:
absolute
;
left
:
50%
;
bottom
:
28px
;
width
:
min
(
92vw
,
520px
);
height
:
408px
;
transform
:
translateX
(
-50%
);
z-index
:
1
;
}
/* .blessing-tag-slot {
position: absolute;
left: 50%;
bottom: 56px;
width: 138px;
height: 100px;
border-radius: 999px 999px 30px 30px;
background:
linear-gradient(180deg, rgba(107, 68, 0, 0.34), rgba(107, 68, 0, 0.06)),
radial-gradient(circle at 50% 0, rgba(255, 233, 165, 0.42), rgba(255, 233, 165, 0) 74%);
box-shadow:
inset 0 10px 18px rgba(87, 51, 0, 0.18),
0 10px 22px rgba(0, 0, 0, 0.16);
transform: translateX(-50%);
z-index: 2;
} */
.blessing-tag-stage
{
position
:
absolute
;
left
:
50%
;
bottom
:
28px
;
width
:
188px
;
height
:
550px
;
/* overflow: hidden; */
transform
:
translateX
(
-50%
);
z-index
:
2
;
}
.blessing-fan-item
{
position
:
absolute
;
left
:
50%
;
bottom
:
0
;
width
:
84px
;
height
:
450px
;
transform-origin
:
center
100%
;
transform
:
translateX
(
calc
(
-50%
+
var
(
--x-offset
)))
translateY
(
var
(
--y-offset
))
rotate
(
var
(
--angle
))
scale
(
var
(
--scale
));
}
.blessing-fan-face
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
border-radius
:
16px
16px
11px
11px
;
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
249
,
204
,
0.76
)
0%
,
rgba
(
255
,
236
,
165
,
0.16
)
12%
,
transparent
18%
),
linear-gradient
(
180deg
,
#ffdd68
0%
,
#f7c529
56%
,
#ebb116
100%
);
border
:
1px
solid
rgba
(
123
,
79
,
0
,
0.18
);
box-shadow
:
0
16px
28px
rgba
(
0
,
0
,
0
,
0.18
),
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.45
),
inset
0
-10px
24px
rgba
(
162
,
101
,
0
,
0.12
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
20px
0
18px
;
overflow
:
hidden
;
}
.blessing-fan-face
::before
{
content
:
''
;
position
:
absolute
;
inset
:
9px
;
border-radius
:
11px
;
border
:
1px
solid
rgba
(
142
,
92
,
0
,
0.14
);
pointer-events
:
none
;
}
.blessing-fan-emoji
{
position
:
relative
;
z-index
:
1
;
color
:
rgba
(
94
,
53
,
0
,
0.88
);
font-size
:
15px
;
font-weight
:
700
;
letter-spacing
:
0.5px
;
}
.blessing-fan-text
{
position
:
relative
;
z-index
:
1
;
margin-top
:
20px
;
color
:
rgba
(
96
,
53
,
0
,
0.68
);
font-size
:
17px
;
font-weight
:
600
;
letter-spacing
:
1px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
6px
;
}
.blessing-fan-char
{
text-shadow
:
0
1px
0
rgba
(
255
,
255
,
255
,
0.18
);
}
.blessing-tag
{
position
:
absolute
;
left
:
50%
;
bottom
:
0
;
width
:
120px
;
min-height
:
470px
;
padding
:
20px
0
26px
;
border-radius
:
18px
;
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.36
)
0%
,
rgba
(
255
,
255
,
255
,
0
)
14%
),
linear-gradient
(
180deg
,
#ffe783
0%
,
#ffc93f
48%
,
#f0b416
100%
);
border
:
1px
solid
rgba
(
118
,
74
,
0
,
0.18
);
box-shadow
:
0
24px
54px
rgba
(
0
,
0
,
0
,
0.24
),
0
12px
36px
rgba
(
255
,
205
,
63
,
0.24
),
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.52
),
inset
0
-16px
24px
rgba
(
171
,
100
,
0
,
0.12
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
overflow
:
hidden
;
transform
:
translateX
(
-50%
);
z-index
:
3
;
}
.blessing-tag
::before
{
content
:
''
;
position
:
absolute
;
inset
:
10px
9px
;
border-radius
:
14px
;
border
:
1px
solid
rgba
(
125
,
81
,
0
,
0.16
);
pointer-events
:
none
;
}
.blessing-tag
::after
{
content
:
''
;
position
:
absolute
;
left
:
16px
;
right
:
16px
;
top
:
12px
;
height
:
92px
;
border-radius
:
999px
;
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.24
),
rgba
(
255
,
255
,
255
,
0
));
filter
:
blur
(
4px
);
pointer-events
:
none
;
}
.blessing-tag-top
{
position
:
relative
;
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
}
.blessing-tag-hole
{
width
:
13px
;
height
:
13px
;
border-radius
:
50%
;
background
:
rgba
(
116
,
72
,
0
,
0.28
);
box-shadow
:
inset
0
1px
2px
rgba
(
0
,
0
,
0
,
0.14
);
}
.blessing-tag-knot
{
width
:
2px
;
height
:
26px
;
margin-top
:
4px
;
border-radius
:
999px
;
background
:
linear-gradient
(
180deg
,
rgba
(
125
,
82
,
0
,
0.72
),
rgba
(
125
,
82
,
0
,
0.2
));
}
.blessing-tag-headline
{
margin-top
:
6px
;
color
:
rgba
(
120
,
69
,
0
,
0.56
);
font-size
:
11px
;
font-weight
:
800
;
letter-spacing
:
3px
;
}
.blessing-tag-divider
{
width
:
56px
;
height
:
1px
;
margin
:
12px
0
18px
;
background
:
linear-gradient
(
90deg
,
rgba
(
133
,
86
,
0
,
0
),
rgba
(
133
,
86
,
0
,
0.46
),
rgba
(
133
,
86
,
0
,
0
)
);
}
.blessing-text
{
color
:
#603500
;
font-size
:
26px
;
font-weight
:
700
;
letter-spacing
:
2px
;
line-height
:
1
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
10px
;
flex
:
1
;
padding
:
0
10px
;
}
.blessing-text.is-long-text
{
font-size
:
23px
;
gap
:
8px
;
}
.blessing-text
>
span
{
text-shadow
:
0
1px
0
rgba
(
255
,
255
,
255
,
0.24
);
}
.blessing-tag-seal
{
margin-top
:
18px
;
padding
:
5px
10px
;
border-radius
:
999px
;
background
:
rgba
(
144
,
41
,
22
,
0.1
);
color
:
rgba
(
123
,
44
,
28
,
0.68
);
font-size
:
10px
;
font-weight
:
800
;
letter-spacing
:
1.8px
;
}
.blessing-caption
{
margin-top
:
34px
;
text-align
:
center
;
}
.blessing-caption-title
{
margin
:
0
;
color
:
rgba
(
255
,
255
,
255
,
0.96
);
font-size
:
34px
;
font-weight
:
700
;
letter-spacing
:
3px
;
}
.blessing-caption-subtitle
{
margin
:
12px
0
0
;
color
:
rgba
(
255
,
255
,
255
,
0.72
);
font-size
:
15px
;
letter-spacing
:
1px
;
}
@media
(
max-width
:
640px
)
{
.blessing-wrap
{
width
:
min
(
94vw
,
390px
);
min-height
:
560px
;
}
.blessing-fan-scene
{
width
:
min
(
94vw
,
390px
);
height
:
434px
;
}
.blessing-fan
{
width
:
min
(
94vw
,
390px
);
height
:
350px
;
bottom
:
24px
;
}
.blessing-fan-item
{
width
:
70px
;
height
:
304px
;
}
.blessing-tag-stage
{
width
:
160px
;
height
:
484px
;
bottom
:
24px
;
}
.blessing-tag
{
width
:
118px
;
min-height
:
410px
;
}
.blessing-fan-text
{
font-size
:
14px
;
gap
:
6px
;
}
.blessing-text
{
font-size
:
23px
;
gap
:
8px
;
}
.blessing-text.is-long-text
{
font-size
:
20px
;
gap
:
7px
;
}
.blessing-caption-title
{
font-size
:
28px
;
}
}
</
style
>
</
style
>
src/components/common/MessageBox/index.vue
View file @
0519d87a
...
@@ -66,7 +66,10 @@
...
@@ -66,7 +66,10 @@
</p>
</p>
<!-- Actions -->
<!-- Actions -->
<div
class=
"mt-5 grid grid-cols-2 gap-2.5"
>
<div
class=
"mt-5 grid grid-cols-2 gap-2.5"
:class=
"
{ 'grid-cols-1!': !showCancelButton || !showConfirmButton }"
>
<button
<button
v-if=
"showCancelButton"
v-if=
"showCancelButton"
type=
"button"
type=
"button"
...
...
src/views/homePage/index.vue
View file @
0519d87a
...
@@ -71,7 +71,7 @@
...
@@ -71,7 +71,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"right flex-col gap-3
flex basis-1/4 xl:basis-1/4 min-w-0
"
>
<div
class=
"right flex-col gap-3
basis-1/4 xl:basis-1/4 min-w-0 hidden sm:flex
"
>
<!-- 等级等相关信息 -->
<!-- 等级等相关信息 -->
<div
<div
ref=
"levelContainerRef"
ref=
"levelContainerRef"
...
@@ -350,14 +350,20 @@
...
@@ -350,14 +350,20 @@
</div>
</div>
<!-- 大转盘 -->
<!-- 大转盘 -->
<div
class=
"lottery-container common-box rounded-lg bg-#F5F0FF"
>
<div
<div
class=
"flex items-center gap-2 mb-4"
>
v-if=
"wheelConfig?.isActivityActive"
class=
"lottery-container common-box rounded-lg bg-#F5F0FF"
>
<div
class=
"flex items-center gap-2 xl:mb-4"
>
<div
class=
"w-1 h-4 bg-gradient-to-b from-violet-500 to-purple-500 rounded-full"
></div>
<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>
<h1
class=
"text-sm sm:text-base font-bold"
>
大转盘
</h1>
</div>
</div>
<div
class=
"flex justify-center"
>
<div
class=
"flex justify-center"
>
<LuckyWheel
/>
<LuckyWheel
/>
</div>
</div>
<div
class=
"flex items-center justify-center text-sm text-gray-500 xl:mt-4 px-1 truncate"
>
每次抽奖
{{
wheelConfig
?.
costYaCoin
}}
YA币
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -384,6 +390,7 @@ import {
...
@@ -384,6 +390,7 @@ import {
getRecordData
,
getRecordData
,
getUserDailyLotteryInfo
,
getUserDailyLotteryInfo
,
userJoinLottery
,
userJoinLottery
,
getWheelConfig
,
}
from
'@/api'
}
from
'@/api'
import
{
TaskTypeEnum
,
TaskDateLimitTypeText
,
ArticleTypeEnum
}
from
'@/constants'
import
{
TaskTypeEnum
,
TaskDateLimitTypeText
,
ArticleTypeEnum
}
from
'@/constants'
import
type
{
import
type
{
...
@@ -392,6 +399,7 @@ import type {
...
@@ -392,6 +399,7 @@ import type {
UserAccountDataDto
,
UserAccountDataDto
,
UserRecordDataDto
,
UserRecordDataDto
,
DailyLotteryInfo
,
DailyLotteryInfo
,
WheelConfigDto
,
}
from
'@/api'
}
from
'@/api'
import
{
TABS_REF_KEY
,
levelListOptions
}
from
'@/constants'
import
{
TABS_REF_KEY
,
levelListOptions
}
from
'@/constants'
import
{
useScrollTop
}
from
'@/hooks'
import
{
useScrollTop
}
from
'@/hooks'
...
@@ -532,6 +540,11 @@ const handleLottery = async () => {
...
@@ -532,6 +540,11 @@ const handleLottery = async () => {
push.success('参与每日抽奖成功!')
push.success('参与每日抽奖成功!')
}
}
/**
* 大转盘
*/
const wheelConfig = ref<WheelConfigDto>(null)
const handleTask = async (item: TaskItemDto) => {
const handleTask = async (item: TaskItemDto) => {
if (item.currentCount === item.limitCount) return
if (item.currentCount === item.limitCount) return
// 先暂时写死
// 先暂时写死
...
@@ -584,7 +597,8 @@ const initPage = () => {
...
@@ -584,7 +597,8 @@ const initPage = () => {
getUserAccountData(),
getUserAccountData(),
getRecordData(),
getRecordData(),
getUserDailyLotteryInfo(),
getUserDailyLotteryInfo(),
]).then(([r1, r2, r3, r4]) => {
getWheelConfig(),
]).then(([r1, r2, r3, r4, r5]) => {
if (r1.status === 'fulfilled') {
if (r1.status === 'fulfilled') {
carouselList.value = r1.value.data
carouselList.value = r1.value.data
}
}
...
@@ -598,6 +612,9 @@ const initPage = () => {
...
@@ -598,6 +612,9 @@ const initPage = () => {
if (r4.status === 'fulfilled') {
if (r4.status === 'fulfilled') {
lotteryPrizesDetail.value = r4.value.data
lotteryPrizesDetail.value = r4.value.data
}
}
if (r5.status === 'fulfilled') {
wheelConfig.value = r5.value.data
}
})
})
}
}
...
@@ -623,6 +640,7 @@ onActivated(async () => {
...
@@ -623,6 +640,7 @@ onActivated(async () => {
refreshTaskData(false)
refreshTaskData(false)
refreshUserAccountData()
refreshUserAccountData()
getLotteryPrizesDetail()
getLotteryPrizesDetail()
if (route.fullPath.includes('#levelContainerRef')) {
if (route.fullPath.includes('#levelContainerRef')) {
await handleBackTop()
await handleBackTop()
open.value = true
open.value = true
...
...
src/views/userPage/components/selfActivity.vue
View file @
0519d87a
...
@@ -66,10 +66,10 @@
...
@@ -66,10 +66,10 @@
</
template
>
</
template
>
<
script
lang=
"tsx"
setup
>
<
script
lang=
"tsx"
setup
>
import
{
getSelfAuctionRecord
,
getUserLotteryRecordList
}
from
'@/api'
import
{
getSelfAuctionRecord
,
getUserLotteryRecordList
,
getUserWheelRecordList
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
usePageSearch
}
from
'@/hooks'
import
{
ActivityTypeEnum
}
from
'@/constants/enums'
import
{
ActivityTypeEnum
}
from
'@/constants/enums'
import
type
{
UserLotteryRecordItemDto
}
from
'@/api/dailyLottery/types
'
import
type
{
UserLotteryRecordItemDto
,
UserWheelRecordItemDto
}
from
'@/api
'
const
EmptyComp
=
()
=>
(
const
EmptyComp
=
()
=>
(
<
div
class
=
"flex flex-col items-center justify-center h-64"
>
<
div
class
=
"flex flex-col items-center justify-center h-64"
>
...
@@ -182,6 +182,56 @@ const activityTypeListOptions = [
...
@@ -182,6 +182,56 @@ const activityTypeListOptions = [
),
),
refresh
:
()
=>
refresh2
(),
refresh
:
()
=>
refresh2
(),
},
},
{
label
:
'大转盘'
,
value
:
ActivityTypeEnum
.
WHEEL
,
component
:
()
=>
(
<>
{
!
list3
.
value
.
length
?
(
<
EmptyComp
/>
)
:
(
<>
<
div
class
=
"space-y-4"
>
<
el
-
table
height
=
"500"
data
=
{
list3
.
value
}
stripe
border
loading
=
{
loading3
.
value
}
>
<
el
-
table
-
column
prop
=
"prizeName"
label
=
"名称"
>
{({
row
}:
{
row
:
UserWheelRecordItemDto
})
=>
(
<
div
>
{
row
.
prizeName
??
`谢谢参与(
${
row
.
blessingText
}
)`
}
<
/div
>
)}
<
/el-table-column
>
<
el
-
table
-
column
prop
=
"createdAtStr"
label
=
"参与时间"
/>
<
el
-
table
-
column
prop
=
"isLotteryDone"
label
=
"是否中奖"
>
{({
row
}:
{
row
:
UserWheelRecordItemDto
})
=>
(
<
div
>
{
row
.
isWin
?
(
<
span
class
=
"text-green-500"
>
是
<
/span
>
)
:
(
<
span
class
=
"text-red-500"
>
否
<
/span
>
)}
<
/div
>
)}
<
/el-table-column
>
<
/el-table
>
<
/div
>
<
div
class
=
"flex items-center justify-end px-6 py-4 border-t border-gray-200"
>
<
div
class
=
"pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-2"
>
<
el
-
pagination
v
-
model
:
current
-
page
=
{
searchParams3
.
value
.
current
}
v
-
model
:
page
-
size
=
{
searchParams3
.
value
.
size
}
onSizeChange
=
{
changePageSize3
}
onCurrentChange
=
{
goToPage3
}
page
-
sizes
=
{[
10
,
20
,
30
,
40
]}
layout
=
"prev, pager, next, jumper, total"
total
=
{
total3
.
value
}
class
=
"custom-pagination"
/>
<
/div
>
<
/div
>
<
/
>
)}
<
/
>
),
refresh
:
()
=>
refresh3
(),
},
]
]
const
tab
=
ref
(
activityTypeListOptions
[
0
]
!
.
value
)
const
tab
=
ref
(
activityTypeListOptions
[
0
]
!
.
value
)
...
@@ -212,7 +262,17 @@ const {
...
@@ -212,7 +262,17 @@ const {
}
=
usePageSearch
(
getUserLotteryRecordList
,
{
}
=
usePageSearch
(
getUserLotteryRecordList
,
{
immediate
:
false
,
immediate
:
false
,
})
})
const
{
list
:
list3
,
loading
:
loading3
,
searchParams
:
searchParams3
,
total
:
total3
,
refresh
:
refresh3
,
goToPage
:
goToPage3
,
changePageSize
:
changePageSize3
,
}
=
usePageSearch
(
getUserWheelRecordList
,
{
immediate
:
false
,
})
const
refresh
=
()
=>
{
const
refresh
=
()
=>
{
activityTypeListOptions
.
find
((
item
)
=>
item
.
value
===
tab
.
value
)?.
refresh
?.()
activityTypeListOptions
.
find
((
item
)
=>
item
.
value
===
tab
.
value
)?.
refresh
?.()
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment