Commit 0519d87a by lijiabin

【需求 20520】 feat: 完成前台大转盘功能

parent feb96717
<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="prizes" :prizes="computedPrizes"
: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="startCallback" @click="popClick"
> >
<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: '25%', background: '#f75a5a' }, { radius: '20%', 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)
}, 3000) }, 500)
} }
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: 60px; width: 50px;
height: 60px; height: 50px;
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);
......
...@@ -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"
......
...@@ -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
......
...@@ -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?.()
} }
......
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