|
|
|
|
@ -4,8 +4,8 @@ |
|
|
|
|
<div class="chat-header"> |
|
|
|
|
<div class="chat-title"> |
|
|
|
|
<span class="name" v-if="currentChat">{{ currentChat.name }}</span> |
|
|
|
|
<span v-if="currentChat && currentChat.scene === 4"> |
|
|
|
|
({{ currentChat.user_num }}) |
|
|
|
|
<span v-if="currentChat && currentChat.scene === ContactsScene.GROUP"> |
|
|
|
|
({{ currentChat.user_num - 1 }}) |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
<div class="chat-actions"> |
|
|
|
|
@ -13,12 +13,13 @@ |
|
|
|
|
type="text" |
|
|
|
|
icon="el-icon-chat-line-round" |
|
|
|
|
@click="handleSearchRecord" |
|
|
|
|
v-if="currentChat && currentChat.scene != ContactsScene.NOTIFY" |
|
|
|
|
/> |
|
|
|
|
<el-button |
|
|
|
|
type="text" |
|
|
|
|
icon="el-icon-s-tools" |
|
|
|
|
@click="handleGroupSetting" |
|
|
|
|
v-if="currentChat && currentChat.scene === 4" |
|
|
|
|
v-if="currentChat && currentChat.scene === ContactsScene.GROUP" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
@ -41,90 +42,122 @@ |
|
|
|
|
<div |
|
|
|
|
v-for="(msg, index) in currentMessages" |
|
|
|
|
:key="msg.message_id || msg.id || index" |
|
|
|
|
:class="['message-item', msg.is_self ? 'self' : 'other']" |
|
|
|
|
:class="[ |
|
|
|
|
'message-item', |
|
|
|
|
msg.is_self ? 'self' : 'other', |
|
|
|
|
{ |
|
|
|
|
'system-message': |
|
|
|
|
msg.type === MessageType.MULTI_CHAT_INIT_JOIN || |
|
|
|
|
msg.type === MessageType.MULTI_CHAT_QUIT || |
|
|
|
|
msg.type === MessageType.MULTI_CHAT_EDIT || |
|
|
|
|
msg.type === MessageType.MULTI_CHAT_JOIN, |
|
|
|
|
}, |
|
|
|
|
]" |
|
|
|
|
> |
|
|
|
|
<!-- 时间分割线 --> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ formatMessageTime(msg.create_time) }} |
|
|
|
|
</div> |
|
|
|
|
<div |
|
|
|
|
v-if="msg.type === MessageType.MULTI_CHAT_INIT_JOIN" |
|
|
|
|
class="time-divider" |
|
|
|
|
> |
|
|
|
|
{{ |
|
|
|
|
currentChat.user_num > 0 |
|
|
|
|
? `${currentChat.source_name || "成员"}邀请了${ |
|
|
|
|
currentChat.user_num |
|
|
|
|
}人加入群聊` |
|
|
|
|
: `${currentChat.source_name || "成员"}加入了群聊` |
|
|
|
|
}} |
|
|
|
|
</div> |
|
|
|
|
<div |
|
|
|
|
v-if="msg.type === MessageType.MULTI_CHAT_QUIT" |
|
|
|
|
class="time-divider" |
|
|
|
|
> |
|
|
|
|
{{ msg.source_name || "成员" }}退出了群聊 |
|
|
|
|
</div> |
|
|
|
|
<!-- 系统消息:群成员变更通知 --> |
|
|
|
|
<div |
|
|
|
|
class="message-content-wrap" |
|
|
|
|
v-if=" |
|
|
|
|
msg.type !== MessageType.MULTI_CHAT_INIT_JOIN && |
|
|
|
|
msg.type !== MessageType.MULTI_CHAT_QUIT |
|
|
|
|
msg.type === MessageType.MULTI_CHAT_INIT_JOIN || |
|
|
|
|
msg.type === MessageType.MULTI_CHAT_JOIN |
|
|
|
|
" |
|
|
|
|
> |
|
|
|
|
<!-- 头像 --> |
|
|
|
|
<el-avatar |
|
|
|
|
:size="36" |
|
|
|
|
:src=" |
|
|
|
|
msg.is_self |
|
|
|
|
? $store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
$store.state.user.userInfo.avatar |
|
|
|
|
: $store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
currentChat.avatar |
|
|
|
|
" |
|
|
|
|
:icon="msg.is_self ? 'el-icon-user-solid' : 'el-icon-user'" |
|
|
|
|
class="message-avatar" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<div class="message-body"> |
|
|
|
|
<!-- 发送者名称(群聊且非自己) --> |
|
|
|
|
<div |
|
|
|
|
class="message-name" |
|
|
|
|
v-if="!msg.is_self && currentChat && currentChat.scene === 4" |
|
|
|
|
> |
|
|
|
|
{{ msg.sender_name || msg.source_name || "未知用户" }} |
|
|
|
|
</div> |
|
|
|
|
<!-- 消息气泡 --> |
|
|
|
|
<div :class="['message-bubble', msg.type]"> |
|
|
|
|
<!-- 文本消息 --> |
|
|
|
|
<div |
|
|
|
|
v-if=" |
|
|
|
|
msg.type === MessageType.TEXT || (!msg.type && msg.content) |
|
|
|
|
" |
|
|
|
|
class="text-message" |
|
|
|
|
v-html="formatTextContent(msg.payload || msg.content)" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<!-- 图片消息 --> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ formatMessageTime(msg.update_time) }} |
|
|
|
|
</div> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ |
|
|
|
|
(msg.source_name || "成员") + |
|
|
|
|
(JSON.parse(msg.content).invited_user_ids.length > 0 |
|
|
|
|
? `邀请了${ |
|
|
|
|
JSON.parse(msg.content).invited_user_ids.length || 0 |
|
|
|
|
}人加入群聊` |
|
|
|
|
: "加入了群聊") |
|
|
|
|
}} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<template v-else-if="msg.type === MessageType.MULTI_CHAT_QUIT"> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ formatMessageTime(msg.update_time) }} |
|
|
|
|
</div> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ msg.source_name || "成员" }}退出了群聊 |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
<div v-else-if="msg.type === MessageType.MULTI_CHAT_EDIT"> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ formatMessageTime(msg.update_time) }} |
|
|
|
|
</div> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ |
|
|
|
|
(msg.sender_name || msg.source_name || "未知用户") + |
|
|
|
|
`修改了群名为"${JSON.parse(msg.content).name || ""}"` |
|
|
|
|
}} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 普通消息 --> |
|
|
|
|
<div v-else> |
|
|
|
|
<div class="time-divider"> |
|
|
|
|
{{ formatMessageTime(msg.update_time) }} |
|
|
|
|
</div> |
|
|
|
|
<div class="message-content-wrap"> |
|
|
|
|
<!-- 头像 --> |
|
|
|
|
<el-avatar |
|
|
|
|
:size="36" |
|
|
|
|
:src=" |
|
|
|
|
msg.is_self |
|
|
|
|
? $store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
$store.state.user.userInfo.avatar |
|
|
|
|
: currentChat.scene === ContactsScene.GROUP |
|
|
|
|
? $store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
msg.avatar |
|
|
|
|
: $store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
currentChat.avatar |
|
|
|
|
" |
|
|
|
|
class="message-avatar" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<div class="message-body"> |
|
|
|
|
<!-- 发送者名称(群聊且非自己) --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.IMAGE" |
|
|
|
|
class="text-message" |
|
|
|
|
class="message-name" |
|
|
|
|
v-if="!msg.is_self && currentChat && currentChat.scene === 4" |
|
|
|
|
> |
|
|
|
|
<el-image |
|
|
|
|
:src=" |
|
|
|
|
$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
msg.file_path |
|
|
|
|
{{ msg.sender_name || msg.source_name || "未知用户" }} |
|
|
|
|
</div> |
|
|
|
|
<!-- 消息气泡 --> |
|
|
|
|
<div :class="['message-bubble', msg.type]"> |
|
|
|
|
<!-- 文本消息 --> |
|
|
|
|
<div |
|
|
|
|
v-if=" |
|
|
|
|
msg.type === MessageType.TEXT || (!msg.type && msg.content) |
|
|
|
|
" |
|
|
|
|
:alt="msg.file_name" |
|
|
|
|
:preview-src-list="[ |
|
|
|
|
$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
msg.file_path, |
|
|
|
|
]" |
|
|
|
|
class="text-message" |
|
|
|
|
v-html="formatTextContent(msg.payload || msg.content)" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 视频消息 --> |
|
|
|
|
<!-- <div |
|
|
|
|
<!-- 图片消息 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.IMAGE" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
<el-image |
|
|
|
|
:src=" |
|
|
|
|
$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
msg.file_path |
|
|
|
|
" |
|
|
|
|
:alt="msg.file_name" |
|
|
|
|
:preview-src-list="[ |
|
|
|
|
$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
msg.file_path, |
|
|
|
|
]" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 视频消息 --> |
|
|
|
|
<!-- <div |
|
|
|
|
v-else-if="msg.type === MessageType.VIDEO" |
|
|
|
|
class="video-message" |
|
|
|
|
> |
|
|
|
|
@ -136,139 +169,148 @@ |
|
|
|
|
/> |
|
|
|
|
</div> --> |
|
|
|
|
|
|
|
|
|
<!-- 语音消息 --> |
|
|
|
|
<!-- <div |
|
|
|
|
<!-- 语音消息 --> |
|
|
|
|
<!-- <div |
|
|
|
|
v-else-if="msg.type === MessageType.AUDIO" |
|
|
|
|
class="audio-message" |
|
|
|
|
> |
|
|
|
|
<audio :src="getFileUrl(msg.payload || msg.content)" controls /> |
|
|
|
|
</div> --> |
|
|
|
|
|
|
|
|
|
<!-- 文件消息 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.FILE" |
|
|
|
|
class="file-message" |
|
|
|
|
> |
|
|
|
|
<div class="file-card" @click="handleDownloadFile(msg)"> |
|
|
|
|
<div class="file-thumb"> |
|
|
|
|
<i |
|
|
|
|
:class="getFileIconClass(msg.file_name) + ' file-icon'" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<div class="file-info"> |
|
|
|
|
<div class="file-name">{{ msg.file_name }}</div> |
|
|
|
|
<div class="file-size"> |
|
|
|
|
{{ formatFileSize(msg.file_size) }} |
|
|
|
|
<!-- 文件消息 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.FILE" |
|
|
|
|
class="file-message" |
|
|
|
|
> |
|
|
|
|
<div class="file-card" @click="handleDownloadFile(msg)"> |
|
|
|
|
<div class="file-thumb"> |
|
|
|
|
<i |
|
|
|
|
:class="getFileIconClass(msg.file_name) + ' file-icon'" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<div class="file-status"> |
|
|
|
|
<el-button |
|
|
|
|
type="text" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleDownload(msg)" |
|
|
|
|
> |
|
|
|
|
打开 |
|
|
|
|
</el-button> |
|
|
|
|
<el-button |
|
|
|
|
type="text" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenLocation(msg)" |
|
|
|
|
> |
|
|
|
|
打开目录 |
|
|
|
|
</el-button> |
|
|
|
|
<div class="file-info"> |
|
|
|
|
<div class="file-name">{{ msg.file_name }}</div> |
|
|
|
|
<div class="file-size"> |
|
|
|
|
{{ formatFileSize(msg.file_size) }} |
|
|
|
|
</div> |
|
|
|
|
<div class="file-status"> |
|
|
|
|
<el-button |
|
|
|
|
type="text" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleDownload(msg)" |
|
|
|
|
> |
|
|
|
|
打开 |
|
|
|
|
</el-button> |
|
|
|
|
<el-button |
|
|
|
|
type="text" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenLocation(msg)" |
|
|
|
|
> |
|
|
|
|
打开目录 |
|
|
|
|
</el-button> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 病例库分享 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.REPORT_SHARE" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
病例分享: 姓名:{{ JSON.parse(msg.content).name }}, 年龄:{{ |
|
|
|
|
JSON.parse(msg.content).age |
|
|
|
|
}}, 性别:{{ JSON.parse(msg.content).sex }}, |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenReport(msg)" |
|
|
|
|
<!-- 病例库分享 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.REPORT_SHARE" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
病例ID:{{ JSON.parse(msg.content).report_id }} |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
病例分享: 姓名:{{ JSON.parse(msg.content).name }}, 年龄:{{ |
|
|
|
|
JSON.parse(msg.content).age |
|
|
|
|
}}, 性别:{{ JSON.parse(msg.content).sex }}, |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenReport(msg)" |
|
|
|
|
> |
|
|
|
|
病例ID:{{ JSON.parse(msg.content).report_id }} |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 知识分享 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.KNOWLEDGE_SHARE" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
知识库分享: |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenKnowledge(msg)" |
|
|
|
|
<!-- 知识分享 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.KNOWLEDGE_SHARE" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
资源名称:{{ JSON.parse(msg.content).file_name }} |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.CONSULTATION_MESSAGE_INVITE" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
视讯邀请:{{ JSON.parse(msg.content).name }}邀请您参加会诊 |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenConsultationInvite(msg)" |
|
|
|
|
知识库分享: |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenKnowledge(msg)" |
|
|
|
|
> |
|
|
|
|
资源名称:{{ JSON.parse(msg.content).file_name }} |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
<div |
|
|
|
|
v-else-if=" |
|
|
|
|
msg.type === MessageType.CONSULTATION_MESSAGE_INVITE |
|
|
|
|
" |
|
|
|
|
class="text-message" |
|
|
|
|
> |
|
|
|
|
会诊口令:{{ JSON.parse(msg.content).room_id }} |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
<!-- 系统通知 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.NOTIFY_CONTENT" |
|
|
|
|
class="file-message" |
|
|
|
|
> |
|
|
|
|
<div class="file-card"> |
|
|
|
|
<div class="file-info"> |
|
|
|
|
<div class="file-name"> |
|
|
|
|
标题:{{ JSON.parse(msg.content).title }} |
|
|
|
|
</div> |
|
|
|
|
<div class="file-size"> |
|
|
|
|
内容:{{ JSON.parse(msg.content).content }} |
|
|
|
|
</div> |
|
|
|
|
<div class="file-status"> |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenNotify(msg)" |
|
|
|
|
> |
|
|
|
|
进入阅读 |
|
|
|
|
</el-link> |
|
|
|
|
视讯邀请:{{ JSON.parse(msg.content).name }}邀请您参加会诊 |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenConsultationInvite(msg)" |
|
|
|
|
> |
|
|
|
|
会诊口令:{{ JSON.parse(msg.content).room_id }} |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
<!-- 系统通知 --> |
|
|
|
|
<div |
|
|
|
|
v-else-if="msg.type === MessageType.NOTIFY_CONTENT" |
|
|
|
|
class="file-message" |
|
|
|
|
> |
|
|
|
|
<div class="file-card"> |
|
|
|
|
<div class="file-info"> |
|
|
|
|
<div class="file-name"> |
|
|
|
|
标题:{{ JSON.parse(msg.content).title }} |
|
|
|
|
</div> |
|
|
|
|
<div class="file-size"> |
|
|
|
|
内容:{{ JSON.parse(msg.content).content }} |
|
|
|
|
</div> |
|
|
|
|
<div class="file-status"> |
|
|
|
|
<el-link |
|
|
|
|
type="primary" |
|
|
|
|
:underline="false" |
|
|
|
|
@click="handleOpenNotify(msg)" |
|
|
|
|
> |
|
|
|
|
进入阅读 |
|
|
|
|
</el-link> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<!-- 消息状态 --> |
|
|
|
|
<div v-if="msg.is_self" class="message-status"> |
|
|
|
|
<span v-if="msg.sending" class="status-sending">发送中...</span> |
|
|
|
|
<span v-else-if="msg.sendFailed" class="status-failed" |
|
|
|
|
><i class="el-icon-warning" /> 失败</span |
|
|
|
|
> |
|
|
|
|
<span |
|
|
|
|
v-else-if="msg.read_status === 1 || msg.read" |
|
|
|
|
class="status-read" |
|
|
|
|
> |
|
|
|
|
已读 |
|
|
|
|
</span> |
|
|
|
|
<span v-else class="status-unread">{{ |
|
|
|
|
currentChat.scene === 4 |
|
|
|
|
? msg.unread_count |
|
|
|
|
? msg.unread_count + "人未读" |
|
|
|
|
: "未读" |
|
|
|
|
: "对方未读" |
|
|
|
|
}}</span> |
|
|
|
|
<!-- 消息状态 --> |
|
|
|
|
<div v-if="msg.is_self" class="message-status"> |
|
|
|
|
<span v-if="msg.sending" class="status-sending">发送中...</span> |
|
|
|
|
<span v-else-if="msg.sendFailed" class="status-failed" |
|
|
|
|
><i class="el-icon-warning" /> 失败</span |
|
|
|
|
> |
|
|
|
|
|
|
|
|
|
<span |
|
|
|
|
v-if=" |
|
|
|
|
currentChat.scene === ContactsScene.GROUP && |
|
|
|
|
currentChat.user_num - msg.read_user_ids.length != 0 |
|
|
|
|
" |
|
|
|
|
class="status-read" |
|
|
|
|
> |
|
|
|
|
{{ currentChat.user_num - msg.read_user_ids.length }}人未读 |
|
|
|
|
</span> |
|
|
|
|
<span |
|
|
|
|
v-else-if=" |
|
|
|
|
currentChat.scene !== ContactsScene.GROUP && |
|
|
|
|
msg.reader === 0 |
|
|
|
|
" |
|
|
|
|
class="status-unread" |
|
|
|
|
> |
|
|
|
|
未读 |
|
|
|
|
</span> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
@ -323,12 +365,15 @@ export default { |
|
|
|
|
data() { |
|
|
|
|
return { |
|
|
|
|
MessageType, |
|
|
|
|
ContactsScene, |
|
|
|
|
loadingHistory: false, |
|
|
|
|
hasMoreHistory: true, |
|
|
|
|
loadingNewer: false, |
|
|
|
|
hasMoreNewer: true, |
|
|
|
|
scrollDebounceHistory: false, |
|
|
|
|
scrollDebounceNewer: false, |
|
|
|
|
baseMessageId: 0, |
|
|
|
|
pageSize: 4, |
|
|
|
|
pageSize: 10, |
|
|
|
|
previewVisible: false, |
|
|
|
|
previewImageUrl: "", |
|
|
|
|
isNearBottom: true, |
|
|
|
|
@ -380,12 +425,47 @@ export default { |
|
|
|
|
this.setMessages({ key, messages: [] }); |
|
|
|
|
// 默认 base_message_id 为 0,加载最新消息 |
|
|
|
|
await this.loadMessages({ up: 1, baseMessageId: 0 }); |
|
|
|
|
|
|
|
|
|
// 自动加载历史消息直到填满可视区域 |
|
|
|
|
await this.$nextTick(); |
|
|
|
|
await this.preloadHistoryMessages(); |
|
|
|
|
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
this.scrollToBottom(); |
|
|
|
|
}); |
|
|
|
|
this.markAsRead(); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
async preloadHistoryMessages() { |
|
|
|
|
const container = this.$refs.messageContainer; |
|
|
|
|
if (!container) return; |
|
|
|
|
|
|
|
|
|
const containerHeight = container.clientHeight; |
|
|
|
|
let contentHeight = container.scrollHeight; |
|
|
|
|
let prevHeight = 0; |
|
|
|
|
let loadCount = 0; |
|
|
|
|
const maxLoadCount = 10; // 最多加载10次,防止无限循环 |
|
|
|
|
|
|
|
|
|
// 如果内容高度小于容器高度,说明消息不够,需要加载更多历史 |
|
|
|
|
while ( |
|
|
|
|
contentHeight < containerHeight && |
|
|
|
|
this.hasMoreHistory && |
|
|
|
|
!this.loadingHistory && |
|
|
|
|
loadCount < maxLoadCount |
|
|
|
|
) { |
|
|
|
|
prevHeight = contentHeight; |
|
|
|
|
await this.loadMessages({ up: 1 }); |
|
|
|
|
await this.$nextTick(); |
|
|
|
|
contentHeight = container.scrollHeight; |
|
|
|
|
loadCount++; |
|
|
|
|
|
|
|
|
|
// 高度没有变化,说明没有更多数据了 |
|
|
|
|
if (contentHeight === prevHeight) { |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 通用消息加载方法 |
|
|
|
|
// up: 0 表示加载更新消息(向下滚动),1 表示加载历史消息(向上滚动) |
|
|
|
|
async loadMessages({ up, baseMessageId }) { |
|
|
|
|
@ -424,7 +504,7 @@ export default { |
|
|
|
|
base_message_id: currentBaseMessageId, |
|
|
|
|
page: 1, |
|
|
|
|
size: this.pageSize, |
|
|
|
|
up: up, |
|
|
|
|
up: 1, |
|
|
|
|
target_id: this.currentChat.id, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
@ -453,12 +533,17 @@ export default { |
|
|
|
|
const processed = list.map((m) => this.processMessage(m)); |
|
|
|
|
let merged; |
|
|
|
|
|
|
|
|
|
// 创建已存在消息ID的集合用于去重 |
|
|
|
|
const existingIds = new Set(existing.map(m => m.message_id || m.id)); |
|
|
|
|
|
|
|
|
|
if (up === 0) { |
|
|
|
|
// 加载更新消息:追加到尾部 |
|
|
|
|
merged = [...existing, ...processed]; |
|
|
|
|
const newMessages = processed.filter(m => !existingIds.has(m.message_id || m.id)); |
|
|
|
|
merged = [...existing, ...newMessages]; |
|
|
|
|
} else { |
|
|
|
|
// 加载历史消息:插入到头部 |
|
|
|
|
merged = [...processed, ...existing]; |
|
|
|
|
const newMessages = processed.filter(m => !existingIds.has(m.message_id || m.id)); |
|
|
|
|
merged = [...newMessages, ...existing]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.setMessages({ key, messages: merged }); |
|
|
|
|
@ -486,25 +571,30 @@ export default { |
|
|
|
|
handleScroll(e) { |
|
|
|
|
const el = e.target; |
|
|
|
|
const scrollTop = el.scrollTop; |
|
|
|
|
const clientHeight = el.clientHeight; |
|
|
|
|
const scrollHeight = el.scrollHeight; |
|
|
|
|
const isNearBottom = scrollTop + clientHeight >= scrollHeight - 50; |
|
|
|
|
const isNearTop = scrollTop <= 30; |
|
|
|
|
|
|
|
|
|
// 向下滚动到底部 → 加载更新消息(最新的数据) |
|
|
|
|
if (isNearBottom && this.hasMoreNewer && !this.loadingNewer) { |
|
|
|
|
this.loadMessages({ up: 1 }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 向上滚动到顶部 → 加载历史消息(更早的消息) |
|
|
|
|
if (isNearTop && this.hasMoreHistory && !this.loadingHistory) { |
|
|
|
|
if ( |
|
|
|
|
isNearTop && |
|
|
|
|
this.hasMoreHistory && |
|
|
|
|
!this.loadingHistory && |
|
|
|
|
!this.scrollDebounceHistory |
|
|
|
|
) { |
|
|
|
|
this.scrollDebounceHistory = true; |
|
|
|
|
const oldHeight = scrollHeight; |
|
|
|
|
this.loadMessages({ up: 1 }).then(() => { |
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
const newHeight = el.scrollHeight; |
|
|
|
|
el.scrollTop = newHeight - oldHeight; |
|
|
|
|
this.loadMessages({ up: 1 }) |
|
|
|
|
.then(() => { |
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
const newHeight = el.scrollHeight; |
|
|
|
|
el.scrollTop = newHeight - oldHeight; |
|
|
|
|
}); |
|
|
|
|
}) |
|
|
|
|
.finally(() => { |
|
|
|
|
setTimeout(() => { |
|
|
|
|
this.scrollDebounceHistory = false; |
|
|
|
|
}, 1000); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
@ -645,7 +735,11 @@ export default { |
|
|
|
|
// 下载文件 |
|
|
|
|
handleDownload(item) { |
|
|
|
|
if (item.file_path) { |
|
|
|
|
window.open(this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + item.file_path, "_blank"); |
|
|
|
|
window.open( |
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
item.file_path, |
|
|
|
|
"_blank" |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
this.$message.warning("文件链接不可用"); |
|
|
|
|
} |
|
|
|
|
@ -656,7 +750,9 @@ export default { |
|
|
|
|
this.$modal.msg("浏览器安全限制:请下载文件后在下载列表中打开文件夹"); |
|
|
|
|
// 先触发下载 |
|
|
|
|
const a = document.createElement("a"); |
|
|
|
|
a.href = this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + item.file_path; |
|
|
|
|
a.href = |
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
item.file_path; |
|
|
|
|
a.download = item.file_name; |
|
|
|
|
a.target = "_blank"; |
|
|
|
|
document.body.appendChild(a); |
|
|
|
|
@ -680,10 +776,11 @@ export default { |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
handleOpenKnowledge(msg) { |
|
|
|
|
const knowledgeId = msg.payload.knowledge_id; |
|
|
|
|
if (knowledgeId) { |
|
|
|
|
this.$router.push(`/knowledge-base?id=${knowledgeId}`); |
|
|
|
|
} |
|
|
|
|
window.open( |
|
|
|
|
this.$store.state.user.netConfig.MINIO_ENDPOINT_HTTPS + |
|
|
|
|
JSON.parse(msg.content).file_path, |
|
|
|
|
"_blank" |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
handleOpenConsultationInvite(msg) { |
|
|
|
|
this.$router.push({ |
|
|
|
|
@ -754,6 +851,7 @@ export default { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
this.$emit("clear-unread", this.currentChat.id, scene); |
|
|
|
|
this.$emit("refresh-list"); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.error("标记已读失败", e); |
|
|
|
|
} |
|
|
|
|
|