Compare commits
7 Commits
9334819414
...
afd5e75771
Author | SHA1 | Date | |
---|---|---|---|
|
afd5e75771 | ||
|
d22979bd93 | ||
|
9812cd7903 | ||
|
50fe1248e3 | ||
|
def6a6b71e | ||
|
48108c5e84 | ||
|
3c69236caa |
@ -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
|
||||||
|
@ -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>
|
|
183
app/components/liveMinWindow/index.vue
Normal file
183
app/components/liveMinWindow/index.vue
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<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>
|
@ -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>
|
||||||
|
@ -36,11 +36,11 @@ const headItem=(statusCode)=>{
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="auctionData.auctionPriceList?.buys?.length>0">
|
<template v-else-if="auctionData.auctionPriceList?.buys?.length>0">
|
||||||
<div v-for="(item, index) in auctionData.auctionPriceList?.buys" :key="index" class="flex flex-shrink-0 h-25px">
|
<div v-for="(item, index) in auctionData.auctionPriceList?.buys" :key="index" class="flex flex-shrink-0 h-25px">
|
||||||
<div class="flex-grow-1 text-start shrink-0" :style="`color: ${headItem(item.statusCode).color}`" >{{ headItem(item.statusCode).label }}</div>
|
<div class="text-start shrink-0 w-60px" :style="`color: ${headItem(item.statusCode).color}`" >{{ headItem(item.statusCode).label }}</div>
|
||||||
<div class="flex-grow-1 text-center shrink-0">{{ item.auctionType==='local'?'现场竞价':'网络竞价' }}</div>
|
<div class="text-start shrink-0 w-80px">{{ item.auctionType==='local'?'现场竞价':'网络竞价' }}</div>
|
||||||
<div class="flex-grow-1 text-center shrink-0">{{ item.createdAt }}</div>
|
<div class="text-start shrink-0 w-80px">{{ item.createdAt }}</div>
|
||||||
<div class="flex-grow-1 text-center shrink-0">{{item.baseCurrency}}{{ item.baseMoney }}</div>
|
<div class="text-start shrink-0 w-80px">{{item.baseCurrency}}{{ item.baseMoney }}</div>
|
||||||
<div class="flex-grow-1 text-center text-#2B53AC shrink-0 w-20px">{{ item.userId===userInfo.ID?'我':'' }}</div>
|
<div class="text-start text-#2B53AC shrink-0 w-20px">{{ item.userId===userInfo.ID?'我':'' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="auctionData.wsType==='newArtwork'">
|
<template v-if="auctionData.wsType==='newArtwork'">
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import {liveStore} from "~/stores/live/index.js";
|
||||||
|
import { showMinWindow, hideMinWindow } from '@/components/liveMinWindow/createMinWindow.js'
|
||||||
|
const {lastSnapshot,fullLive} = liveStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -20,14 +23,47 @@ const close=()=>{
|
|||||||
}
|
}
|
||||||
const confirm=()=>{
|
const confirm=()=>{
|
||||||
router.push('/signature/protocol')
|
router.push('/signature/protocol')
|
||||||
|
handleCapture()
|
||||||
emit('update:show',false)
|
emit('update:show',false)
|
||||||
}
|
}
|
||||||
|
const captureVideoFrame = () => {
|
||||||
|
try {
|
||||||
|
const video = document.querySelector('#J_prismPlayer video')
|
||||||
|
if (!video) {
|
||||||
|
console.error('未找到视频元素')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
canvas.width = video.videoWidth
|
||||||
|
canvas.height = video.videoHeight
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||||
|
return canvas.toDataURL('image/jpeg', 0.9)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频截图失败:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleCapture = () => {
|
||||||
|
const imageUrl = captureVideoFrame()
|
||||||
|
if (imageUrl) {
|
||||||
|
lastSnapshot.value=imageUrl
|
||||||
|
showMinWindow(lastSnapshot.value,{
|
||||||
|
onClick:()=>{
|
||||||
|
router.replace('/')
|
||||||
|
fullLive.value=true
|
||||||
|
console.log('执行')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<van-dialog :show="show" show-cancel-button @close="close" @confirm="confirm">
|
<van-dialog :show="show" show-cancel-button @cancel="close" @confirm="confirm">
|
||||||
<div class="flex flex-col pt-18px pb-13px justify-between items-center h-144px">
|
<div class="flex flex-col pt-18px pb-13px justify-between items-center h-144px">
|
||||||
<template v-if="payStatus===0">
|
<template v-if="payStatus===0">
|
||||||
<div class="text-#000 text-16px font-600 ">支付全部</div>
|
<div class="text-#000 text-16px font-600 ">支付全部</div>
|
||||||
|
@ -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
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {ref, onMounted, onBeforeUnmount, computed, watch} from 'vue'
|
import {ref, onMounted, onBeforeUnmount, watch} from 'vue'
|
||||||
import Aliplayer from 'aliyun-aliplayer'
|
import Aliplayer from 'aliyun-aliplayer'
|
||||||
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
|
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
|
||||||
import sideButton from '@/pages/liveRoom/components/SideButton/index.vue'
|
import sideButton from '@/pages/liveRoom/components/SideButton/index.vue'
|
||||||
@ -7,80 +7,31 @@ import broadcast from '@/pages/liveRoom/components/Broadcast/index.vue'
|
|||||||
import {liveStore} from "@/stores/live/index.js"
|
import {liveStore} from "@/stores/live/index.js"
|
||||||
import paymentResults from '@/pages/liveRoom/components/PaymentResults/index.vue'
|
import paymentResults from '@/pages/liveRoom/components/PaymentResults/index.vue'
|
||||||
import paymentInput from '@/pages/liveRoom/components/PaymentInput/index.vue'
|
import paymentInput from '@/pages/liveRoom/components/PaymentInput/index.vue'
|
||||||
import xButton from '@/components/x-button/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 { showDialog } from 'vant';
|
||||||
import {artworkBuy} from "@/api/goods/index.js"
|
import {artworkBuy} from "@/api/goods/index.js"
|
||||||
import CryptoJS from 'crypto-js'
|
|
||||||
|
|
||||||
const {auctionDetail, getAuctionDetail} = goodStore()
|
|
||||||
const player = ref(null)
|
const player = ref(null)
|
||||||
const {quoteStatus, changeStatus, 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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const isPiPActive = ref(false)
|
|
||||||
const videoRef = ref(null)
|
|
||||||
|
|
||||||
// 检查浏览器是否支持画中画
|
|
||||||
const isPiPSupported = computed(() => {
|
|
||||||
return document.pictureInPictureEnabled ||
|
|
||||||
document.webkitPictureInPictureEnabled
|
|
||||||
})
|
|
||||||
|
|
||||||
// 进入画中画模式
|
|
||||||
const enterPiP = async () => {
|
|
||||||
try {
|
|
||||||
if (!videoRef.value) return
|
|
||||||
|
|
||||||
if (document.pictureInPictureElement) {
|
|
||||||
await document.exitPictureInPicture()
|
|
||||||
}
|
|
||||||
|
|
||||||
await videoRef.value.requestPictureInPicture()
|
|
||||||
isPiPActive.value = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('进入画中画模式失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出画中画模式
|
|
||||||
const exitPiP = async () => {
|
|
||||||
try {
|
|
||||||
if (document.pictureInPictureElement) {
|
|
||||||
await document.exitPictureInPicture()
|
|
||||||
isPiPActive.value = false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('退出画中画模式失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: '主页',
|
title: '主页',
|
||||||
i18n: 'login.title',
|
i18n: 'login.title',
|
||||||
})
|
})
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
const handlePlayerError = (error) => {
|
const handlePlayerError = (error) => {
|
||||||
console.error('播放器错误:', error)
|
console.error('播放器错误:', error)
|
||||||
if (player.value) {
|
|
||||||
player.value?.play()
|
player.value?.play()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const initializePlayer = () => {
|
const initializePlayer = async () => {
|
||||||
try {
|
try {
|
||||||
if (player.value) {
|
if (player.value) {
|
||||||
player.value?.dispose()
|
player.value.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerConfig = {
|
const playerConfig = {
|
||||||
id: playerId.value,
|
id: playerId.value,
|
||||||
source: pullLink.value,
|
source: pullLink.value,
|
||||||
@ -89,16 +40,23 @@ const initializePlayer = () => {
|
|||||||
autoplayPolicy: {fallbackToMute: true},
|
autoplayPolicy: {fallbackToMute: true},
|
||||||
controlBarVisibility: 'never',
|
controlBarVisibility: 'never',
|
||||||
}
|
}
|
||||||
|
|
||||||
player.value = new Aliplayer(playerConfig, (playerInstance) => {
|
player.value = new Aliplayer(playerConfig, (playerInstance) => {
|
||||||
isPlayerReady.value = true
|
isPlayerReady.value = true
|
||||||
playerInstance?.play()
|
playerInstance?.play()
|
||||||
})
|
})
|
||||||
player.value?.on('error', handlePlayerError)
|
|
||||||
player.value?.on('rtsTraceId', (event) => {
|
player.value.on('error', handlePlayerError)
|
||||||
})
|
|
||||||
player.value?.on('rtsFallback', (event) => {
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
showDialog({
|
||||||
|
message: '直播内容获取失败,是否刷新页面重新获取',
|
||||||
|
showCancelButton:true
|
||||||
|
}).then(() => {
|
||||||
|
location.reload()
|
||||||
|
// on close
|
||||||
|
}) .catch(() => {
|
||||||
|
// on cancel
|
||||||
|
});;
|
||||||
console.error('播放器初始化失败:', error)
|
console.error('播放器初始化失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,27 +67,12 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (player.value) {
|
|
||||||
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(() => {
|
|
||||||
return props.fullLive
|
|
||||||
}, (newVal) => {
|
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
getSocketData()
|
getSocketData()
|
||||||
setTimeout(() => {
|
|
||||||
fullLive1.value = true
|
|
||||||
}, 400)
|
|
||||||
} else {
|
|
||||||
fullLive1.value = false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -145,15 +88,17 @@ const goBuy = async () => {
|
|||||||
|
|
||||||
const tipOpen = () => {
|
const tipOpen = () => {
|
||||||
message.warning('出价状态未开启')
|
message.warning('出价状态未开启')
|
||||||
|
}
|
||||||
|
const updateShow=()=>{
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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)">
|
||||||
@ -171,8 +116,7 @@ const tipOpen = () => {
|
|||||||
<van-button @click="goBuy" color="#FFB25F" class="w-344px !h-[40px]">
|
<van-button @click="goBuy" color="#FFB25F" class="w-344px !h-[40px]">
|
||||||
<div>{{
|
<div>{{
|
||||||
`确认出价 ${auctionData?.nowAuctionPrice?.currency} ${auctionData?.nowAuctionPrice?.nextPrice ?? 0}`
|
`确认出价 ${auctionData?.nowAuctionPrice?.currency} ${auctionData?.nowAuctionPrice?.nextPrice ?? 0}`
|
||||||
}}
|
}}</div>
|
||||||
</div>
|
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-10px mb-10px">
|
<div v-else class="mt-10px mb-10px">
|
||||||
@ -197,16 +141,19 @@ const tipOpen = () => {
|
|||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#J_prismPlayer {
|
#J_prismPlayer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|
||||||
& > video {
|
& > video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.v-enter-active,
|
.v-enter-active,
|
||||||
.v-leave-active {
|
.v-leave-active {
|
||||||
|
@ -97,7 +97,7 @@ const onRefresh = async () => {
|
|||||||
<x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" :preview="false" alt=""/>
|
<x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" :preview="false" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-between grow-1">
|
<div class="flex flex-col justify-between grow-1">
|
||||||
<div class="text-#000 text-16px ellipsis line-height-21px">{{item1?.auctionArtworkInfo?.artworkTitle}}{{item1?.auctionArtworkInfo?.artworkTitle}}{{item1?.auctionArtworkInfo?.artworkTitle}}</div>
|
<div class="text-#000 text-16px ellipsis line-height-21px">{{item1?.auctionArtworkInfo?.artworkTitle}}</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-#575757 text-14px line-height-none mb-5px">起拍价:RMB 1,000</div>
|
<div class="text-#575757 text-14px line-height-none mb-5px">起拍价:RMB 1,000</div>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const activeNames = ref(['1']);
|
const activeNames = ref(['1']);
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'default',
|
||||||
|
title: '签署内容'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
@ -19,6 +20,31 @@ export const liveStore = createGlobalState(() => {
|
|||||||
const socket=ref(null)
|
const socket=ref(null)
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const pullLink=ref('')
|
const pullLink=ref('')
|
||||||
|
const isMinWindow = ref(false)
|
||||||
|
const lastSnapshot = ref('')
|
||||||
|
const liveInfo = ref(null)
|
||||||
|
|
||||||
|
// 设置最小化状态
|
||||||
|
const setMinWindow = (status) => {
|
||||||
|
isMinWindow.value = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置截图
|
||||||
|
const setSnapshot = (snapshot) => {
|
||||||
|
lastSnapshot.value = snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置直播信息
|
||||||
|
const setLiveInfo = (info) => {
|
||||||
|
liveInfo.value = info
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
const reset = () => {
|
||||||
|
isMinWindow.value = false
|
||||||
|
lastSnapshot.value = ''
|
||||||
|
liveInfo.value = null
|
||||||
|
}
|
||||||
// 解密工具函数
|
// 解密工具函数
|
||||||
const decryptUtils = {
|
const decryptUtils = {
|
||||||
// 解密配置
|
// 解密配置
|
||||||
@ -229,6 +255,14 @@ export const liveStore = createGlobalState(() => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
return{
|
return{
|
||||||
|
fullLive,
|
||||||
|
isMinWindow,
|
||||||
|
lastSnapshot,
|
||||||
|
liveInfo,
|
||||||
|
setMinWindow,
|
||||||
|
setSnapshot,
|
||||||
|
setLiveInfo,
|
||||||
|
reset,
|
||||||
pullLink,
|
pullLink,
|
||||||
getLiveLink,
|
getLiveLink,
|
||||||
auctionData,
|
auctionData,
|
||||||
|
9563
pnpm-lock.yaml
9563
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user