<template> <div ref="dragRef" :style="style" class="fixed rounded-5px overflow-hidden shadow-lg cursor-move z-50" @mousedown.stop="handleDragStart" @touchstart.stop="handleDragStart"> <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" > <span class="text-white text-12px">点击回到直播</span> </div> <button @click.stop="handleClose" class="absolute top-10px right-10px text-white bg-black/40 rounded-full w-5 h-5 flex items-center justify-center hover:bg-black/60 cursor-pointer"> <van-icon name="cross" /> </button> </div> </div> </template> <script setup> import { ref, computed, onBeforeUnmount, shallowRef, onMounted } from 'vue' import { useRouter } from 'vue-router' import { useThrottleFn } from '@vueuse/core' const props = defineProps({ snapshot: { type: String, required: true }, onClose: { type: Function, default: () => {} }, onClick: { type: Function, default: () => {} }, initialPosition: { type: Object, default: () => ({ top: '80px', right: '16px' }) } }) const router = useRouter() const dragRef = shallowRef(null) const route = useRoute() const isDragging = ref(false) const startX = ref(0) const startY = ref(0) const left = ref(0) const top = ref(0) onMounted(() => { const rect = dragRef.value.getBoundingClientRect() left.value = window.innerWidth - rect.width - parseInt(props.initialPosition.right) top.value = parseInt(props.initialPosition.top) }) const style = computed(() => ({ left: left.value ? `${left.value}px` : 'auto', top: top.value ? `${top.value}px` : 'auto', right: !left.value ? props.initialPosition.right : 'auto', transition: isDragging.value ? 'none' : 'all 0.3s ease', })) const handleDragStart = (event) => { event.stopPropagation() isDragging.value = true const point = event.touches ? event.touches[0] : event const rect = dragRef.value.getBoundingClientRect() left.value = rect.left top.value = rect.top startX.value = point.clientX - left.value startY.value = point.clientY - top.value if (event.type === 'mousedown') { document.addEventListener('mousemove', handleDragMove) document.addEventListener('mouseup', handleDragEnd) } else { document.addEventListener('touchmove', handleDragMove, { passive: false }) document.addEventListener('touchend', handleDragEnd) } } const handleDragMove = useThrottleFn((event) => { if (!isDragging.value) return event.preventDefault() event.stopPropagation() const point = event.touches ? event.touches[0] : event const rect = dragRef.value.getBoundingClientRect() const maxX = window.innerWidth - rect.width const maxY = window.innerHeight - rect.height left.value = Math.min(Math.max(0, point.clientX - startX.value), maxX) top.value = Math.min(Math.max(0, point.clientY - startY.value), maxY) }, 16) const handleEdgeSnap = useThrottleFn(() => { const rect = dragRef.value.getBoundingClientRect() const centerX = rect.left + rect.width / 2 left.value = centerX > window.innerWidth / 2 ? window.innerWidth - rect.width - 16 : 16 }, 100) const handleDragEnd = () => { if (!isDragging.value) return isDragging.value = false handleEdgeSnap() document.removeEventListener('mousemove', handleDragMove) document.removeEventListener('mouseup', handleDragEnd) document.removeEventListener('touchmove', handleDragMove) document.removeEventListener('touchend', handleDragEnd) } const handleClick = (event) => { event.stopPropagation() if (!isDragging.value) { handleReturnLive() } } const handleReturnLive = () => { props.onClick() } const handleClose = (event) => { event?.stopPropagation() props.onClose() } watch(() => route.path, (newVal) => { if (['/','/home'].includes(newVal)){ handleClose() } }) onBeforeUnmount(() => { document.removeEventListener('mousemove', handleDragMove) document.removeEventListener('mouseup', handleDragEnd) document.removeEventListener('touchmove', handleDragMove) document.removeEventListener('touchend', handleDragEnd) }) </script> <style scoped> .fixed { will-change: transform, opacity; touch-action: none; -webkit-user-select: none; user-select: none; transform: translateZ(0); } .min-window-enter-active, .min-window-leave-active { transition: transform 0.3s ease, opacity 0.3s ease; } .min-window-enter-from, .min-window-leave-to { transform: translateY(100%); opacity: 0; } img { pointer-events: none; -webkit-user-drag: none; user-drag: none; } </style>