feat(components): 添加浮动气泡组件并优化直播相关页面
- 在首页引入 FloatingBubble 组件 - 移除 liveRoom 页面的 SideButton 组件中的拖动功能 - 在 liveRoom 页面中使用 floating2 组件包裹 SideButton - 优化 signature 页面布局 - 在 nuxt.config.js 中启用 vscode 配置
This commit is contained in:
parent
da4a2c509c
commit
4d358d130b
93
app/components/floating2/index.vue
Normal file
93
app/components/floating2/index.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div
|
||||
ref="bubbleRef"
|
||||
class="floating-bubble"
|
||||
:style="bubbleStyle"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useThrottleFn } from '@vueuse/core'
|
||||
|
||||
const props = defineProps({
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const bubbleRef = ref(null)
|
||||
const position = ref({
|
||||
right: props.offset,
|
||||
bottom: 100
|
||||
})
|
||||
|
||||
let startTouch = { x: 0, y: 0 }
|
||||
let startPos = { right: 0, bottom: 0 }
|
||||
let isDragging = false
|
||||
|
||||
const bubbleStyle = computed(() => ({
|
||||
position: 'fixed',
|
||||
right: `${position.value.right}px`,
|
||||
bottom: `${position.value.bottom}px`,
|
||||
zIndex: 99999,
|
||||
transition: isDragging ? 'none' : 'all 0.3s'
|
||||
}))
|
||||
|
||||
const onTouchStart = (event) => {
|
||||
isDragging = true
|
||||
const touch = event.touches[0]
|
||||
startTouch = { x: touch.clientX, y: touch.clientY }
|
||||
startPos = { ...position.value }
|
||||
}
|
||||
|
||||
const onTouchMove = useThrottleFn((event) => {
|
||||
if (!isDragging) return
|
||||
event.preventDefault()
|
||||
|
||||
const touch = event.touches[0]
|
||||
const { clientWidth, clientHeight } = document.documentElement
|
||||
const rect = bubbleRef.value.getBoundingClientRect()
|
||||
|
||||
// 计算新位置
|
||||
const deltaX = startTouch.x - touch.clientX
|
||||
const deltaY = startTouch.y - touch.clientY
|
||||
const newRight = startPos.right + deltaX
|
||||
const newBottom = startPos.bottom + deltaY
|
||||
|
||||
// 边界检测
|
||||
position.value = {
|
||||
right: Math.min(Math.max(newRight, props.offset),
|
||||
clientWidth - rect.width - props.offset),
|
||||
bottom: Math.min(Math.max(newBottom, props.offset),
|
||||
clientHeight - rect.height - props.offset)
|
||||
}
|
||||
}, 16)
|
||||
|
||||
const onTouchEnd = () => {
|
||||
if (!isDragging) return
|
||||
isDragging = false
|
||||
|
||||
const { clientWidth } = document.documentElement
|
||||
const rect = bubbleRef.value.getBoundingClientRect()
|
||||
const left = clientWidth - position.value.right - rect.width
|
||||
|
||||
// 吸附
|
||||
position.value.right = left < clientWidth / 2
|
||||
? clientWidth - rect.width - props.offset
|
||||
: props.offset
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.floating-bubble {
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
@ -3,6 +3,7 @@ import liveRoom from '@/pages/liveRoom/index.client.vue';
|
||||
import {goodStore} from "@/stores/goods/index.js";
|
||||
import ItemList from './components/ItemList/index.vue'
|
||||
import Cescribe from './components/Cescribe/index.vue'
|
||||
import FloatingBubble from '~/components/floating2/index.vue'
|
||||
|
||||
import {liveStore} from "~/stores/live/index.js";
|
||||
const {auctionDetail,getArtworkList} = goodStore();
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue"
|
||||
import { ref, computed } from "vue"
|
||||
import lockClosed from "@/static/images/lockdfd@2x.png"
|
||||
import lockOpen from "@/static/images/lock4@2x.png"
|
||||
import { liveStore } from "@/stores/live/index.js"
|
||||
@ -15,104 +15,6 @@ const { pageRef } = goodStore()
|
||||
const { userInfo ,payment} = authStore()
|
||||
const showTang = ref(false)
|
||||
const router = useRouter()
|
||||
// 拖动相关状态
|
||||
const isDragging = ref(false)
|
||||
const startX = ref(0)
|
||||
const startY = ref(0)
|
||||
const currentTop = ref(196)
|
||||
const currentLeft = ref(window.innerWidth - 60)
|
||||
const sidebarRef = ref(null)
|
||||
|
||||
// 限制拖动范围
|
||||
const minTop = 0
|
||||
const maxTop = window.innerHeight - 180
|
||||
const minLeft = 0
|
||||
const maxLeft = window.innerWidth - 60
|
||||
|
||||
// 吸附相关
|
||||
const snapThreshold = 30
|
||||
const snapPosition = () => {
|
||||
const centerX = currentLeft.value + 30
|
||||
if (centerX < window.innerWidth / 2) {
|
||||
currentLeft.value = 0
|
||||
} else {
|
||||
currentLeft.value = window.innerWidth - 60
|
||||
}
|
||||
}
|
||||
|
||||
// 开始拖动
|
||||
const handleMouseDown = (e) => {
|
||||
isDragging.value = true
|
||||
startX.value = e.clientX - currentLeft.value
|
||||
startY.value = e.clientY - currentTop.value
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
// 拖动中
|
||||
const handleMouseMove = (e) => {
|
||||
if (!isDragging.value) return
|
||||
|
||||
let newTop = e.clientY - startY.value
|
||||
let newLeft = e.clientX - startX.value
|
||||
|
||||
// 限制范围
|
||||
newTop = Math.max(minTop, Math.min(newTop, maxTop))
|
||||
newLeft = Math.max(minLeft, Math.min(newLeft, maxLeft))
|
||||
|
||||
currentTop.value = newTop
|
||||
currentLeft.value = newLeft
|
||||
|
||||
// 更新位置
|
||||
if (sidebarRef.value) {
|
||||
sidebarRef.value.style.top = `${newTop}px`
|
||||
sidebarRef.value.style.left = `${newLeft}px`
|
||||
}
|
||||
}
|
||||
|
||||
// 结束拖动
|
||||
const handleMouseUp = () => {
|
||||
isDragging.value = false
|
||||
snapPosition()
|
||||
if (sidebarRef.value) {
|
||||
sidebarRef.value.style.left = `${currentLeft.value}px`
|
||||
}
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
// 触摸事件处理
|
||||
const handleTouchStart = (e) => {
|
||||
isDragging.value = true
|
||||
startX.value = e.touches[0].clientX - currentLeft.value
|
||||
startY.value = e.touches[0].clientY - currentTop.value
|
||||
}
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
if (!isDragging.value) return
|
||||
|
||||
let newTop = e.touches[0].clientY - startY.value
|
||||
let newLeft = e.touches[0].clientX - startX.value
|
||||
|
||||
newTop = Math.max(minTop, Math.min(newTop, maxTop))
|
||||
newLeft = Math.max(minLeft, Math.min(newLeft, maxLeft))
|
||||
|
||||
currentTop.value = newTop
|
||||
currentLeft.value = newLeft
|
||||
|
||||
if (sidebarRef.value) {
|
||||
sidebarRef.value.style.top = `${newTop}px`
|
||||
sidebarRef.value.style.left = `${newLeft}px`
|
||||
}
|
||||
}
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
isDragging.value = false
|
||||
snapPosition()
|
||||
if (sidebarRef.value) {
|
||||
sidebarRef.value.style.left = `${currentLeft.value}px`
|
||||
}
|
||||
}
|
||||
|
||||
const captureVideoFrame = () => {
|
||||
try {
|
||||
@ -134,17 +36,6 @@ const captureVideoFrame = () => {
|
||||
}
|
||||
|
||||
const handleCapture = () => {
|
||||
/* const imageUrl = captureVideoFrame()
|
||||
if (imageUrl) {
|
||||
lastSnapshot.value=imageUrl
|
||||
|
||||
/!* showMinWindow(lastSnapshot.value,{
|
||||
onClick:()=>{
|
||||
router.replace('/')
|
||||
fullLive.value=true
|
||||
}
|
||||
})*!/
|
||||
}*/
|
||||
showMinWindow1({
|
||||
onClick:()=>{
|
||||
router.replace('/')
|
||||
@ -154,27 +45,13 @@ const handleCapture = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时添加事件监听
|
||||
onMounted(() => {
|
||||
if (sidebarRef.value) {
|
||||
sidebarRef.value.style.top = `${currentTop.value}px`
|
||||
sidebarRef.value.style.left = `${currentLeft.value}px`
|
||||
}
|
||||
})
|
||||
|
||||
// 组件卸载时清理事件监听
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
})
|
||||
|
||||
const openOne = () => {
|
||||
showTang.value = true
|
||||
}
|
||||
|
||||
const paySide = computed(() => {
|
||||
//当前是否已成交
|
||||
return auctionData.value.needPayBuys?.length>0
|
||||
return auctionData.value.needPayBuys?.length>0
|
||||
})
|
||||
|
||||
const goPay = () => {
|
||||
@ -188,14 +65,7 @@ const goPay = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="sidebarRef"
|
||||
class="bg-white w-60px rounded-4px overflow-hidden absolute z-999 cursor-move"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
>
|
||||
<div class="bg-white w-60px rounded-4px overflow-hidden">
|
||||
<!-- 拍品信息 -->
|
||||
<van-button
|
||||
class="w-60px !h-60px"
|
||||
@ -250,10 +120,4 @@ const goPay = () => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cursor-move {
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
</style>
|
@ -13,6 +13,7 @@ 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 floating2 from '@/components/floating2/index.vue'
|
||||
const { t } = useI18n()
|
||||
const { auctionDetail,getAuctionDetail} = goodStore();
|
||||
const player = ref(null)
|
||||
@ -97,11 +98,16 @@ const initializePlayer = async () => {
|
||||
onMounted(async () => {
|
||||
await getAuctionDetail()
|
||||
pullLink.value = await getLiveLink()
|
||||
console.log('auctionDetail.value.isLiving',auctionDetail.value.isLiving)
|
||||
if (auctionDetail.value.isLiving===1){
|
||||
initializePlayer()
|
||||
}
|
||||
})
|
||||
onActivated(()=>{
|
||||
if (auctionDetail.value.isLiving===1){
|
||||
initializePlayer()
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
player.value?.dispose()
|
||||
player.value = null
|
||||
@ -133,9 +139,13 @@ const tipOpen = () => {
|
||||
<div v-if="loading1" class="absolute left-1/2 transform translate-x--1/2 top-1/2 translate-y--1/2">
|
||||
<van-loading type="spinner" >直播加载中...</van-loading>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="fullLive">
|
||||
<sideButton class="absolute top-196px right-0 z-999"></sideButton>
|
||||
|
||||
<floating2 top="200px">
|
||||
<sideButton></sideButton>
|
||||
</floating2>
|
||||
<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 flex">
|
||||
|
@ -68,6 +68,7 @@ const goSignature = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="bg-#EBEBEB h-screen-nav flex flex-col">
|
||||
<div class="h-50px text-14px text-#191919 bg-#fff flex items-center px-21px mb-6px shrink-0">
|
||||
{{ $t('signature.tips.prePayment') }}
|
||||
|
@ -133,7 +133,9 @@ export default defineNuxtConfig({
|
||||
vscode: {
|
||||
// 配置为 cursor 编辑器
|
||||
editor: 'cursor'
|
||||
}
|
||||
},
|
||||
|
||||
enabled: true
|
||||
},
|
||||
|
||||
typescript: {
|
||||
@ -155,4 +157,4 @@ export default defineNuxtConfig({
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user