feat(components): 新增悬浮窗组件并优化直播相关功能

- 新增悬浮窗组件,用于在直播页面显示"回到直播"按钮- 优化直播室侧边按钮,使用新的悬浮窗组件
- 修复商品详情页面的成交价显示问题
- 优化首页拍卖列表的成交价显示
-改进用户主页的拍卖信息展示
- 重构签名面板组件,使用 vue-signature-pad 替代原生实现
- 优化 nuxt 配置,启用 vscode devtools
This commit is contained in:
xingyy 2025-02-20 14:47:57 +08:00
parent 92cddb5da5
commit f1e4601f7c
12 changed files with 112 additions and 87 deletions

View File

@ -0,0 +1,34 @@
import { createApp } from 'vue'
import MinWindow from '@/components/floatingBubble/index.vue'
let minWindowInstance = null
let minWindowApp = null // 新增:保存应用实例
// 创建悬浮窗
export const showMinWindow1 = ( props = {}) => {
if (process.client){
const container = document.createElement('div')
container.className = 'floating-bubble-container' // 添加类名
document.body.appendChild(container)
const app = createApp(MinWindow, {
...props
})
minWindowApp = app // 保存应用实例
minWindowInstance = app.mount(container)
return minWindowInstance
}
}
export const hideMinWindow1 = () => {
if (!minWindowApp) return
const cleanup = () => {
minWindowApp.unmount() // 使用应用实例的unmount方法
const container = document.querySelector('.floating-bubble-container') // 假设您的容器有这个类名
container && document.body.removeChild(container)
minWindowApp = null
minWindowInstance = null
}
cleanup()
}

View File

@ -0,0 +1,26 @@
<script setup>
const props=defineProps({
onClick:{
type:Function,
}
})
</script>
<template>
<van-floating-bubble
axis="xy"
magnetic="x"
:offset="{ x: 300, y: 50 }"
@click="onClick"
>
回到直播
</van-floating-bubble>
</template>
<style>
.van-floating-bubble{
width: 70px;
height: 70px;
border-radius: 5px!important;
}
</style>

View File

