pk优化版

This commit is contained in:
2026-02-26 13:15:19 +08:00
parent d4c0dcf6b1
commit 5c1911314f
22 changed files with 742 additions and 386 deletions

View File

@@ -1,9 +1,9 @@
<template>
<!-- 消息页面 -->
<div class="message-page">
<el-splitter class="message-splitter">
<div class="message-layout">
<!-- 会话列表 -->
<el-splitter-panel :size="25" :min="20" :max="35">
<div class="conversation-panel">
<div class="conversation-list">
<div
v-for="(item, index) in chatList"
@@ -13,9 +13,6 @@
@click="selectChat(item)"
>
<el-badge :value="item.unread > 0 ? item.unread : ''" :max="99">
<div class="conv-avatar">
<img :src="item.data?.avatar || defaultAvatar" alt="" />
</div>
</el-badge>
<div class="conv-info">
<div class="conv-header">
@@ -28,10 +25,10 @@
<div v-if="chatList.length === 0" class="empty-tip">暂无会话</div>
</div>
</el-splitter-panel>
</div>
<!-- 消息列表 -->
<el-splitter-panel>
<div class="chat-panel">
<div v-if="selectedChat" class="chat-container">
<div class="chat-messages" ref="chatMessagesRef">
<div
@@ -40,13 +37,10 @@
class="message-item"
:class="{ mine: msg.senderId == currentUser.id }"
>
<div class="message-avatar">
<img :src="msg.senderId == currentUser.id ? currentUser.headerIcon : selectedChat.data?.avatar" alt="" />
</div>
<div class="message-bubble">
<div v-if="msg.type === 'text'" class="text-message">{{ msg.payload.text }}</div>
<PictureMessage v-else-if="msg.type === 'image'" :item="msg" />
<PKMessage v-else-if="msg.type === 'pk'" :item="msg" />
<MiniPKMessage v-else-if="msg.type === 'pk'" :item="msg" />
<VoiceMessage v-else-if="msg.type === 'audio'" :item="msg.payload.url" :size="msg.payload.duration" />
</div>
</div>
@@ -73,8 +67,8 @@
<span class="material-icons-round placeholder-icon">chat_bubble_outline</span>
<p>选择左侧会话开始聊天</p>
</div>
</el-splitter-panel>
</el-splitter>
</div>
</div>
<!-- 隐藏的文件输入 -->
<input
@@ -88,7 +82,7 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { getMainUserData } from '@/utils/pk-mini/storage'
import { TimestamptolocalTime } from '@/utils/pk-mini/timeConversion'
import { isGoEasyEnabled } from '@/config/pk-mini'
@@ -102,9 +96,10 @@ import {
GoEasy
} from '@/utils/pk-mini/goeasy'
import PictureMessage from '@/components/pk-mini/chat/PictureMessage.vue'
import PKMessage from '@/components/pk-mini/chat/PKMessage.vue'
import MiniPKMessage from '@/components/pk-mini/chat/MiniPKMessage.vue'
import VoiceMessage from '@/components/pk-mini/chat/VoiceMessage.vue'
import { ElMessage } from 'element-plus'
import { pkUnreadStore } from '@/stores/pk-mini/notice.js'
const defaultAvatar = 'https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/default-avatar.png'
const chatList = ref([])
@@ -114,6 +109,7 @@ const inputText = ref('')
const currentUser = ref({})
const chatMessagesRef = ref(null)
const fileInputRef = ref(null)
const unreadStore = pkUnreadStore()
const formatTime = TimestamptolocalTime
@@ -125,7 +121,7 @@ async function loadConversations() {
try {
const result = await goEasyGetConversations()
chatList.value = result?.content || []
chatList.value = result?.content?.conversations || []
} catch (e) {
console.error('加载会话列表失败', e)
}
@@ -136,12 +132,15 @@ async function selectChat(item) {
selectedChat.value = item
try {
const messages = await goEasyGetMessages({ id: item.userId, timestamp: null })
const messages = await goEasyGetMessages({ id: String(item.userId), timestamp: null })
messagesList.value = messages || []
await nextTick()
scrollToBottom()
// 异步卡片内容加载完后再滚一次
setTimeout(scrollToBottom, 300)
// 标记消息已读
goEasyMessageRead({ id: item.userId }).catch(() => {})
goEasyMessageRead({ id: String(item.userId) }).catch(() => {})
unreadStore.decrease(item.unread || 0)
item.unread = 0
} catch (e) {
console.error('加载消息失败', e)
@@ -166,7 +165,7 @@ async function sendMessage() {
try {
const msg = await goEasySendMessage({
text: inputText.value,
id: selectedChat.value.userId,
id: String(selectedChat.value.userId),
avatar: currentUser.value.headerIcon,
nickname: currentUser.value.nickName
})
@@ -202,7 +201,7 @@ async function handleFileSelect(event) {
try {
const msg = await goEasySendImageMessage({
imagefile: file,
id: selectedChat.value.userId,
id: String(selectedChat.value.userId),
avatar: currentUser.value.headerIcon,
nickname: currentUser.value.nickName
})
@@ -219,14 +218,25 @@ onMounted(() => {
currentUser.value = getMainUserData() || {}
if (isGoEasyEnabled()) {
loadConversations()
// 监听新消息
const goeasy = getPkGoEasy()
if (goeasy) {
goeasy.im.on(GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, onMessageReceived)
goeasy.im.on(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, onConversationsUpdated)
}
}
// 切换回消息页面时,滚到聊天记录最底部
nextTick(() => setTimeout(scrollToBottom, 300))
})
// 监听 chatMessagesRef 出现selectedChat 从 null 变为有值时 DOM 才渲染)
watch(chatMessagesRef, (el) => {
if (el) setTimeout(scrollToBottom, 300)
})
function onConversationsUpdated(conversations) {
chatList.value = conversations.conversations || []
}
function onMessageReceived(message) {
if (!isGoEasyEnabled()) return
// 更新会话列表中的未读数
@@ -249,6 +259,7 @@ onUnmounted(() => {
if (goeasy) {
try {
goeasy.im.off(GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, onMessageReceived)
goeasy.im.off(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, onConversationsUpdated)
} catch (e) {
console.warn('清理 GoEasy 监听器失败', e)
}
@@ -262,13 +273,27 @@ onUnmounted(() => {
width: 100%;
height: 100%;
background-color: #ffffff;
border: 1px solid #f1f5f9; // slate-100
border: 1px solid #f1f5f9;
border-radius: 12px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-sm
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.message-splitter {
.message-layout {
display: flex;
height: 100%;
}
.conversation-panel {
width: 280px;
min-width: 220px;
flex-shrink: 0;
border-right: 1px solid #e5e7eb;
}
.chat-panel {
flex: 1;
min-width: 0;
height: 100%;
}

View File

@@ -71,111 +71,100 @@
</div>
<!-- 列表和聊天区域 -->
<el-splitter class="pk-splitter">
<el-splitter-panel>
<el-splitter>
<!-- 列表面板 -->
<el-splitter-panel :size="70" :resizable="false">
<div class="list-panel">
<div
v-infinite-scroll="loadMore"
:infinite-scroll-distance="100"
:infinite-scroll-disabled="loading || noMore"
class="pk-list"
>
<div
v-for="(item, index) in pkList"
:key="index"
class="pk-card"
:class="{ selected: selectedItem === item }"
@click="handleItemClick(item)"
>
<!-- 头像 -->
<div class="pk-avatar">
<img :src="item.anchorIcon" alt="" />
</div>
<div class="pk-info">
<!-- 个人信息 -->
<div class="pk-personal">
<span class="pk-name">{{ item.disPlayId }}</span>
<span class="pk-gender" :class="item.sex === 1 ? 'male' : 'female'">
{{ item.sex === 1 ? '男' : '女' }}
</span>
<span class="pk-country">{{ item.country }}</span>
</div>
<!-- 时间 -->
<div class="pk-time">PK时间本地时间: {{ formatTime(item.pkTime * 1000) }}</div>
<!-- PK信息 -->
<div class="pk-stats">
<img class="stat-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png" alt="" />
<span>金币: {{ item.coin }}K</span>
<img class="stat-icon session-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png" alt="" />
<span>场次: {{ item.pkNumber }}</span>
</div>
<!-- 备注 -->
<div class="pk-remark">{{ item.remark }}</div>
</div>
</div>
<div class="content-area">
<!-- 列表面板 -->
<div class="list-panel">
<div
v-infinite-scroll="loadMore"
:infinite-scroll-distance="100"
:infinite-scroll-disabled="loading || noMore"
class="pk-list"
>
<div
v-for="(item, index) in pkList"
:key="index"
class="pk-card"
:class="{ selected: selectedItem === item }"
@click="handleItemClick(item)"
>
<!-- 头像 -->
<div class="pk-avatar">
<img :src="item.anchorIcon" alt="" />
</div>
<div class="pk-info">
<!-- 个人信息 -->
<div class="pk-personal">
<span class="pk-name">{{ item.disPlayId }}</span>
<span class="pk-gender" :class="item.sex === 1 ? 'male' : 'female'">
{{ item.sex === 1 ? '男' : '女' }}
</span>
<span class="pk-country">{{ item.country }}</span>
</div>
<!-- 时间 -->
<div class="pk-time">PK时间本地时间: {{ formatTime(item.pkTime * 1000) }}</div>
<!-- PK信息 -->
<div class="pk-stats">
<img class="stat-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png" alt="" />
<span>金币: {{ item.coin }}K</span>
<img class="stat-icon session-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png" alt="" />
<span>场次: {{ item.pkNumber }}</span>
</div>
<!-- 备注 -->
<div class="pk-remark">{{ item.remark }}</div>
</div>
</div>
<div v-if="pkList.length === 0" class="empty-tip">暂无数据</div>
<div v-if="pkList.length === 0" class="empty-tip">暂无数据</div>
</div>
</div>
<!-- 聊天面板 -->
<div class="chat-panel">
<div v-if="selectedItem" class="chat-container">
<div class="chat-header">{{ chatUserInfo.nickName || '聊天' }}</div>
<div class="chat-messages" ref="chatMessagesRef">
<div
v-for="(msg, index) in messagesList"
:key="index"
class="message-item"
:class="{ mine: msg.senderId == currentUser.id, 'pk-message': msg.type === 'pk' }"
>
<div class="message-triangle" v-if="msg.type === 'text'"></div>
<div class="message-content">
<div v-if="msg.type === 'text'" class="text-message">{{ msg.payload.text }}</div>
<PictureMessage v-else-if="msg.type === 'image'" :item="msg" />
<MiniPKMessage v-else-if="msg.type === 'pk'" :item="msg" :compact="true" />
<VoiceMessage v-else-if="msg.type === 'audio'" :item="msg.payload.url" :size="msg.payload.duration" />
</div>
</div>
</el-splitter-panel>
<!-- 聊天面板 -->
<el-splitter-panel :size="30" :resizable="false">
<div class="chat-panel">
<div v-if="selectedItem" class="chat-container">
<div class="chat-header">{{ chatUserInfo.nickName || '聊天' }}</div>
<div class="chat-messages" ref="chatMessagesRef">
<div
v-for="(msg, index) in messagesList"
:key="index"
class="message-item"
:class="{ mine: msg.senderId == currentUser.id }"
>
<div class="message-avatar">
<img :src="msg.senderId == currentUser.id ? currentUser.headerIcon : chatUserInfo.headerIcon" alt="" />
</div>
<div class="message-triangle" v-if="msg.type === 'text'"></div>
<div class="message-content">
<div v-if="msg.type === 'text'" class="text-message">{{ msg.payload.text }}</div>
<PictureMessage v-else-if="msg.type === 'image'" :item="msg" />
<MiniPKMessage v-else-if="msg.type === 'pk'" :item="msg" />
<VoiceMessage v-else-if="msg.type === 'audio'" :item="msg.payload.url" :size="msg.payload.duration" />
</div>
</div>
</div>
<!-- 聊天输入区 -->
<div class="chat-input-area">
<div class="input-controls">
<div class="control-btns">
<div class="control-btn" @click="handleSendImage">
<img src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/Album.png" alt="" />
</div>
<!-- 聊天输入区 -->
<div class="chat-input-area">
<div class="input-controls">
<div class="control-btns">
<div class="control-btn" @click="handleSendImage">
<img src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/Album.png" alt="" />
</div>
<div class="control-btn" @click="handleInvite">
<img src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/chat_invite.png" alt="" />
</div>
</div>
<div class="send-btn" @click="sendMessage">发送</div>
</div>
<div class="input-box">
<textarea
v-model="inputText"
placeholder="输入消息..."
@keydown.enter.prevent="sendMessage"
></textarea>
</div>
<div class="control-btn" @click="handleInvite">
<img src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/chat_invite.png" alt="" />
</div>
</div>
<div v-else class="chat-placeholder">
<span>右方选择主播立即聊天</span>
</div>
<div class="send-btn" @click="sendMessage">发送</div>
</div>
</el-splitter-panel>
</el-splitter>
</el-splitter-panel>
</el-splitter>
<div class="input-box">
<textarea
v-model="inputText"
placeholder="输入消息..."
@keydown.enter.prevent="sendMessage"
></textarea>
</div>
</div>
</div>
<div v-else class="chat-placeholder">
<span>右方选择主播立即聊天</span>
</div>
</div>
</div>
<!-- 隐藏的文件输入 -->
<input
@@ -388,6 +377,7 @@ async function loadPkList() {
}
} catch (e) {
console.error('加载 PK 列表失败', e)
noMore.value = true
} finally {
loading.value = false
}
@@ -403,12 +393,14 @@ async function handleItemClick(item) {
if (isGoEasyEnabled()) {
// GoEasy 已启用,加载聊天消息
const messages = await goEasyGetMessages({ id: item.senderId, timestamp: null })
const messages = await goEasyGetMessages({ id: String(item.senderId), timestamp: null })
messagesList.value = messages || []
await nextTick()
scrollToBottom()
// 异步卡片内容加载完后再滚一次
setTimeout(scrollToBottom, 300)
// 标记消息已读
goEasyMessageRead({ id: item.senderId }).catch(() => {})
goEasyMessageRead({ id: String(item.senderId) }).catch(() => {})
} else {
messagesList.value = []
ElMessage.warning('聊天功能暂时不可用GoEasy 订阅未续费)')
@@ -446,7 +438,7 @@ async function sendMessage() {
try {
const msg = await goEasySendMessage({
text: inputText.value,
id: selectedItem.value.senderId,
id: String(selectedItem.value.senderId),
avatar: currentUser.value.headerIcon,
nickname: currentUser.value.nickName
})
@@ -483,7 +475,7 @@ async function handleFileSelect(event) {
try {
const msg = await goEasySendImageMessage({
imagefile: file,
id: selectedItem.value.senderId,
id: String(selectedItem.value.senderId),
avatar: currentUser.value.headerIcon,
nickname: currentUser.value.nickName
})
@@ -540,7 +532,16 @@ async function confirmInvite() {
const pkRecord = await createPkRecord({
pkIdA: selectedItem.value.id,
pkIdB: selectedAnchor.value.id,
userId: userId
userIdA: selectedItem.value.senderId,
userIdB: userId,
pkTime: selectedItem.value.pkTime,
pkNumber: selectedItem.value.pkNumber,
anchorIdA: selectedItem.value.anchorId,
anchorIdB: selectedAnchor.value.anchorId,
anchorIconA: selectedItem.value.anchorIcon,
anchorIconB: selectedAnchor.value.anchorIcon,
piIdA: selectedItem.value.id,
piIdB: selectedAnchor.value.id,
})
// 发送 PK 邀请消息
@@ -548,7 +549,7 @@ async function confirmInvite() {
msgid: pkRecord.id,
pkIdA: selectedItem.value.id,
pkIdB: selectedAnchor.value.id,
id: selectedItem.value.senderId,
id: String(selectedItem.value.senderId),
avatar: currentUser.value.headerIcon,
nickname: currentUser.value.nickName
})
@@ -573,31 +574,9 @@ onMounted(() => {
console.log('[PkHall] 当前用户数据:', currentUser.value)
console.log('[PkHall] 解析的用户 ID:', userId)
// 同时加载今日PK和PK大厅数据
// 初始加载 PK 大厅数据(通过 loadPkList 统一管理 page/loading/noMore 状态)
if (userId) {
// 加载今日PK
getPkList({
status: 0,
page: 0,
size: 10,
userId: userId,
condition: { type: 1 }
}).then(res => {
todayList.value = res || []
}).catch(() => {})
// 加载PK大厅
getPkList({
status: 0,
page: 0,
size: 10,
userId: userId,
condition: { type: 2 }
}).then(res => {
hallList.value = res || []
pkList.value = hallList.value
page.value = 1
}).catch(() => {})
loadPkList()
} else {
console.warn('[PkHall] 未找到用户 ID无法加载数据')
}
@@ -644,9 +623,10 @@ onUnmounted(() => {
border-bottom: 1px solid #f1f5f9; // slate-100
}
.pk-splitter {
.content-area {
flex: 1;
height: calc(100% - 100px);
display: flex;
min-height: 0;
}
// 切换按钮
@@ -769,7 +749,8 @@ onUnmounted(() => {
// 列表面板
.list-panel {
height: 100%;
flex: 1;
min-width: 0;
overflow: hidden;
}
@@ -894,9 +875,12 @@ onUnmounted(() => {
// 聊天面板
.chat-panel {
height: 100%;
width: 350px;
flex-shrink: 0;
border-left: 1px solid #f1f5f9; // slate-100
background-color: #ffffff;
display: flex;
flex-direction: column;
}
.chat-container {
@@ -929,21 +913,6 @@ onUnmounted(() => {
flex-direction: row-reverse;
}
.message-avatar {
width: 45px;
height: 45px;
border-radius: 10px;
overflow: hidden;
margin: 0 10px;
flex-shrink: 0;
}
.message-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.message-triangle {
width: 0;
height: 0;
@@ -962,6 +931,10 @@ onUnmounted(() => {
max-width: 65%;
}
.message-item.pk-message .message-content {
max-width: 100%;
}
.text-message {
padding: 10px 15px;
background: #f5f5f5;