需要添加直播接口

This commit is contained in:
cbb
2026-01-12 17:52:15 +08:00
parent 83fec2617c
commit 13af9eb303
281 changed files with 313157 additions and 104 deletions

View File

@@ -0,0 +1,418 @@
<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 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 handleReceiveGift = {
callback: (event) => {
const res = JSON.parse(event)
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>