refactor(live): 重构直播相关功能和状态管理
-将 fullLive 状态从 goodStore 移动到 liveStore - 优化 liveRoom 页面逻辑,移除不必要的 props - 更新 AppHeader 组件,使用 liveStore 中的 fullLive 状态 - 删除 floatingVideo 组件 - 调整 liveMinWindow 组件,增加 onClick 事件处理 - 更新 home 页面,使用 liveStore 中的 fullLive 状态 - 优化 liveRoom 中的 PaymentInput 和 SideButton 组件
This commit is contained in:
parent
48108c5e84
commit
def6a6b71e
@ -1,10 +1,9 @@
|
||||
<script setup>
|
||||
import { useAppHeaderRouteNames as routeWhiteList } from '@/config'
|
||||
import { goodStore } from "@/stores/goods/index.js";
|
||||
const { fullLive } = goodStore()
|
||||
import { liveStore } from "@/stores/live/index.js";
|
||||
const { fullLive } = liveStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function onBack() {
|
||||
if (fullLive.value){
|
||||
fullLive.value=false
|
||||
|
@ -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>
|
@ -4,13 +4,13 @@
|
||||
class="fixed rounded-5px overflow-hidden shadow-lg cursor-move z-50"
|
||||
@mousedown.stop="handleDragStart"
|
||||
@touchstart.stop="handleDragStart">
|
||||
<div class="relative">
|
||||
<div class="relative" @click.stop="handleClick">
|
||||
<img :src="props.snapshot"
|
||||
class="w-80px object-cover"
|
||||
alt="直播画面">
|
||||
|
||||
<div class="absolute inset-0 bg-black/40 flex items-center justify-center"
|
||||
@click.stop="handleClick">
|
||||
>
|
||||
<span class="text-white text-12px">点击回到直播</span>
|
||||
</div>
|
||||
<button @click.stop="handleClose"
|
||||
@ -35,6 +35,10 @@ const props = defineProps({
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
initialPosition: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
@ -46,7 +50,8 @@ const props = defineProps({
|
||||
|
||||
const router = useRouter()
|
||||
const dragRef = shallowRef(null)
|
||||
|
||||
const route = useRoute()
|
||||
console.log('route',route)
|
||||
const isDragging = ref(false)
|
||||
const startX = ref(0)
|
||||
const startY = ref(0)
|
||||
@ -131,8 +136,7 @@ const handleClick = (event) => {
|
||||
}
|
||||
}
|
||||
const handleReturnLive = () => {
|
||||
router.push('/')
|
||||
props.onClose()
|
||||
props.onClick()
|
||||
}
|
||||
|
||||
const handleClose = (event) => {
|
||||
|
@ -4,10 +4,9 @@ import {goodStore} from "@/stores/goods/index.js";
|
||||
import ItemList from './components/ItemList/index.vue'
|
||||
import Cescribe from './components/Cescribe/index.vue'
|
||||
import {message} from '@/components/x-message/useMessage.js'
|
||||
import floatingVideo from '@/components/floatingVideo/index.vue'
|
||||
import {liveStore} from "~/stores/live/index.js";
|
||||
const {fullLive,getAuctionDetail,auctionDetail} = goodStore();
|
||||
const {getLiveLink}= liveStore()
|
||||
const {getAuctionDetail,auctionDetail} = goodStore();
|
||||
const {fullLive}= liveStore()
|
||||
const changeLive = () => {
|
||||
fullLive.value = true;
|
||||
};
|
||||
@ -18,10 +17,8 @@ if (!auctionDetail.value.uuid){
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex-grow-1">
|
||||
<!-- <floatingVideo :is-visible="true"></floatingVideo>-->
|
||||
<client-only>
|
||||
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"
|
||||
:fullLive="fullLive"/>
|
||||
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"/>
|
||||
</client-only>
|
||||
<div v-if="!fullLive" class="bg-#fff">
|
||||
<van-tabs sticky animated>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import {liveStore} from "~/stores/live/index.js";
|
||||
import { showMinWindow, hideMinWindow } from '@/components/liveMinWindow/createMinWindow.js'
|
||||
const {lastSnapshot} = liveStore()
|
||||
const {lastSnapshot,fullLive} = liveStore()
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
@ -51,8 +51,13 @@ const handleCapture = () => {
|
||||
const imageUrl = captureVideoFrame()
|
||||
if (imageUrl) {
|
||||
lastSnapshot.value=imageUrl
|
||||
showMinWindow(lastSnapshot.value)
|
||||
console.log('截图成功:', imageUrl)
|
||||
showMinWindow(lastSnapshot.value,{
|
||||
onClick:()=>{
|
||||
router.replace('/')
|
||||
fullLive.value=true
|
||||
console.log('执行')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -18,7 +18,7 @@ const openOne = () => {
|
||||
|
||||
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
|
||||
} else {
|
||||
return false
|
||||
|
@ -10,20 +10,11 @@ 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 {artworkBuy} from "@/api/goods/index.js"
|
||||
|
||||
|
||||
const {auctionDetail, getAuctionDetail} = goodStore()
|
||||
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 pullLink = ref('')
|
||||
|
||||
const props = defineProps({
|
||||
fullLive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
title: '主页',
|
||||
@ -69,20 +60,9 @@ onBeforeUnmount(() => {
|
||||
player.value?.dispose()
|
||||
player.value = null
|
||||
})
|
||||
|
||||
const goPay = () => {
|
||||
show.value = true
|
||||
}
|
||||
|
||||
const fullLive1 = ref(false)
|
||||
watch(() => props.fullLive, (newVal) => {
|
||||
watch(()=>fullLive.value, (newVal) => {
|
||||
if (newVal) {
|
||||
getSocketData()
|
||||
setTimeout(() => {
|
||||
fullLive1.value = true
|
||||
}, 400)
|
||||
} else {
|
||||
fullLive1.value = false
|
||||
}
|
||||
})
|
||||
|
||||
@ -107,9 +87,8 @@ const updateShow=()=>{
|
||||
<template>
|
||||
<div class="relative h-full">
|
||||
<div :id="playerId" class="w-full h-full"></div>
|
||||
|
||||
<transition>
|
||||
<div v-if="fullLive1">
|
||||
<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)">
|
||||
|
@ -5,7 +5,6 @@ import { artworkList, defaultDetail, artworkDetail } from "@/api/goods/index.js"
|
||||
export const goodStore = createGlobalState(() => {
|
||||
// 状态定义
|
||||
const actionDetails = ref({})
|
||||
const fullLive = ref(false)
|
||||
const currentItem = ref({})
|
||||
const myArtWorks = ref([])
|
||||
const pageRef = ref({
|
||||
@ -95,7 +94,6 @@ export const goodStore = createGlobalState(() => {
|
||||
return {
|
||||
// 状态
|
||||
actionDetails,
|
||||
fullLive,
|
||||
|
||||
currentItem,
|
||||
myArtWorks,
|
||||
|
@ -10,6 +10,7 @@ import CryptoJS from "crypto-js";
|
||||
export const liveStore = createGlobalState(() => {
|
||||
const {auctionDetail} = goodStore();
|
||||
const { token } = authStore()
|
||||
const fullLive = ref(false)
|
||||
const quoteStatus = ref(false)
|
||||
const show = ref(false)
|
||||
const cleanup = ref(null)
|
||||
@ -254,6 +255,7 @@ export const liveStore = createGlobalState(() => {
|
||||
|
||||
}
|
||||
return{
|
||||
fullLive,
|
||||
isMinWindow,
|
||||
lastSnapshot,
|
||||
liveInfo,
|
||||
|
Loading…
Reference in New Issue
Block a user