refactor(live): 重构直播相关功能和状态管理

-将 fullLive 状态从 goodStore 移动到 liveStore
- 优化 liveRoom 页面逻辑,移除不必要的 props
- 更新 AppHeader 组件,使用 liveStore 中的 fullLive 状态
- 删除 floatingVideo 组件
- 调整 liveMinWindow 组件,增加 onClick 事件处理
- 更新 home 页面,使用 liveStore 中的 fullLive 状态
- 优化 liveRoom 中的 PaymentInput 和 SideButton 组件
This commit is contained in:
xingyy 2025-02-11 15:27:31 +08:00
parent 48108c5e84
commit def6a6b71e
9 changed files with 28 additions and 251 deletions

View File

@ -1,10 +1,9 @@
<script setup> <script setup>
import { useAppHeaderRouteNames as routeWhiteList } from '@/config' import { useAppHeaderRouteNames as routeWhiteList } from '@/config'
import { goodStore } from "@/stores/goods/index.js"; import { liveStore } from "@/stores/live/index.js";
const { fullLive } = goodStore() const { fullLive } = liveStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
function onBack() { function onBack() {
if (fullLive.value){ if (fullLive.value){
fullLive.value=false fullLive.value=false

View File

@ -1,207 +0,0 @@
<template>
<div
v-show="isVisible"
:class="['floating-video', { minimized: isMinimized }]"
class="!aspect-video"
:style="[positionStyle, containerStyle]"
@touchstart.prevent="handleTouchStart"
@touchmove.prevent="handleTouchMove"
@touchend="handleTouchEnd"
>
<video
ref="videoRef"
class="video-player "
controls
@loadedmetadata="handleVideoMetadata"
>
<source src="@/static/video/example.mp4" type="video/mp4" />
您的浏览器不支持 HTML5 视频
</video>
<div class="control-bar">
<div class="minimize-btn" @click="toggleMinimize">
<span v-if="isMinimized"></span>
<span v-else></span>
</div>
<div class="close-btn" @click="closeVideo"></div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
const props = defineProps({
isVisible: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['close'])
const videoRef = ref(null)
const aspectRatio = ref(16 / 9) //
const isMinimized = ref(false)
const position = ref({ x: 20, y: 20 })
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
// 0
const windowSize = ref({
width: 0,
height: 0
})
const handleVideoMetadata = () => {
const video = videoRef.value
if (video.videoWidth && video.videoHeight) {
aspectRatio.value = video.videoWidth / video.videoHeight
}
}
const containerDimensions = computed(() => {
const baseWidth = isMinimized.value ? 150 : 300
const height = baseWidth / aspectRatio.value
return { width: baseWidth, height }
})
const containerStyle = computed(() => {
return {
width: `${containerDimensions.value.width}px`,
height: `${containerDimensions.value.height}px`
}
})
const positionStyle = computed(() => ({
transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
}))
const handleTouchStart = (event) => {
isDragging.value = true
const touch = event.touches[0]
dragStart.value = {
x: touch.clientX - position.value.x,
y: touch.clientY - position.value.y
}
}
const handleTouchMove = (event) => {
if (!isDragging.value) return
const touch = event.touches[0]
let newX = touch.clientX - dragStart.value.x
let newY = touch.clientY - dragStart.value.y
//
const maxX = windowSize.value.width - containerDimensions.value.width
const maxY = windowSize.value.height - containerDimensions.value.height
newX = Math.max(0, Math.min(newX, maxX))
newY = Math.max(0, Math.min(newY, maxY))
requestAnimationFrame(() => {
position.value = { x: newX, y: newY }
})
}
const handleTouchEnd = () => {
isDragging.value = false
}
const toggleMinimize = () => {
isMinimized.value = !isMinimized.value
const maxX = windowSize.value.width - containerDimensions.value.width
const maxY = windowSize.value.height - containerDimensions.value.height
position.value = {
x: Math.max(0, Math.min(position.value.x, maxX)),
y: Math.max(0, Math.min(position.value.y, maxY))
}
}
const closeVideo = () => {
emit('close')
}
const handleResize = () => {
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
}
const maxX = windowSize.value.width - containerDimensions.value.width
const maxY = windowSize.value.height - containerDimensions.value.height
position.value = {
x: Math.max(0, Math.min(position.value.x, maxX)),
y: Math.max(0, Math.min(position.value.y, maxY))
}
}
//
onMounted(() => {
//
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
}
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
})
</script>
<style scoped>
/* 样式部分保持不变 */
.floating-video {
position: fixed;
z-index: 1000;
background: #000;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
transition: width 0.3s ease, height 0.3s ease;
overflow: hidden;
transform: translate3d(0, 0, 0);
will-change: transform;
touch-action: none;
}
.video-player {
width: 100%;
height: 100%;
object-fit: fill!important;
}
.control-bar {
position: absolute;
top: 0;
right: 0;
display: flex;
gap: 8px;
padding: 8px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
}
.minimize-btn,
.close-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
cursor: pointer;
color: white;
font-size: 14px;
}
.minimize-btn:hover,
.close-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
</style>

View File

@ -4,13 +4,13 @@
class="fixed rounded-5px overflow-hidden shadow-lg cursor-move z-50" class="fixed rounded-5px overflow-hidden shadow-lg cursor-move z-50"
@mousedown.stop="handleDragStart" @mousedown.stop="handleDragStart"
@touchstart.stop="handleDragStart"> @touchstart.stop="handleDragStart">
<div class="relative"> <div class="relative" @click.stop="handleClick">
<img :src="props.snapshot" <img :src="props.snapshot"
class="w-80px object-cover" class="w-80px object-cover"
alt="直播画面"> alt="直播画面">
<div class="absolute inset-0 bg-black/40 flex items-center justify-center" <div class="absolute inset-0 bg-black/40 flex items-center justify-center"
@click.stop="handleClick"> >
<span class="text-white text-12px">点击回到直播</span> <span class="text-white text-12px">点击回到直播</span>
</div> </div>
<button @click.stop="handleClose" <button @click.stop="handleClose"
@ -35,6 +35,10 @@ const props = defineProps({
type: Function, type: Function,
default: () => {} default: () => {}
}, },
onClick: {
type: Function,
default: () => {}
},
initialPosition: { initialPosition: {
type: Object, type: Object,
default: () => ({ default: () => ({
@ -46,7 +50,8 @@ const props = defineProps({
const router = useRouter() const router = useRouter()
const dragRef = shallowRef(null) const dragRef = shallowRef(null)
const route = useRoute()
console.log('route',route)
const isDragging = ref(false) const isDragging = ref(false)
const startX = ref(0) const startX = ref(0)
const startY = ref(0) const startY = ref(0)
@ -131,8 +136,7 @@ const handleClick = (event) => {
} }
} }
const handleReturnLive = () => { const handleReturnLive = () => {
router.push('/') props.onClick()
props.onClose()
} }
const handleClose = (event) => { const handleClose = (event) => {

View File

@ -4,10 +4,9 @@ import {goodStore} from "@/stores/goods/index.js";
import ItemList from './components/ItemList/index.vue' import ItemList from './components/ItemList/index.vue'
import Cescribe from './components/Cescribe/index.vue' import Cescribe from './components/Cescribe/index.vue'
import {message} from '@/components/x-message/useMessage.js' import {message} from '@/components/x-message/useMessage.js'
import floatingVideo from '@/components/floatingVideo/index.vue'
import {liveStore} from "~/stores/live/index.js"; import {liveStore} from "~/stores/live/index.js";
const {fullLive,getAuctionDetail,auctionDetail} = goodStore(); const {getAuctionDetail,auctionDetail} = goodStore();
const {getLiveLink}= liveStore() const {fullLive}= liveStore()
const changeLive = () => { const changeLive = () => {
fullLive.value = true; fullLive.value = true;
}; };
@ -18,10 +17,8 @@ if (!auctionDetail.value.uuid){
</script> </script>
<template> <template>
<div class="flex-grow-1"> <div class="flex-grow-1">
<!-- <floatingVideo :is-visible="true"></floatingVideo>-->
<client-only> <client-only>
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']" <liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"/>
:fullLive="fullLive"/>
</client-only> </client-only>
<div v-if="!fullLive" class="bg-#fff"> <div v-if="!fullLive" class="bg-#fff">
<van-tabs sticky animated> <van-tabs sticky animated>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import {liveStore} from "~/stores/live/index.js"; import {liveStore} from "~/stores/live/index.js";
import { showMinWindow, hideMinWindow } from '@/components/liveMinWindow/createMinWindow.js' import { showMinWindow, hideMinWindow } from '@/components/liveMinWindow/createMinWindow.js'
const {lastSnapshot} = liveStore() const {lastSnapshot,fullLive} = liveStore()
const props = defineProps({ const props = defineProps({
show: { show: {
type: Boolean, type: Boolean,
@ -51,8 +51,13 @@ const handleCapture = () => {
const imageUrl = captureVideoFrame() const imageUrl = captureVideoFrame()
if (imageUrl) { if (imageUrl) {
lastSnapshot.value=imageUrl lastSnapshot.value=imageUrl
showMinWindow(lastSnapshot.value) showMinWindow(lastSnapshot.value,{
console.log('截图成功:', imageUrl) onClick:()=>{
router.replace('/')
fullLive.value=true
console.log('执行')
}
})
} }
} }
</script> </script>

View File

@ -18,7 +18,7 @@ const openOne = () => {
const paySide = computed(() => { const paySide = computed(() => {
// //
if (auctionData.value.artwork.isSoled && auctionData.value.artwork.buyInfo.userID === userInfo.value.ID) { if (auctionData.value.artwork?.isSoled && auctionData.value.artwork?.buyInfo.userID === userInfo.value.ID) {
return true return true
} else { } else {
return false return false

View File

@ -10,20 +10,11 @@ import paymentInput from '@/pages/liveRoom/components/PaymentInput/index.vue'
import {goodStore} from "@/stores/goods/index.js" import {goodStore} from "@/stores/goods/index.js"
import {message} from "~/components/x-message/useMessage.js" import {message} from "~/components/x-message/useMessage.js"
import {artworkBuy} from "@/api/goods/index.js" import {artworkBuy} from "@/api/goods/index.js"
const {auctionDetail, getAuctionDetail} = goodStore()
const player = ref(null) const player = ref(null)
const {quoteStatus, show, playerId, show1, auctionData, getSocketData, getLiveLink} = liveStore() const {quoteStatus, show, playerId, show1, auctionData, getSocketData, getLiveLink,fullLive} = liveStore()
const isPlayerReady = ref(false) const isPlayerReady = ref(false)
const pullLink = ref('') const pullLink = ref('')
const props = defineProps({
fullLive: {
type: Boolean,
default: true,
},
})
definePageMeta({ definePageMeta({
title: '主页', title: '主页',
@ -69,20 +60,9 @@ onBeforeUnmount(() => {
player.value?.dispose() player.value?.dispose()
player.value = null player.value = null
}) })
watch(()=>fullLive.value, (newVal) => {
const goPay = () => {
show.value = true
}
const fullLive1 = ref(false)
watch(() => props.fullLive, (newVal) => {
if (newVal) { if (newVal) {
getSocketData() getSocketData()
setTimeout(() => {
fullLive1.value = true
}, 400)
} else {
fullLive1.value = false
} }
}) })
@ -107,9 +87,8 @@ const updateShow=()=>{
<template> <template>
<div class="relative h-full"> <div class="relative h-full">
<div :id="playerId" class="w-full h-full"></div> <div :id="playerId" class="w-full h-full"></div>
<transition> <transition>
<div v-if="fullLive1"> <div v-if="fullLive">
<sideButton class="absolute top-196px right-0 z-999"></sideButton> <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" <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)"> style="bottom:calc(var(--safe-area-inset-bottom) + 26px)">

View File

@ -5,7 +5,6 @@ import { artworkList, defaultDetail, artworkDetail } from "@/api/goods/index.js"
export const goodStore = createGlobalState(() => { export const goodStore = createGlobalState(() => {
// 状态定义 // 状态定义
const actionDetails = ref({}) const actionDetails = ref({})
const fullLive = ref(false)
const currentItem = ref({}) const currentItem = ref({})
const myArtWorks = ref([]) const myArtWorks = ref([])
const pageRef = ref({ const pageRef = ref({
@ -95,7 +94,6 @@ export const goodStore = createGlobalState(() => {
return { return {
// 状态 // 状态
actionDetails, actionDetails,
fullLive,
currentItem, currentItem,
myArtWorks, myArtWorks,

View File

@ -10,6 +10,7 @@ import CryptoJS from "crypto-js";
export const liveStore = createGlobalState(() => { export const liveStore = createGlobalState(() => {
const {auctionDetail} = goodStore(); const {auctionDetail} = goodStore();
const { token } = authStore() const { token } = authStore()
const fullLive = ref(false)
const quoteStatus = ref(false) const quoteStatus = ref(false)
const show = ref(false) const show = ref(false)
const cleanup = ref(null) const cleanup = ref(null)
@ -254,6 +255,7 @@ export const liveStore = createGlobalState(() => {
} }
return{ return{
fullLive,
isMinWindow, isMinWindow,
lastSnapshot, lastSnapshot,
liveInfo, liveInfo,