Files
lilishop-ui/manager/src/views/live/components/LiveChatPanel.vue
Ryan Ran 709d114986 feat: 添加直播聊天功能与相关配置
- 在 LiveChatPanel.vue 中实现直播间聊天功能,支持发送和接收消息。
- 新增 useChat.js 组合式 API,管理聊天逻辑与状态。
- 更新 Live.vue 控制面板,集成聊天组件并优化直播状态显示。
- 更新 package.json 和 pnpm-lock.yaml,添加 @tencentcloud/chat 和 hls.js 依赖。
2026-07-07 16:40:09 +08:00

252 lines
5.2 KiB
Vue
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>
<div class="live-chat-panel">
<div v-if="initError" class="chat-error">
<el-alert :title="initError" type="warning" :closable="false" show-icon />
</div>
<div ref="chatListRef" class="chat-list" @scroll="onScroll">
<div v-if="isLoadingHistory" class="chat-loading">加载历史消息...</div>
<div
v-for="msg in messageList"
:key="msg.messageId"
class="chat-item"
:class="{ 'is-system': msg.isSystem }"
>
<el-avatar v-if="!msg.isSystem" :src="msg.avatar" :size="28">
{{ (msg.username || "").charAt(0) }}
</el-avatar>
<div class="chat-bubble">
<div v-if="!msg.isSystem" class="chat-user">{{ msg.username }}</div>
<div class="chat-text">{{ msg.message }}</div>
</div>
</div>
<el-empty v-if="!messageList.length && !initError && !initializing" :image-size="64" description="暂无聊天消息" />
<div ref="bottomAnchorRef" class="chat-bottom-anchor" />
</div>
<div v-if="unreadCount > 0" class="chat-unread" @click="scrollToBottomAndClearUnread">
{{ unreadCount }} 条新消息
</div>
<div class="chat-input-bar">
<el-input
v-model="messageContent"
placeholder="发送消息到直播间..."
maxlength="200"
@keyup.enter="createTextMessageByInput"
/>
<el-button type="primary" :disabled="!messageContent.trim()" @click="handleSend">
发送
</el-button>
</div>
</div>
</template>
<script>
import Cookies from "js-cookie";
import { toRef } from "vue";
import { useChat } from "../composables/useChat";
export default {
name: "LiveChatPanel",
props: {
liveId: {
type: String,
required: true,
},
liveDetail: {
type: Object,
default: () => ({}),
},
active: {
type: Boolean,
default: true,
},
},
setup(props) {
let userInfo = {};
try {
userInfo = JSON.parse(Cookies.get("userInfoManager") || "{}");
} catch {
userInfo = {};
}
return useChat(toRef(props, "liveId"), userInfo);
},
data() {
return {
initializing: false,
chatInited: false,
};
},
computed: {
/** 腾讯云 IM 群组:优先后端字段,否则用直播间 ID与旧版中控台一致 */
imGroupId() {
return this.liveDetail?.imGroupId || this.liveDetail?.groupId || this.liveId || "";
},
},
watch: {
imGroupId: {
immediate: true,
handler(id) {
if (!id) return;
this.setGroupID(id);
if (this.active) {
this.bootstrapChat();
}
},
},
active(val) {
if (val && this.imGroupId && !this.chatInited) {
this.bootstrapChat();
}
},
liveId() {
this.chatInited = false;
this.cleanup();
if (this.active && this.imGroupId) {
this.bootstrapChat();
}
},
},
beforeUnmount() {
this.cleanup();
},
methods: {
handleSend() {
this.createTextMessageByInput();
},
async bootstrapChat() {
if (!this.liveId || this.chatInited || this.initializing) {
return;
}
const groupId = this.imGroupId;
if (!groupId) {
this.initError = "缺少直播间 ID无法初始化聊天";
return;
}
this.setGroupID(groupId);
this.initializing = true;
try {
await this.initTencentIm();
if (!this.initError) {
this.chatInited = true;
}
} catch (error) {
console.error("初始化直播聊天失败:", error);
this.initError = "聊天初始化失败,请刷新重试";
this.chatInited = false;
} finally {
this.initializing = false;
}
},
},
};
</script>
<style lang="scss" scoped>
.live-chat-panel {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
min-height: 0;
max-height: 100%;
overflow: hidden;
position: relative;
}
.chat-error {
flex-shrink: 0;
margin-bottom: 8px;
}
.chat-list {
flex: 1 1 0;
height: 0;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 4px 0;
overscroll-behavior: contain;
}
.chat-bottom-anchor {
height: 1px;
flex-shrink: 0;
}
.chat-loading {
text-align: center;
color: #909399;
font-size: 12px;
padding: 8px 0;
}
.chat-item {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 12px;
&.is-system {
justify-content: center;
.chat-bubble {
background: #f4f4f5;
border-radius: 12px;
padding: 4px 12px;
}
.chat-text {
color: #909399;
font-size: 12px;
}
}
}
.chat-bubble {
min-width: 0;
flex: 1;
}
.chat-user {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.chat-text {
font-size: 14px;
color: #303133;
line-height: 1.5;
word-break: break-all;
}
.chat-unread {
position: absolute;
left: 50%;
bottom: 56px;
transform: translateX(-50%);
padding: 4px 12px;
background: $theme_color;
color: #fff;
font-size: 12px;
border-radius: 12px;
cursor: pointer;
z-index: 2;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.chat-input-bar {
flex-shrink: 0;
display: flex;
gap: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
background: #fff;
}
</style>