mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
chore: bump version to 2.10.7 and improve message sending reliability with explicit refresh and type safety
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "2.10.5",
|
"version": "2.10.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": "dist/service.js",
|
"bin": "dist/service.js",
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ import { getQueryValue } from '@/@core/utils'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { clearAppBadge } from '@/utils/badge'
|
import { clearAppBadge } from '@/utils/badge'
|
||||||
|
|
||||||
|
type MessageViewExpose = {
|
||||||
|
pauseSSE?: () => void
|
||||||
|
resumeSSE?: () => void
|
||||||
|
refreshLatestMessages?: () => Promise<void> | void
|
||||||
|
}
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -63,7 +69,7 @@ const sendButtonDisabled = ref(false)
|
|||||||
const messageDialogRef = ref<any>(null)
|
const messageDialogRef = ref<any>(null)
|
||||||
|
|
||||||
// 消息视图引用
|
// 消息视图引用
|
||||||
const messageViewRef = ref<any>(null)
|
const messageViewRef = ref<MessageViewExpose | null>(null)
|
||||||
|
|
||||||
// 滚动容器引用
|
// 滚动容器引用
|
||||||
const messageContentRef = ref<any>()
|
const messageContentRef = ref<any>()
|
||||||
@@ -153,9 +159,7 @@ async function openMessageDialog() {
|
|||||||
}, 600)
|
}, 600)
|
||||||
// 等待对话框打开后恢复SSE连接
|
// 等待对话框打开后恢复SSE连接
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (messageViewRef.value && typeof messageViewRef.value.resumeSSE === 'function') {
|
messageViewRef.value?.resumeSSE?.()
|
||||||
messageViewRef.value.resumeSSE()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,16 +207,23 @@ function allLoggingUrl() {
|
|||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
async function sendMessage() {
|
async function sendMessage() {
|
||||||
if (user_message.value) {
|
const messageText = user_message.value.trim()
|
||||||
try {
|
if (!messageText) {
|
||||||
sendButtonDisabled.value = true
|
return
|
||||||
await api.post(`message/web?text=${user_message.value}`)
|
}
|
||||||
user_message.value = ''
|
|
||||||
sendButtonDisabled.value = false
|
try {
|
||||||
forceScrollToEnd() // 发送消息后强制滚动到底部
|
sendButtonDisabled.value = true
|
||||||
} catch (error) {
|
await api.post(`message/web?text=${encodeURIComponent(messageText)}`)
|
||||||
console.error(error)
|
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 => {
|
watch(messageDialog, newValue => {
|
||||||
if (!newValue && messageViewRef.value && typeof messageViewRef.value.pauseSSE === 'function') {
|
if (!newValue && messageViewRef.value?.pauseSSE) {
|
||||||
// 对话框关闭时暂停SSE连接
|
// 对话框关闭时暂停SSE连接
|
||||||
messageViewRef.value.pauseSSE()
|
messageViewRef.value.pauseSSE()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ const messages = ref<Message[]>([])
|
|||||||
// 当前页数据
|
// 当前页数据
|
||||||
const currData = ref<Message[]>([])
|
const currData = ref<Message[]>([])
|
||||||
|
|
||||||
|
// 已加载消息的签名集合
|
||||||
|
// 使用消息内容签名去重,避免仅按秒级时间戳判断时误吞同一秒内的不同消息。
|
||||||
|
const messageKeys = new Set<string>()
|
||||||
|
|
||||||
// 是否完成加载
|
// 是否完成加载
|
||||||
const isLoaded = ref(false)
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
@@ -29,18 +33,72 @@ const page = ref(1)
|
|||||||
// 存量消息最新时间
|
// 存量消息最新时间
|
||||||
const lastTime = ref('')
|
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消息处理函数
|
// SSE消息处理函数
|
||||||
function handleSSEMessage(event: MessageEvent) {
|
function handleSSEMessage(event: MessageEvent) {
|
||||||
const message = event.data
|
const message = event.data
|
||||||
if (message) {
|
if (message) {
|
||||||
const object = JSON.parse(message)
|
const object = JSON.parse(message)
|
||||||
// 使用reg_time或date字段进行比较
|
if (mergeMessages([object])) {
|
||||||
const messageTime = object.reg_time || object.date
|
nextTick(() => {
|
||||||
if (compareTime(messageTime, lastTime.value) <= 0) return
|
emit('scroll') // 新消息到达时触发智能滚动
|
||||||
messages.value.push(object)
|
})
|
||||||
nextTick(() => {
|
}
|
||||||
emit('scroll') // 新消息到达时触发智能滚动
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,28 +133,10 @@ async function loadMessages({ done }: { done: any }) {
|
|||||||
// 已加载过
|
// 已加载过
|
||||||
isLoaded.value = true
|
isLoaded.value = true
|
||||||
if (currData.value.length > 0) {
|
if (currData.value.length > 0) {
|
||||||
// 按时间排序,确保最新的消息在最后
|
const hasNewMessage = mergeMessages(currData.value)
|
||||||
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
|
|
||||||
|
|
||||||
// 首次加载时滚动到底部
|
// 首次加载时滚动到底部
|
||||||
if (page.value === 1) {
|
if (page.value === 1 && hasNewMessage) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
emit('scroll')
|
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时间大小
|
// 比较yyyy-MM-dd HH:mm:ss时间大小
|
||||||
function compareTime(time1: string, time2: string) {
|
function compareTime(time1: string, time2: string) {
|
||||||
if (!time1 && !time2) return 0
|
if (!time1 && !time2) return 0
|
||||||
@@ -160,14 +220,19 @@ function pauseSSE() {
|
|||||||
// 恢复SSE连接
|
// 恢复SSE连接
|
||||||
function resumeSSE() {
|
function resumeSSE() {
|
||||||
if (manager) {
|
if (manager) {
|
||||||
|
// 先移除再重建监听,确保恢复时拿到一条新的SSE连接。
|
||||||
|
manager.removeMessageListener('message-view')
|
||||||
manager.addMessageListener('message-view', handleSSEMessage)
|
manager.addMessageListener('message-view', handleSSEMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshLatestMessages()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
pauseSSE,
|
pauseSSE,
|
||||||
resumeSSE,
|
resumeSSE,
|
||||||
|
refreshLatestMessages,
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -194,7 +259,7 @@ onMounted(() => {
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-for="(msg, index) in messages"
|
v-for="(msg, index) in messages"
|
||||||
:key="index"
|
:key="getMessageKey(msg) || index"
|
||||||
class="chat-group d-flex mt-5 mb-8"
|
class="chat-group d-flex mt-5 mb-8"
|
||||||
:class="msg.action == 1 ? 'flex-row align-start' : 'flex-row-reverse align-end'"
|
:class="msg.action == 1 ? 'flex-row align-start' : 'flex-row-reverse align-end'"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user