feat(component): 优化消息组件并添加新功能

- 重构 x-message 组件,支持更多自定义选项
- 添加 artDetail 页面用于展示艺术品详情
- 修改 liveRoom 页面,接入新的消息提示功能- 优化 profile 页面布局,增加去支付按钮
- 调整 home 页面,集成新的消息系统
- 修改 websocket 插件,支持携带 token 认证
This commit is contained in:
xingyy 2025-02-08 10:06:21 +08:00
parent 5d645a8106
commit aec3825a3b
14 changed files with 268 additions and 64 deletions

View File

@ -30,3 +30,11 @@ export async function userArtworks(data) {
data data
}) })
} }
export async function userArtwork(data) {
return await request( {
url:'/api/v1/m/user/artwork',
method: 'POST',
data
})
}

View File

@ -1,21 +1,65 @@
<script setup> <script setup>
import { ref, computed } from 'vue'
import MessageContent from './message/index.vue' import MessageContent from './message/index.vue'
import { ref } from 'vue'
const visible = ref(false) const visible = ref(false)
const messageType = ref('success')
const messageText = ref('') const messageText = ref('')
const messageType = ref('') const showIcon = ref(true)
const customStyle = ref({})
const title = ref({})
const subTitle = ref({})
const containerStyle = computed(() => {
const { top, bottom, left, right, transform, ...otherStyles } = customStyle.value || {}
const baseStyle = {
position: 'fixed',
zIndex: 9999
}
const horizontalPosition = left || right
? { left, right }
: { left: '50%', transform: 'translateX(-50%)' }
const verticalPosition = {}
if (bottom !== undefined) {
verticalPosition.bottom = bottom
} else {
verticalPosition.top = top || '50px'
}
return {
...baseStyle,
...horizontalPosition,
...verticalPosition,
...otherStyles
}
})
const emit = defineEmits(['after-leave']) const emit = defineEmits(['after-leave'])
const showMessage = ({ type = 'warning', message, duration = 2000 }) => { const showMessage = (options) => {
messageText.value = message if (typeof options === 'string') {
messageType.value = type messageText.value = options
title.value = {}
subTitle.value = {}
} else {
messageText.value = options.message || ''
title.value = options.title || {}
subTitle.value = options.subTitle || {}
}
messageType.value = options.type || 'success'
showIcon.value = options.icon !== false
customStyle.value = options.style || {}
visible.value = true visible.value = true
setTimeout(() => { setTimeout(() => {
visible.value = false visible.value = false
}, duration) }, options.duration || 2000)
} }
defineExpose({ showMessage }) defineExpose({ showMessage })
</script> </script>
@ -26,9 +70,12 @@ defineExpose({ showMessage })
> >
<MessageContent <MessageContent
v-if="visible" v-if="visible"
:text="messageText" :message="messageText"
:type="messageType" :type="messageType"
class="fixed top-50px left-1/2 -translate-x-1/2 z-9999" :title="title"
:sub-title="subTitle"
:show-icon="showIcon"
:style="containerStyle"
/> />
</transition> </transition>
</template> </template>

View File

@ -7,13 +7,36 @@ import warning from '../images/warning.png'
const props = defineProps({ const props = defineProps({
type: { type: {
type: String, type: String,
default: 'success', default: 'success'
validator: value => ['success', 'error', 'warning'].includes(value),
}, },
text: { message: {
type: String, type: String,
default: '', default: ''
}, },
title: {
type: Object,
default: () => ({
text: '',
color: '',
align: 'left'
})
},
subTitle: {
type: Object,
default: () => ({
text: '',
color: '',
align: 'left'
})
},
showIcon: {
type: Boolean,
default: true
},
customStyle: {
type: Object,
default: () => ({})
}
}) })
const typeConfig = { const typeConfig = {
@ -24,34 +47,64 @@ const typeConfig = {
}, },
error: { error: {
imgSrc: error, imgSrc: error,
borderColor: '#F3CBD3', borderColor: '#FFD4D4',
bgColor: '#FBEEF1', bgColor: '#FFF0F0',
}, },
warning: { warning: {
imgSrc: warning, imgSrc: warning,
borderColor: '#FAE0B5', borderColor: '#FFE2BA',
bgColor: '#FEF7ED', bgColor: '#FFF7EC',
}, }
} }
</script> </script>
<template> <template>
<div <div
class="box-border min-h-[46px] w-[343px] flex items-center border rounded-[4px] px-[15px] shadow-sm" :class="`box-border flex items-center border rounded-[4px] px-[15px] shadow-sm py-8px ${!message?'justify-center':''}`"
:style="{ :style="{
borderColor: typeConfig[type].borderColor, borderColor: typeConfig[type].borderColor,
backgroundColor: typeConfig[type].bgColor, backgroundColor: typeConfig[type].bgColor,
width: customStyle.width || '343px',
height: customStyle.height || 'auto',
minHeight: '46px'
}" }"
> >
<div class="mr-[9px] h-[20px] w-[20px]"> <div v-if="showIcon" class="mr-[12px]">
<img <img
:src="typeConfig[type].imgSrc" :src="typeConfig[type].imgSrc"
class="h-full w-full" class="w-20px h-20px"
style="object-fit: contain"
alt="" alt=""
> >
</div> </div>
<div class="text-[14px] text-black leading-normal"> <div class="flex flex-col justify-center">
{{ text }} <!-- 如果是简单文本模式 -->
<div v-if="message" class="text-[14px] line-height-none">
{{ message }}
</div>
<!-- 如果是标题+副标题模式 -->
<template v-else>
<div
v-if="title.text"
class="text-[14px] line-height-none"
:style="{
color: title.color || 'inherit',
textAlign: title.align || 'left'
}"
>
{{ title.text }}
</div>
<div
v-if="subTitle.text"
class="text-[12px] leading-normal mt-1"
:style="{
color: subTitle.color || '#939393',
textAlign: subTitle.align || 'left'
}"
>
{{ subTitle.text }}
</div>
</template>
</div> </div>
</div> </div>
</template> </template>