@ -8,7 +8,7 @@ const props = defineProps({
default: null default: null
} }
}) })
console.log(props.detailInfo)
</script> </script>
<template> <template>
@ -41,6 +41,10 @@ const props = defineProps({
<div class="text-[#575757] text-[14px]">{{$t('detail.text6')}}</div> <div class="text-[#575757] text-[14px]">{{$t('detail.text6')}}</div>
<div class="text-#575757 text-14px font-bold">{{detailInfo?.startPriceCurrency}} {{detailInfo?.startPrice}}</div> <div class="text-#575757 text-14px font-bold">{{detailInfo?.startPriceCurrency}} {{detailInfo?.startPrice}}</div>
</div> </div>
<div class="flex px-[16px] bg-#fff h-[36px] items-center mb-6px" v-if="detailInfo?.soldPrice">
<div class="text-[#B58047] text-[14px]">成交价</div>
<div class="text-#B58047 text-14px font-bold">{{detailInfo?.soldPriceCurrency}} {{detailInfo?.soldPrice}}</div>
</div>
<div class="px-[16px] bg-#fff pt-12px pb-18px"> <div class="px-[16px] bg-#fff pt-12px pb-18px">
<div class="text-[#575757] text-[14px] mb-4px">{{$t('detail.text7')}}</div> <div class="text-[#575757] text-[14px] mb-4px">{{$t('detail.text7')}}</div>
<div v-if="detailInfo?.priceRuleType!=='diy'"> <div v-if="detailInfo?.priceRuleType!=='diy'">

View File

@ -44,7 +44,7 @@ router.push('/collectCode/signature/result')
<template> <template>
<div class="signature-container"> <div class="signature-container">
<div class="flex flex-col h-100vh px-20px py-20px bg-gray"> <div class="flex flex-col h-100vh px-20px py-20px bg-gray w-100vw">
<VueSignaturePad <VueSignaturePad
width="100%" width="100%"
class="signature bg-#fff rounded-10px mb-10px" class="signature bg-#fff rounded-10px mb-10px"
@ -67,40 +67,3 @@ router.push('/collectCode/signature/result')
</div> </div>
</div> </div>
</template> </template>
<style scoped>
.signature-container {
width: 100%;
height: 100vh;
/* 强制竖屏显示 */
view-transition: none;
transform: rotate(0deg) !important;
}
:deep(.signature>canvas) {
height: 100%;
}
/* 横屏适配 */
@media screen and (orientation: landscape) {
.signature-container {
/* 在横屏时保持竖屏宽度 */
max-width: 100vh;
margin: 0 auto;
}
.flex {
/* 确保在横屏时内容不会过宽 */
max-width: 100vh;
margin: 0 auto;
}
}
/* 确保签名板在各种屏幕尺寸下都能正常显示 */
.signature {
flex: 1;
min-height: 60vh;
display: flex;
flex-direction: column;
}
</style>

View File

@ -87,7 +87,7 @@ const openShow = async (item) => {
v-if="item.soldPrice" v-if="item.soldPrice"
class="mt-[4px] text-[12px] text-[#b58047]" class="mt-[4px] text-[12px] text-[#b58047]"
> >
{{ $t('home.close_price') }}{{ item?.startPrice??0 }} {{ $t('home.close_price') }}{{ item?.soldPrice??0 }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,6 +5,7 @@ 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 {liveStore} from "~/stores/live/index.js"; import {liveStore} from "~/stores/live/index.js";
import { showMinWindow1} from "~/components/floatingBubble/floating.js";
const {getAuctionDetail, auctionDetail} = goodStore(); const {getAuctionDetail, auctionDetail} = goodStore();
const {fullLive} = liveStore() const {fullLive} = liveStore()
@ -13,6 +14,7 @@ const changeLive = () => {
fullLive.value = true; fullLive.value = true;
} }
} }
await getAuctionDetail() await getAuctionDetail()
</script> </script>
<template> <template>

View File

@ -8,6 +8,7 @@ import tangPopup from './tangPopup.vue'
import { goodStore } from "@/stores/goods/index.js" import { goodStore } from "@/stores/goods/index.js"
import { authStore } from "~/stores/auth/index.js" import { authStore } from "~/stores/auth/index.js"
import {showMinWindow} from "~/components/liveMinWindow/createMinWindow.js"; import {showMinWindow} from "~/components/liveMinWindow/createMinWindow.js";
import {hideMinWindow1, showMinWindow1} from "~/components/floatingBubble/floating.js";
const { quoteStatus, changeStatus, show, auctionData, getSocketData ,lastSnapshot,fullLive} = liveStore() const { quoteStatus, changeStatus, show, auctionData, getSocketData ,lastSnapshot,fullLive} = liveStore()
const { pageRef } = goodStore() const { pageRef } = goodStore()
@ -135,17 +136,25 @@ const captureVideoFrame = () => {
} }
const handleCapture = () => { const handleCapture = () => {
const imageUrl = captureVideoFrame() /* const imageUrl = captureVideoFrame()
if (imageUrl) { if (imageUrl) {
lastSnapshot.value=imageUrl lastSnapshot.value=imageUrl
showMinWindow(lastSnapshot.value,{
/!* showMinWindow(lastSnapshot.value,{
onClick:()=>{ onClick:()=>{
router.replace('/') router.replace('/')
fullLive.value=true fullLive.value=true
console.log('执行') console.log('执行')
} }
}) })*!/
}*/
showMinWindow1({
onClick:()=>{
router.replace('/')
fullLive.value=true
hideMinWindow1()
} }
})
} }
// //
@ -167,18 +176,14 @@ const openOne = () => {
} }
const paySide = computed(() => { const paySide = computed(() => {
// //
if (auctionData.value.artwork?.isSoled && auctionData.value.artwork?.buyInfo?.userID === userInfo.value.ID) { return auctionData.value.needPayBuys?.length>0
return true
} else {
return false
}
}) })
const goPay = () => { const goPay = () => {
payment.value.leftCurrency=auctionData.value?.nowAuctionPrice?.currency payment.value.leftCurrency=auctionData.value.needPayBuys?.[0]?.leftCurrency
payment.value.leftPrice=auctionData.value?.nowAuctionPrice?.successPrice payment.value.leftPrice=auctionData.value.needPayBuys?.[0]?.leftPrice
payment.value.buyUid=auctionData.value?.nowAuctionPrice?.successBuyUuid payment.value.buyUid=auctionData.value.needPayBuys?.[0]?.uuid
handleCapture() handleCapture()
router.push('/signature/protocol') router.push('/signature/protocol')
} }

View File

