mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-08 01:06:41 +08:00
修复消息中心滚动显示
This commit is contained in:
@@ -78,7 +78,6 @@ watch(visible, async newValue => {
|
|||||||
if (newValue) {
|
if (newValue) {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
messageViewRef.value?.resumeSSE?.()
|
messageViewRef.value?.resumeSSE?.()
|
||||||
messageViewRef.value?.forceScrollToEnd?.()
|
|
||||||
|
|
||||||
clearUnreadMessageState()
|
clearUnreadMessageState()
|
||||||
|
|
||||||
@@ -90,8 +89,6 @@ watch(visible, async newValue => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
messageViewRef.value?.resumeSSE?.()
|
|
||||||
messageViewRef.value?.forceScrollToEnd?.()
|
|
||||||
|
|
||||||
clearUnreadMessageState()
|
clearUnreadMessageState()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const MESSAGE_AUTO_SCROLL_THRESHOLD = 64
|
|||||||
|
|
||||||
let scrollTimer: number | undefined
|
let scrollTimer: number | undefined
|
||||||
let scrollReleaseTimer: number | undefined
|
let scrollReleaseTimer: number | undefined
|
||||||
|
let boundScrollContainer: HTMLElement | null = null
|
||||||
|
|
||||||
// 生成消息去重签名
|
// 生成消息去重签名
|
||||||
// SSE 消息只有 date 没有 reg_time,数据库消息只有 reg_time 没有 date;
|
// SSE 消息只有 date 没有 reg_time,数据库消息只有 reg_time 没有 date;
|
||||||
@@ -83,10 +84,34 @@ function updateLastTime(message: Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScrollContainer() {
|
/** 判断元素自身是否是真正承载滚动的位置。 */
|
||||||
const container = messageListRef.value?.$el ?? messageListRef.value
|
function isScrollableElement(element: HTMLElement) {
|
||||||
|
const { overflowY } = window.getComputedStyle(element)
|
||||||
|
const canScroll = overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay'
|
||||||
|
|
||||||
return container instanceof HTMLElement ? container : null
|
return canScroll && element.scrollHeight > element.clientHeight + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取消息列表所在的真实滚动容器。 */
|
||||||
|
function getScrollContainer() {
|
||||||
|
const element = messageListRef.value?.$el ?? messageListRef.value
|
||||||
|
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let container: HTMLElement | null = element
|
||||||
|
while (container) {
|
||||||
|
if (isScrollableElement(container)) {
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
container = container.parentElement
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogCardText = element.closest('.v-card-text')
|
||||||
|
|
||||||
|
return dialogCardText instanceof HTMLElement ? dialogCardText : element
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNearBottom(container: HTMLElement) {
|
function isNearBottom(container: HTMLElement) {
|
||||||
@@ -114,23 +139,31 @@ function bindScrollListener() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boundScrollContainer && boundScrollContainer !== container) {
|
||||||
|
boundScrollContainer.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
|
|
||||||
container.removeEventListener('scroll', handleScroll)
|
container.removeEventListener('scroll', handleScroll)
|
||||||
container.addEventListener('scroll', handleScroll, { passive: true })
|
container.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
boundScrollContainer = container
|
||||||
updateAutoScrollState()
|
updateAutoScrollState()
|
||||||
}
|
}
|
||||||
|
|
||||||
function unbindScrollListener() {
|
function unbindScrollListener() {
|
||||||
getScrollContainer()?.removeEventListener('scroll', handleScroll)
|
boundScrollContainer?.removeEventListener('scroll', handleScroll)
|
||||||
|
boundScrollContainer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollContainerToEnd() {
|
/** 滚动到底部,并在布局稳定前连续几帧校正滚动位置。 */
|
||||||
|
function scrollContainerToEnd(retryCount = 1) {
|
||||||
const container = getScrollContainer()
|
const container = getScrollContainer()
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindScrollListener()
|
||||||
isSyncingScroll.value = true
|
isSyncingScroll.value = true
|
||||||
container.scrollTop = container.scrollHeight
|
container.scrollTop = Math.max(0, container.scrollHeight - container.clientHeight)
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const latestContainer = getScrollContainer()
|
const latestContainer = getScrollContainer()
|
||||||
@@ -139,9 +172,14 @@ function scrollContainerToEnd() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
latestContainer.scrollTop = latestContainer.scrollHeight
|
latestContainer.scrollTop = Math.max(0, latestContainer.scrollHeight - latestContainer.clientHeight)
|
||||||
shouldAutoScroll.value = true
|
shouldAutoScroll.value = true
|
||||||
|
|
||||||
|
if (retryCount > 0) {
|
||||||
|
scrollContainerToEnd(retryCount - 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (scrollReleaseTimer) {
|
if (scrollReleaseTimer) {
|
||||||
window.clearTimeout(scrollReleaseTimer)
|
window.clearTimeout(scrollReleaseTimer)
|
||||||
}
|
}
|
||||||
@@ -165,7 +203,7 @@ function requestScrollToEnd(force = false) {
|
|||||||
scrollTimer = window.setTimeout(() => {
|
scrollTimer = window.setTimeout(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
scrollContainerToEnd()
|
scrollContainerToEnd(force ? 6 : 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, force ? 0 : 80)
|
}, force ? 0 : 80)
|
||||||
@@ -231,6 +269,8 @@ async function loadMessages({ done }: { done: any }) {
|
|||||||
try {
|
try {
|
||||||
// 设置加载中
|
// 设置加载中
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
const isFirstPage = page.value === 1
|
||||||
|
|
||||||
currData.value = await api.get('message/web', {
|
currData.value = await api.get('message/web', {
|
||||||
params: {
|
params: {
|
||||||
page: page.value,
|
page: page.value,
|
||||||
@@ -240,16 +280,17 @@ 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)
|
mergeMessages(currData.value)
|
||||||
|
|
||||||
// 首次加载时滚动到底部
|
|
||||||
if (page.value === 1 && hasNewMessage) {
|
|
||||||
requestScrollToEnd(true)
|
|
||||||
}
|
|
||||||
// 页码+1
|
// 页码+1
|
||||||
page.value++
|
page.value++
|
||||||
// 完成
|
// 完成
|
||||||
done('ok')
|
done('ok')
|
||||||
|
|
||||||
|
// 首次加载完成后再滚动,避免列表尚未完成布局时滚动失效。
|
||||||
|
if (isFirstPage) {
|
||||||
|
requestScrollToEnd(true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有新数据
|
// 没有新数据
|
||||||
done('empty')
|
done('empty')
|
||||||
@@ -341,7 +382,6 @@ defineExpose({
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
bindScrollListener()
|
bindScrollListener()
|
||||||
requestScrollToEnd(true)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -372,10 +412,8 @@ onBeforeUnmount(() => {
|
|||||||
<LoadingBanner />
|
<LoadingBanner />
|
||||||
</template>
|
</template>
|
||||||
<template #empty> {{ t('message.noMoreData') }} </template>
|
<template #empty> {{ t('message.noMoreData') }} </template>
|
||||||
<VVirtualScroll renderless :items="messages" :item-height="160">
|
|
||||||
<template #default="{ item, index, itemRef }">
|
|
||||||
<div
|
<div
|
||||||
:ref="itemRef"
|
v-for="(item, index) in messages"
|
||||||
:key="getMessageKey(item) || index"
|
:key="getMessageKey(item) || index"
|
||||||
class="chat-group d-flex mt-5 mb-8"
|
class="chat-group d-flex mt-5 mb-8"
|
||||||
:class="item.action == 1 ? 'flex-row align-start' : 'flex-row-reverse align-end'"
|
:class="item.action == 1 ? 'flex-row align-start' : 'flex-row-reverse align-end'"
|
||||||
@@ -384,7 +422,5 @@ onBeforeUnmount(() => {
|
|||||||
<MessageCard :message="item" @imageload="handleImageLoad" />
|
<MessageCard :message="item" @imageload="handleImageLoad" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</VVirtualScroll>
|
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user