chore: bump version to 2.10.7 and improve message sending reliability with explicit refresh and type safety

This commit is contained in:
jxxghp
2026-04-28 13:04:05 +08:00
parent b785769138
commit f761cdff00
3 changed files with 120 additions and 44 deletions

View File

@@ -14,6 +14,12 @@ import { getQueryValue } from '@/@core/utils'
import { useI18n } from 'vue-i18n'
import { clearAppBadge } from '@/utils/badge'
type MessageViewExpose = {
pauseSSE?: () => void
resumeSSE?: () => void
refreshLatestMessages?: () => Promise<void> | void
}
// 国际化
const { t } = useI18n()
@@ -63,7 +69,7 @@ const sendButtonDisabled = ref(false)
const messageDialogRef = ref<any>(null)
// 消息视图引用
const messageViewRef = ref<any>(null)
const messageViewRef = ref<MessageViewExpose | null>(null)
// 滚动容器引用
const messageContentRef = ref<any>()
@@ -153,9 +159,7 @@ async function openMessageDialog() {
}, 600)
// 等待对话框打开后恢复SSE连接
nextTick(() => {
if (messageViewRef.value && typeof messageViewRef.value.resumeSSE === 'function') {
messageViewRef.value.resumeSSE()
}
messageViewRef.value?.resumeSSE?.()
})
}
@@ -203,16 +207,23 @@ function allLoggingUrl() {
// 发送消息
async function sendMessage() {
if (user_message.value) {
try {
sendButtonDisabled.value = true
await api.post(`message/web?text=${user_message.value}`)
user_message.value = ''
sendButtonDisabled.value = false
forceScrollToEnd() // 发送消息后强制滚动到底部
} catch (error) {
console.error(error)
}
const messageText = user_message.value.trim()
if (!messageText) {
return
}
try {
sendButtonDisabled.value = true
await api.post(`message/web?text=${encodeURIComponent(messageText)}`)
user_message.value = ''
// 发送成功后主动同步最新一页消息避免SSE短暂断流时界面停留在旧状态。
await messageViewRef.value?.refreshLatestMessages?.()
forceScrollToEnd() // 发送消息后强制滚动到底部
} catch (error) {
console.error(error)
} finally {
sendButtonDisabled.value = false
}
}
@@ -228,7 +239,7 @@ defineExpose({
// 监听消息对话框状态变化
watch(messageDialog, newValue => {
if (!newValue && messageViewRef.value && typeof messageViewRef.value.pauseSSE === 'function') {
if (!newValue && messageViewRef.value?.pauseSSE) {
// 对话框关闭时暂停SSE连接
messageViewRef.value.pauseSSE()
}

View File

@@ -17,6 +17,10 @@ const messages = ref<Message[]>([])
// 当前页数据
const currData = ref<Message[]>([])
// 已加载消息的签名集合
// 使用消息内容签名去重,避免仅按秒级时间戳判断时误吞同一秒内的不同消息。
const messageKeys = new Set<string>()
// 是否完成加载
const isLoaded = ref(false)
@@ -29,18 +33,72 @@ const page = ref(1)
// 存量消息最新时间
const lastTime = ref('')
// 获取消息时间
function getMessageTime(message: Message) {
return message.reg_time || message.date || ''
}
// 生成消息签名
function getMessageKey(message: Message) {
return [
message.action ?? '',
message.userid ?? '',
message.reg_time ?? '',
message.date ?? '',
message.title ?? '',
message.text ?? '',
message.image ?? '',
message.link ?? '',
message.note ?? '',
].join('::')
}
// 排序消息列表,确保最新消息始终位于底部
function sortMessages(items: Message[]) {
return [...items].sort((a, b) => compareTime(getMessageTime(a), getMessageTime(b)))
}
// 记录最新消息时间
function updateLastTime(message: Message) {
const messageTime = getMessageTime(message)
if (messageTime && compareTime(messageTime, lastTime.value) > 0) {
lastTime.value = messageTime
}
}
// 合并消息到当前列表
function mergeMessages(items: Message[]) {
let hasNewMessage = false
for (const item of sortMessages(items)) {
const messageKey = getMessageKey(item)
if (messageKeys.has(messageKey)) {
continue
}
messageKeys.add(messageKey)
messages.value.push(item)
updateLastTime(item)
hasNewMessage = true
}
if (hasNewMessage) {
messages.value = sortMessages(messages.value)
}
return hasNewMessage
}
// SSE消息处理函数
function handleSSEMessage(event: MessageEvent) {
const message = event.data
if (message) {
const object = JSON.parse(message)
// 使用reg_time或date字段进行比较
const messageTime = object.reg_time || object.date
if (compareTime(messageTime, lastTime.value) <= 0) return
messages.value.push(object)
nextTick(() => {
emit('scroll') // 新消息到达时触发智能滚动
})
if (mergeMessages([object])) {
nextTick(() => {
emit('scroll') // 新消息到达时触发智能滚动
})
}
}
}
@@ -75,28 +133,10 @@ async function loadMessages({ done }: { done: any }) {
// 已加载过
isLoaded.value = true
if (currData.value.length > 0) {
// 按时间排序,确保最新的消息在最后
currData.value.sort((a, b) => {
const timeA = a.reg_time || a.date || ''
const timeB = b.reg_time || b.date || ''
return compareTime(timeA, timeB)
})
// 取最后一条时间为存量消息最新时间
const lastMessage = currData.value[currData.value.length - 1]
lastTime.value = lastMessage.reg_time || lastMessage.date || ''
// 合并数据并重新排序
const allMessages = [...currData.value, ...messages.value]
allMessages.sort((a, b) => {
const timeA = a.reg_time || a.date || ''
const timeB = b.reg_time || b.date || ''
return compareTime(timeA, timeB)
})
messages.value = allMessages
const hasNewMessage = mergeMessages(currData.value)
// 首次加载时滚动到底部
if (page.value === 1) {
if (page.value === 1 && hasNewMessage) {
nextTick(() => {
emit('scroll')
})
@@ -118,6 +158,26 @@ async function loadMessages({ done }: { done: any }) {
}
}
// 主动刷新最新一页消息作为SSE偶发丢流时的兜底
async function refreshLatestMessages() {
try {
const latestMessages = (await api.get('message/web', {
params: {
page: 1,
size: 20,
},
})) as Message[]
if (mergeMessages(latestMessages)) {
nextTick(() => {
emit('scroll')
})
}
} catch (error) {
console.error('刷新最新消息失败:', error)
}
}
// 比较yyyy-MM-dd HH:mm:ss时间大小
function compareTime(time1: string, time2: string) {
if (!time1 && !time2) return 0
@@ -160,14 +220,19 @@ function pauseSSE() {
// 恢复SSE连接
function resumeSSE() {
if (manager) {
// 先移除再重建监听确保恢复时拿到一条新的SSE连接。
manager.removeMessageListener('message-view')
manager.addMessageListener('message-view', handleSSEMessage)
}
refreshLatestMessages()
}
// 暴露方法给父组件
defineExpose({
pauseSSE,
resumeSSE,
refreshLatestMessages,
})
onMounted(() => {
@@ -194,7 +259,7 @@ onMounted(() => {
<div>
<div
v-for="(msg, index) in messages"
:key="index"
:key="getMessageKey(msg) || index"
class="chat-group d-flex mt-5 mb-8"
:class="msg.action == 1 ? 'flex-row align-start' : 'flex-row-reverse align-end'"
>