View File

@ -2,22 +2,46 @@ import { createApp, nextTick } from 'vue'
import MessagePopup from './index.vue' import MessagePopup from './index.vue'
const message = { const message = {
success(text, duration = 2000) { success(options, duration = 2000) {
if (process.client) { if (process.client) {
this.show({ type: 'success', message: text, duration }) if (typeof options === 'string') {
this.show({ type: 'success', message: options, duration })
} else {
this.show({
type: 'success',
...options,
duration
})
}
} }
}, },
error(text, duration = 2000) { error(options, duration = 2000) {
if (process.client) { if (process.client) {
this.show({ type: 'error', message: text, duration }) if (typeof options === 'string') {
this.show({ type: 'error', message: options, duration })
} else {
this.show({
type: 'error',
...options,
duration
})
}
} }
}, },
warning(text, duration = 2000) { warning(options, duration = 2000) {
if (process.client) { if (process.client) {
this.show({ type: 'warning', message: text, duration }) if (typeof options === 'string') {
this.show({ type: 'warning', message: options, duration })
} else {
this.show({
type: 'warning',
...options,
duration
})
}
} }
}, },
show({ type = 'success', message, duration = 2000 }) { show(options) {
if (!process.client) return if (!process.client) return
const container = document.createElement('div') const container = document.createElement('div')
@ -32,11 +56,7 @@ const message = {
const instance = app.mount(container) const instance = app.mount(container)
nextTick(() => { nextTick(() => {
instance.showMessage?.({ instance.showMessage?.(options)
type,
message,
duration
})
}) })
} }
} }

View File

@ -0,0 +1,27 @@
<script setup>
import itemDetail from '@/components/itemDetail/index.vue'
import {goodStore} from "~/stores/goods/index.js";
const {artWorkDetail} = goodStore()
</script>
<template>
<div class="relative h-screen-nav flex flex-col">
<itemDetail class="grow-1" :detail-info="artWorkDetail"/>
<div class="h-81px bg-#fff flex justify-center pt-7px">
<van-button class="w-213px van-btn-h-38px" type="primary">
<span class="text-#fff text-14px">去支付 RMB10,000</span>
</van-button>
</div>
<div class="w-108px h-137px absolute bottom-240px right-6px">
<img src="@/static/images/zd5530@2x.png" class="w-full h-full" alt="">
<div class="flex flex-col items-center absolute bottom-25px text-14px text-#B58047 left-1/2 transform translate-x--1/2 whitespace-nowrap">
<div>恭喜您</div>
<div>竞拍成功</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -3,6 +3,7 @@ import liveRoom from '@/pages/liveRoom/index.client.vue';
import {goodStore} from "@/stores/goods/index.js"; 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'
const {fullLive,getAuctionDetail,auctionDetail} = goodStore(); const {fullLive,getAuctionDetail,auctionDetail} = goodStore();
const changeLive = () => { const changeLive = () => {
fullLive.value = true; fullLive.value = true;

View File

@ -65,10 +65,10 @@ onUnmounted(() => {
<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" :style="`color: ${item.statusCode==='head'?'#D03050':'#939393'}`" >{{ item.statusCode==='head'?'领先':'出局' }}</div> <div class="flex-grow-1 text-start" :style="`color: ${item.statusCode==='head'?'#D03050':'#939393'}`" >{{ item.statusCode==='head'?'领先':'出局' }}</div>
<div class="flex-grow-1 text-start">{{ item.auctionType==='local'?'现场竞价':'网络竞价' }}</div> <div class="flex-grow-1 text-center">{{ item.auctionType==='local'?'现场竞价':'网络竞价' }}</div>
<div class="flex-grow-1 text-start">{{ item.createdAt }}</div> <div class="flex-grow-1 text-center">{{ item.createdAt }}</div>
<div class="flex-grow-1 text-start">{{ item.baseMoney }}</div> <div class="flex-grow-1 text-center">{{ item.baseMoney }}</div>
<div class="flex-grow-1 text-start text-#2B53AC">{{ item.userId===userInfo.ID?'我':'' }}</div> <div class="flex-grow-1 text-center text-#2B53AC">{{ item.userId===userInfo.ID?'我':'' }}</div>
</div> </div>
</template> </template>
<template v-if="auctionData.wsType==='newArtwork'"> <template v-if="auctionData.wsType==='newArtwork'">

View File

@ -13,7 +13,7 @@ const showTang=ref(false)
const openOne=()=>{ const openOne=()=>{
showTang.value=true showTang.value=true
} }
getSocketData()
</script> </script>
<template> <template>

View File

@ -9,9 +9,10 @@ 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 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";
const {auctionDetail,getAuctionDetail} = goodStore(); const {auctionDetail,getAuctionDetail} = goodStore();
const player = ref(null) const player = ref(null)
const {quoteStatus, changeStatus, show, playerId, show1,auctionData} = liveStore() const {quoteStatus, changeStatus, show, playerId, show1,auctionData,getSocketData} = liveStore()
const isPlayerReady = ref(false) const isPlayerReady = ref(false)
const props = defineProps({ const props = defineProps({
fullLive: { fullLive: {
@ -70,11 +71,11 @@ const goPay = () => {
show.value = true show.value = true
} }
const fullLive1 = ref(false) const fullLive1 = ref(false)
watch(()=>{ watch(()=>{
return props.fullLive return props.fullLive
}, (newVal) => { }, (newVal) => {
if (newVal) { if (newVal) {
getSocketData()
setTimeout(() => { setTimeout(() => {
fullLive1.value = true fullLive1.value = true
}, 400) }, 400)
@ -82,6 +83,7 @@ watch(()=>{
fullLive1.value = false fullLive1.value = false
} }
}) })
</script> </script>
<template> <template>

View File

@ -2,11 +2,13 @@
import {userArtworks} from "@/api/goods/index.js"; import {userArtworks} from "@/api/goods/index.js";
import {authStore} from "@/stores/auth/index.js"; import {authStore} from "@/stores/auth/index.js";
import xImage from '@/components/x-image/index.vue' import xImage from '@/components/x-image/index.vue'
import {goodStore} from "~/stores/goods/index.js";
definePageMeta({ definePageMeta({
layout: 'default', layout: 'default',
title: '我的', title: '我的',
i18n: 'menu.profile', i18n: 'menu.profile',
}) })
const {artWorkDetail} = goodStore()
const myList=ref([]) const myList=ref([])
const showMyList=ref([]) const showMyList=ref([])
const {userInfo}= authStore() const {userInfo}= authStore()
@ -33,7 +35,17 @@ const initData=async ()=>{
showMyList.value=groupAndSortByDate(myList.value) showMyList.value=groupAndSortByDate(myList.value)
} }
} }
const router = useRouter()
initData() initData()
const goPay=()=>{
}
const goDetail=(item)=>{
artWorkDetail.value=item.auctionArtworkInfo
console.log(' artWorkDetail.value', artWorkDetail.value)
router.push('/artDetail')
}
</script> </script>
<template> <template>
@ -55,17 +67,24 @@ initData()
<van-list <van-list
finished-text="没有更多了" finished-text="没有更多了"
> >
<div class="px-16px pt-14px" v-for="(item,index) of showMyList"> <div class="px-16px pt-14px" v-for="(item,index) of showMyList" >
<div class="text-#575757 text-14px mb-3px">{{item.userCreatedAt}}</div> <div class="text-#575757 text-14px mb-3px">{{item.userCreatedAt}}</div>
<div class="flex mb-22px" v-for="(item1,index1) of item.list"> <div class="flex mb-22px" v-for="(item1,index1) of item.list" @click="goDetail(item1)">
<div class="flex-shrink-0 mr-10px rounded-4px overflow-hidden"> <div class="flex-shrink-0 mr-10px rounded-4px overflow-hidden">
<x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" alt=""/> <x-image class="w-80px h-80px" :src="item1?.auctionArtworkInfo?.artwork?.hdPic" alt=""/>
</div> </div>
<div class="flex flex-col justify-between"> <div class="flex flex-col justify-between grow-1">
<div class="text-#000 text-16px ellipsis line-height-21px">{{item1?.auctionArtworkInfo?.artworkTitle}}</div> <div class="text-#000 text-16px ellipsis line-height-21px">{{item1?.auctionArtworkInfo?.artworkTitle}}{{item1?.auctionArtworkInfo?.artworkTitle}}{{item1?.auctionArtworkInfo?.artworkTitle}}</div>
<div class="text-#575757 text-14px line-height-none ">起拍价RMB 1,000</div> <div class="flex justify-between">
<div>
<div class="text-#575757 text-14px line-height-none mb-5px">起拍价RMB 1,000</div>
<div class="text-#B58047 text-14px line-height-none">成交价RMB 10,000</div> <div class="text-#B58047 text-14px line-height-none">成交价RMB 10,000</div>
</div> </div>
<div v-if="[1,3,4].includes(item.status)" @click="goPay">
<van-button class="w-73px van-btn-h-30px" type="primary"><span class="text-12px">去支付</span></van-button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</van-list> </van-list>

View File

@ -1,5 +1,8 @@
import {authStore} from "~/stores/auth";
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const { token } = authStore()
const ws = reactive({ const ws = reactive({
instance: null as WebSocket | null, instance: null as WebSocket | null,
isConnected: false, isConnected: false,
@ -11,8 +14,8 @@ export default defineNuxtPlugin(() => {
} }
// 构建查询字符串 // 构建查询字符串
const queryString = data const queryString =data
? '?' + Object.entries(data) ? '?' + Object.entries({ token: token.value,...data})
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&') .join('&')
: '' : ''

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,4 +1,4 @@
import { createGlobalState } from '@vueuse/core' import { createGlobalState,useLocalStorage } from '@vueuse/core'
import { ref } from 'vue' import { ref } from 'vue'
import { artworkList, defaultDetail, artworkDetail } from "@/api/goods/index.js" import { artworkList, defaultDetail, artworkDetail } from "@/api/goods/index.js"
@ -13,7 +13,7 @@ export const goodStore = createGlobalState(() => {
pageSize: 5, pageSize: 5,
itemCount: 0 itemCount: 0
}) })
const artWorkDetail = ref(null) const artWorkDetail = useLocalStorage('artWorkDetail',{})
const itemList = ref([]) const itemList = ref([])
const auctionDetail = ref({}) const auctionDetail = ref({})
const loading = ref(false) const loading = ref(false)

View File

@ -2,34 +2,58 @@ import { createGlobalState } from '@vueuse/core'
import {ref} from "vue"; import {ref} from "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 {message} from "~/components/x-message/useMessage.js";
export const liveStore = createGlobalState(() => { export const liveStore = createGlobalState(() => {
const {auctionDetail,getAuctionDetail} = goodStore(); const {auctionDetail,getAuctionDetail} = goodStore();
const { token } = authStore()
const quoteStatus = ref(false) const quoteStatus = ref(false)
const show = ref(false) const show = ref(false)
const cleanup = ref(null)
const show1=ref(true) const show1=ref(true)
const playerId=ref('J_prismPlayer') const playerId=ref('J_prismPlayer')
const auctionData=ref({}) const auctionData=ref({})
const getSocketData=async ()=>{ const getSocketData = async () => {
if (!auctionDetail.value.uuid){ if (!auctionDetail.value.uuid) {
await getAuctionDetail() await getAuctionDetail()
} }
const { ws, messages, onMessage } = useWebSocket()
// 连接 const { ws, onMessage } = useWebSocket()
ws.connect('/api/v1/m/auction/live',{auctionUuid: auctionDetail.value.uuid,token:token.value}) // 建立新连接
ws.connect('/api/v1/m/auction/live', {
auctionUuid: auctionDetail.value.uuid,
/*// 发送消息 })
ws.send({ type: 'chat', content: 'Hello!' })*/ // 保存清理函数
// 监听消息 cleanup.value = onMessage((data) => {
onMessage((data) => {
auctionData.value = data.data auctionData.value = data.data
console.log('auctionData.value',auctionData.value) if (auctionData.value.wsType === 'tip') {
if (auctionData.value.tip?.tipType === 'falling') {
message.warning({
title: {
text: '即将落槌',
color: '#F09F1F',
align: 'center',
},
style: {
width: '151px',
bottom: '230px'
},
}, 500000)
}
}
console.log('auctionData.value', auctionData.value)
}) })
} }
const changeStatus = () => { const changeStatus = () => {
if (auctionData.value.artwork.isSelling){
quoteStatus.value = !quoteStatus.value quoteStatus.value = !quoteStatus.value
}else {
if (quoteStatus.value){
quoteStatus.value = false
}
}
} }
return{ return{
auctionData, auctionData,