liveh5-nuxt/app/pages/liveRoom/index.client.vue
xingyy a0623e8bd9 feat(liveRoom): 实现直播间价格动画效果
- 引入 gsap 和 countup.js 库
- 实现价格滚动动画效果
- 优化价格显示逻辑
2025-02-17 14:29:31 +08:00

224 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import {ref, onMounted, onBeforeUnmount, watch} from 'vue'
import Aliplayer from 'aliyun-aliplayer'
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
import sideButton from '@/pages/liveRoom/components/SideButton/index.vue'
import broadcast from '@/pages/liveRoom/components/Broadcast/index.vue'
import {liveStore} from "@/stores/live/index.js"
import paymentResults from '@/pages/liveRoom/components/PaymentResults/index.vue'
import paymentInput from '@/pages/liveRoom/components/PaymentInput/index.vue'
import {goodStore} from "@/stores/goods/index.js"
import {message} from "~/components/x-message/useMessage.js"
import {showConfirmDialog} from 'vant';
import {artworkBuy} from "@/api/goods/index.js"
import {useI18n} from 'vue-i18n'
import gsap from 'gsap'
import { CountUp } from 'countup.js'
const countUpRef = ref(null)
const nextPriceRef = ref(null)
const player = ref(null)
const {quoteStatus, show, playerId, show1, auctionData, getSocketData, getLiveLink, fullLive} = liveStore()
const pullLink = ref('')
definePageMeta({
i18n: 'login.title',
})
const handlePlayerError = (error) => {
console.error('播放器错误:', error)
player.value?.play()
}
const initializePlayer = async () => {
try {
if (player.value) {
player.value.dispose()
}
const playerConfig = {
id: playerId.value,
source: pullLink.value,
isLive: true,
preload: true,
autoplay: false,
autoplayPolicy: {
fallbackToMute: true, // 有声自动播放失败后是否降级为静音自动播放默认为false
showUnmuteBtn: true, // 静音自动播放时是否居中显示静音大按钮默认为true
},
controlBarVisibility: 'never',
}
player.value = new Aliplayer(playerConfig, (playerInstance) => {
playerInstance?.play()
})
player.value.on('error', handlePlayerError)
} catch (error) {
showConfirmDialog({
message: useI18n().t('live_room.error_mess'),
showCancelButton: true
}).then(() => {
location.reload()
// on close
}).catch(() => {
// on cancel
})
console.error('播放器初始化失败:', error)
}
}
onMounted(async () => {
pullLink.value = await getLiveLink()
initializePlayer()
})
onBeforeUnmount(() => {
player.value?.dispose()
player.value = null
})
const createCountUp = (element, value, options = {}) => {
const defaultOptions = {
duration: 1.5,
separator: ',',
decimal: '.',
decimalPlaces: 0,
enableScrollSpy: true,
}
const countUp = new CountUp(element, Number(value), { ...defaultOptions, ...options })
if (!countUp.error) {
countUp.start()
} else {
console.error(countUp.error)
}
return countUp
}
watch(() => fullLive.value, async (newVal) => {
if (!newVal) return
await getSocketData()
nextTick(() => {
const { nowAuctionPrice } = auctionData.value
createCountUp(countUpRef.value, nowAuctionPrice.nowPrice)
createCountUp(nextPriceRef.value, nowAuctionPrice.nextPrice)
})
})
const goBuy = async () => {
const res = await artworkBuy({
auctionArtworkUuid: auctionData.value?.artwork?.uuid,
buyMoney: String(auctionData.value?.nowAuctionPrice?.nextPrice ?? 0)
})
if (res.status === 0) {
message.success(useI18n().t('live_room.success_mess'))
}
}
const tipOpen = () => {
message.warning(useI18n().t('live_room.warn_mess'))
}
</script>
<template>
<div class="relative h-full">
<div :id="playerId" class="w-full h-full"></div>
<transition name="fade">
<div v-if="fullLive">
<sideButton class="absolute top-196px right-0 z-999"></sideButton>
<div class="absolute left-1/2 transform -translate-x-1/2 flex flex-col items-center"
style="bottom:calc(var(--safe-area-inset-bottom) + 26px)">
<div class="text-16px text-#FFB25F font-600">
{{ $t('live_room.now_price') }}{{ auctionData?.nowAuctionPrice?.currency }}
<span ref="countUpRef" ></span>
</div>
<div class="text-16px text-#fff font-600">
{{ $t('live_room.lower_price') }}{{ auctionData?.nowAuctionPrice?.currency }}
<span ref="nextPriceRef" ></span>
</div>
<div v-if="quoteStatus" class="mt-10px mb-10px">
<van-button @click="goBuy" color="#FFB25F" class="w-344px !h-[40px]">
<div>{{
$t('live_room.confirm')` ${auctionData?.nowAuctionPrice?.currency} ${auctionData?.nowAuctionPrice?.nextPrice ?? 0}`
}}
</div>
</van-button>
</div>
<div v-else class="mt-10px mb-10px">
<van-button @click="tipOpen" color="#D6D6D8" class="w-344px !h-[40px]" v-if="!quoteStatus">
<div class="text-#7D7D7F text-14px">{{ $t('live_room.button') }}</div>
</van-button>
</div>
<broadcast></broadcast>
</div>
<paymentInput v-model:show="show"/>
<div>
</div>
<paymentResults v-model:show="show1" type="error"/>
<div v-if="auctionData?.wsType==='newArtwork'"
class="w-344px h-31px rounded-4px absolute top-9px bg-[#151824]/45 backdrop-blur-[10px] backdrop-saturate-[180%] left-1/2 transform translate-x--1/2 flex text-#fff text-14px items-center px-12px line-height-none">
<div class="mr-11px whitespace-nowrap">LOT{{ auctionData.artwork.index }}</div>
<div class="mr-10px truncate">{{ auctionData.artwork.name }}</div>
<div class="whitespace-nowrap">{{ $t('live_room.start') }}</div>
</div>
</div>
</transition>
</div>
</template>
<style lang="scss">
#J_prismPlayer {
width: 100%;
height: 100% !important;
& > video {
width: 100%;
height: 100%;
}
}
</style>
<style scoped>
/* 定义过渡动画 */
.fade-enter-active{
transition: opacity 1s ease;
}
.fade-leave-active {
transition: opacity 0.2s ease;
}
/* 定义进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
}
.my-rolling-text {
--van-rolling-text-item-width: 10px;
--van-rolling-text-font-size: 16px;
--van-rolling-text-color: #FFB25F;
}
.my-rolling-text1 {
--van-rolling-text-item-width: 10px;
--van-rolling-text-font-size: 16px;
--van-rolling-text-color: #FFF;
}
:deep(.prism-license-watermark) {
display: none !important;
}
</style>