@ -118,7 +118,7 @@ watch(()=>props.show,(newValue)=>{
loading="lazy" loading="lazy"
/> />
<div class="w-45px h-17px bg-#2B53AC text-12px line-height-none flex justify-center items-center absolute top-2px left-2px text-#fff">LOT{{item.index}}</div> <div class="w-45px h-17px bg-#2B53AC text-12px line-height-none flex justify-center items-center absolute top-2px left-2px text-#fff">LOT{{item.index}}</div>
<div v-show="auctionData.artwork.index===item?.index" class="w-80px h-20px bg-#B58047 flex line-height-none justify-center items-center text-#fff text-12px bottom-0 absolute blink">{{ $t('live_room.cast') }}</div> <div v-show="auctionData?.artwork?.index===item?.index" class="w-80px h-20px bg-#B58047 flex line-height-none justify-center items-center text-#fff text-12px bottom-0 absolute blink">{{ $t('live_room.cast') }}</div>
</div> </div>
<div> <div>
<div class="ellipsis line-height-20px text-16px font-600 min-h-40px"> <div class="ellipsis line-height-20px text-16px font-600 min-h-40px">

View File

@ -151,7 +151,7 @@ nextTick(()=>{
const goBuy = async () => { const goBuy = async () => {
const res = await artworkBuy({ const res = await artworkBuy({
auctionArtworkUuid: auctionData.value?.artwork?.uuid, auctionArtworkUuid: auctionData.value?.artwork?.uuid,
buyMoney: String(auctionData.value?.nowAuctionPrice?.nextPrice ?? 0) buyMoney: String(auctionData?.value.nowAuctionPrice?.nowPrice??0)
}) })
if (res.status === 0) { if (res.status === 0) {
message.success(t('live_room.success_mess')) message.success(t('live_room.success_mess'))

View File

@ -122,7 +122,7 @@ fetchData()
<div class="flex justify-between"> <div class="flex justify-between">
<div> <div>
<div class="text-#575757 text-14px line-height-none mb-5px"> <div class="text-#575757 text-14px line-height-none mb-5px">
{{ $t('home.start_price') }}{{item.auctionArtworkInfo?.soldPriceCurrency}} {{item.auctionArtworkInfo?.soldPrice}} {{ $t('home.start_price') }}{{item.auctionArtworkInfo?.startPriceCurrency}} {{item.auctionArtworkInfo?.startPrice}}
</div> </div>
<div class="text-#B58047 text-14px line-height-none"> <div class="text-#B58047 text-14px line-height-none">
{{ $t('home.close_price') }}{{item.baseCurrency}} {{item.baseMoney}} {{ $t('home.close_price') }}{{item.baseCurrency}} {{item.baseMoney}}

View File

@ -2,12 +2,12 @@
import {showToast} from 'vant'; import {showToast} from 'vant';
import {onMounted, onUnmounted, ref} from 'vue'; import {onMounted, onUnmounted, ref} from 'vue';
import {signOffline, signOnline} from "~/api/goods/index.js"; import {signOffline, signOnline} from "~/api/goods/index.js";
import {VueSignaturePad} from "vue-signature-pad";
const router = useRouter(); const router = useRouter();
definePageMeta({ definePageMeta({
layout: '' layout: ''
}) })
const signaturePad = ref(null);
const signaturePadRef = ref(null);
const isLandscapeMode = ref(false); const isLandscapeMode = ref(false);
const checkScreenOrientation = () => { const checkScreenOrientation = () => {
@ -37,16 +37,16 @@ onUnmounted(() => {
const imgUrl = ref('') const imgUrl = ref('')
const show = ref(false) const show = ref(false)
const clearSignature = () => { const clearSignature = () => {
signaturePadRef.value?.resize(); signaturePad.value?.clearSignature();
signaturePadRef.value?.clear();
}; };
const submitSignature = () => { const submitSignature = () => {
signaturePadRef.value?.submit(); if (signaturePad.value?.isEmpty()) {
}; showToast('请先签名');
return;
const handleSignatureSubmit = async (data) => { }
imgUrl.value = data.image const { data } = signaturePad.value?.saveSignature(); // base64
show.value = true imgUrl.value = data;
show.value = true;
nextTick(() => { nextTick(() => {
const overlay = document.querySelector('.signature-container .van-overlay'); const overlay = document.querySelector('.signature-container .van-overlay');
if (overlay) { if (overlay) {
@ -56,7 +56,6 @@ const handleSignatureSubmit = async (data) => {
} }
}) })
}; };
const confirm = async () => { const confirm = async () => {
const res = await signOnline({ const res = await signOnline({
signImgFileData: imgUrl.value signImgFileData: imgUrl.value
@ -71,15 +70,15 @@ router.back()
</script> </script>
<template> <template>
<div class="signature-container"> <div class="signature-container bg-gray ">
<template v-if="isLandscapeMode"> <template v-if="isLandscapeMode">
<div class="signature-content"> <div class="signature-content px-10px py-10px">
<van-signature <VueSignaturePad
class="signature-pad" width="100%"
ref="signaturePadRef" class="signature bg-#fff rounded-10px mb-10px"
@submit="handleSignatureSubmit" ref="signaturePad"
/> />
<div class="control-buttons"> <div class="control-buttons justify-evenly">
<van-button <van-button
class="control-button" class="control-button"
size="mini" size="mini"
@ -122,7 +121,6 @@ router.back()
.signature-container { .signature-container {
position: fixed; position: fixed;
inset: 0; inset: 0;
background-color: #fff;
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) 0; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) 0;
} }
@ -140,18 +138,11 @@ router.back()
height: 100%; height: 100%;
} }
:deep(.van-overlay) {
/* left: initial;*/
}
.signature-pad {
flex-grow: 1;
}
.control-buttons { .control-buttons {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px 10px 0; padding: 0 10px 0;
gap: 10px; gap: 10px;
} }
@ -169,7 +160,4 @@ router.back()
font-size: 18px; font-size: 18px;
} }
:deep(.van-signature__footer) {
display: none;
}
</style> </style>

View File

@ -118,7 +118,10 @@ export default defineNuxtConfig({
}, },
devtools: { devtools: {
enabled: true, vscode: {
// 配置为 cursor 编辑器
editor: 'cursor'
}
}, },
typescript: { typescript: {