Files
uniapp-im-shop/uni_modules/tuikit-atomic-x/components/BarrageList.nvue
cbb 20ccbf1f14 注释搜索:主播发送消息
观看列表只能主播跟管理员查看
2026-02-10 17:49:33 +08:00

431 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="barrage-container">
<!-- 聊天消息列表 -->
<list class="chat-list" scroll-y :show-scrollbar="false" :style="{ bottom: bottomPx + 'px' }">
<cell class="chat-item" v-if="mixMessageList.length > 0" v-for="(message) in mixMessageList"
:key="message?.sequence" @tap="onItemTap(message)">
<view class="message-content-wrapper">
<view class="nickname-content-gift" v-if="message?.gift">
<text class="chat-nickname"
numberOfLines="1">{{ message?.sender?.userName || message?.sender?.userID }}</text>
<view class="gift-row">
<text class="gift-prefix">{{ giftPrefix }}</text>
<text class="gift-recipient">{{ giftReceiverName }}</text>
<text class="gift-name">{{ message?.textContent || '' }}</text>
<image class="gift-icon" :src="message?.gift?.iconURL || ''" alt="" />
<text class="gift-count" style="font-size: 26rpx;">x{{ message?.count || '1' }}</text>
</view>
</view>
<view class="nickname-content-wrapper" v-else>
<view class="message-right" v-if="message?.sender?.userID === currentLive?.liveOwner.userID">
<text class="message-role">主播</text>
</view>
<view class="nickname-content-wrapper">
<text class="chat-nickname"
numberOfLines="1">{{ message?.sender?.userName || message?.sender?.userID }}</text>
<!-- 主播发送消息
<text v-if="message?.businessID == 'anchor'" class="chat-content">
{{ getAnchorMessageText(message) }}
</text> -->
<text class="chat-content">
{{ message?.textContent || '' }}
</text>
</view>
</view>
</view>
</cell>
<cell ref="ListBottom" style="height: 50rpx;"></cell>
</list>
<!-- GiftToast 提示 -->
<view class="toast-container" v-for="toast in visibleToasts" :key="toast.id" :style="getToastStyle(toast)">
<view class="toast-content">
<!-- 左侧用户信息 -->
<view class="user-info">
<image class="user-avatar" :src="toast?.avatarURL || defaultAvatarURL" mode="aspectFill" />
<view class="user-details">
<text class="username">{{ toast?.name || '' }}</text>
<text class="action-text">{{ toast?.desc || '' }}</text>
</view>
</view>
<!-- 右侧图标 -->
<view class="icon-container" v-if="toast.iconURL">
<image class="icon" :src="toast?.iconURL || ''" mode="aspectFit" />
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
import { useBarrageState } from "@/uni_modules/tuikit-atomic-x/state/BarrageState";
import { useGiftState } from "@/uni_modules/tuikit-atomic-x/state/GiftState";
import { useLiveListState } from "@/uni_modules/tuikit-atomic-x/state/LiveListState";
const props = defineProps<{
mode ?: 'anchor' | 'audience',
bottomPx ?: number,
liveID ?: string,
toast ?: any, // GiftToast 的 toast 属性
}>();
const mode = computed(() => props.mode || 'audience');
const bottomPx = computed(() => props.bottomPx ?? 0);
const liveID = computed(() => props.liveID || uni?.$liveID);
const { messageList } = useBarrageState(liveID.value);
const { addGiftListener, removeGiftListener } = useGiftState(liveID.value);
const { currentLive } = useLiveListState(liveID.value);
const mixMessageList = ref<any[]>(messageList.value || []);
const dom = uni.requireNativePlugin('dom');
const ListBottom = ref('ListBottom');
const giftPrefix = computed(() => mode.value === 'anchor' ? '送给我' : '送给');
const giftReceiverName = computed(() => {
if (mode.value === 'anchor') return '';
const owner = currentLive?.value?.liveOwner || {};
return owner?.userName || owner?.userID || '';
});
// GiftToast 相关状态和逻辑
const defaultAvatarURL = 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_01.png';
const defaultPosition = {
top: 'auto',
bottom: 720,
left: 32,
right: 'auto',
};
const toastHeight = 105;
const visibleToasts = ref<any[]>([]);
const generateId = () => {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
};
const showToast = (toastConfig : Partial<any>) => {
const toast = {
id: generateId(),
duration: 3000,
autoHide: true,
position: {
top: defaultPosition.top,
bottom: defaultPosition.bottom + (visibleToasts.value || []).length * toastHeight,
left: '32rpx',
right: defaultPosition.bottom,
},
...toastConfig
};
visibleToasts.value.push(toast);
if (toast.autoHide && toast.duration) {
setTimeout(() => {
hideToast(toast.id);
}, toast.duration);
}
return toast.id;
};
const hideToast = (id : string) => {
const index = visibleToasts.value.findIndex(toast => toast.id === id);
if (index > -1) {
const removedToast = visibleToasts.value.splice(index, 1)[0];
emit('toastClosed', removedToast);
}
};
const hideAllToasts = () => {
visibleToasts.value = [];
};
const getToastStyle = (toast : any) => {
const style : any = {
position: 'fixed',
zIndex: 999
};
if (toast.position) {
if (toast.position.top !== undefined) {
style.top = typeof toast.position.top === 'number' ? `${toast.position.top}rpx` : toast.position.top;
}
if (toast.position.bottom !== undefined) {
style.bottom = typeof toast.position.bottom === 'number' ? `${toast.position.bottom}rpx` : toast.position.bottom;
}
if (toast.position.left !== undefined) {
style.left = typeof toast.position.left === 'number' ? `${toast.position.left}rpx` : toast.position.left;
}
if (toast.position.right !== undefined) {
style.right = typeof toast.position.right === 'number' ? `${toast.position.right}rpx` : toast.position.right;
}
}
return style;
};
watch(messageList, (newVal, oldVal) => {
if (!newVal) return;
const value = newVal.slice((oldVal || []).length, (newVal || []).length);
if (value.length > 0) {
mixMessageList.value = [...mixMessageList.value, ...value];
dom.scrollToElement(ListBottom.value);
}
});
const emit = defineEmits(['itemTap', 'toastClosed']);
const onItemTap = (message : any) => {
emit('itemTap', message);
};
/** 主播发送自定义消息显示文本 */
const getAnchorMessageText = (message: any) => {
const data = JSON.parse(message.data)
return data.count
}
const handleReceiveGift = {
callback: (event) => {
const res = JSON.parse(event)
console.log('===消息接收===', res)
const value = {
...res,
textContent: `${res.gift?.name || ''}`,
};
mixMessageList.value = [...mixMessageList.value, value];
dom.scrollToElement(ListBottom.value);
}
}
onMounted(() => {
if (props.toast && Object.keys(props.toast).length > 0) {
showToast(props.toast);
}
addGiftListener(uni.$liveID, 'onReceiveGift', handleReceiveGift)
});
onUnmounted(() => {
removeGiftListener(uni.$liveID, 'onReceiveGift', handleReceiveGift)
});
// 暴露给父组件的方法
defineExpose({
showToast,
hideToast,
hideAllToasts
});
</script>
<style>
.barrage-container {
position: relative;
}
.chat-list {
position: fixed;
left: 32rpx;
right: 32rpx;
bottom: 800rpx;
height: 380rpx;
width: 500rpx;
}
/* GiftToast 样式 */
.toast-container {
/* 基础样式由getToastStyle动态设置 */
}
.toast-content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: rgba(34, 38, 46, 0.4);
padding: 20rpx 24rpx;
border: 1rpx solid rgba(255, 255, 255, 0.1);
height: 100rpx;
width: 320rpx;
border-radius: 50rpx;
backdrop-filter: blur(10rpx);
}
.user-info {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
}
.user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
border-width: 2rpx;
border-color: #8B5CF6;
margin-right: 16rpx;
}
.user-details {
display: flex;
flex-direction: column;
flex: 1;
}
.username {
color: #ffffff;
font-size: 28rpx;
font-weight: 600;
margin-bottom: 4rpx;
max-width: 120rpx;
lines: 1;
text-overflow: ellipsis;
overflow: hidden;
}
.action-text {
color: rgba(255, 255, 255, 0.8);
font-size: 24rpx;
font-weight: 400;
max-width: 120rpx;
lines: 1;
text-overflow: ellipsis;
overflow: hidden;
}
.icon-container {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
.icon {
width: 80rpx;
height: 80rpx;
}
.chat-item {
flex-direction: row;
align-items: flex-start;
border-radius: 32rpx;
padding: 6rpx;
width: fit-content;
max-width: 500rpx;
}
.message-content-wrapper {
flex-direction: row;
align-items: flex-start;
min-width: 0;
max-width: 500rpx;
width: fit-content;
background-color: rgba(0, 0, 0, 0.25);
border-radius: 999rpx;
padding: 8rpx 12rpx;
}
.nickname-content-wrapper {
flex: 1;
flex-direction: row;
align-items: flex-start;
justify-content: center;
min-width: 0;
}
.nickname-content-gift {
flex-direction: row;
align-items: flex-start;
min-width: 0;
max-width: 500rpx;
width: fit-content;
}
.message-role {
background-color: #0468FC;
border-radius: 999px;
margin-right: 5rpx;
color: #fff;
padding: 5rpx 15rpx;
font-size: 20rpx;
}
.chat-nickname {
color: #80BEF6;
font-size: 24rpx;
line-height: 24rpx;
margin-right: 8rpx;
padding: 5rpx 0;
flex-shrink: 0;
max-width: 200rpx;
lines: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.gift-row {
display: flex;
flex-direction: row;
align-items: center;
max-width: 300rpx;
flex: 1;
justify-content: flex-start;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
flex-wrap: wrap;
padding-top: 2rpx;
}
.gift-icon {
width: 24rpx;
height: 24rpx;
flex-shrink: 0;
margin: 0;
}
.gift-recipient,
.gift-name {
color: #ffffff;
font-size: 24rpx;
line-height: 24rpx;
font-weight: 500;
z-index: 999;
padding: 2rpx 0;
max-width: 300rpx;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
text-align: left;
min-width: 0;
}
.gift-prefix,
.gift-count {
color: #ffffff;
font-size: 24rpx;
line-height: 24rpx;
font-weight: 500;
z-index: 999;
}
.chat-content {
color: #ffffff;
font-size: 24rpx;
line-height: 24rpx;
font-weight: 500;
z-index: 999;
padding: 5rpx 0;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
flex: 1;
min-width: 0;
text-indent: 0;
padding-left: 0;
}
</style>