fix(room): 新消息自动滚动到底部
This commit is contained in:
@@ -28,7 +28,11 @@
|
|||||||
|
|
||||||
<!-- 右侧消息与输入区域 -->
|
<!-- 右侧消息与输入区域 -->
|
||||||
<section class="flex-1 flex flex-col bg-slate-100/80 min-w-0">
|
<section class="flex-1 flex flex-col bg-slate-100/80 min-w-0">
|
||||||
<div ref="messageListRef" class="flex-1 overflow-auto px-4 sm:px-6 py-4">
|
<div
|
||||||
|
ref="messageListRef"
|
||||||
|
class="flex-1 overflow-auto px-4 sm:px-6 py-4"
|
||||||
|
@scroll="handleMessageListScroll"
|
||||||
|
>
|
||||||
<div class="max-w-2xl mx-auto space-y-4">
|
<div class="max-w-2xl mx-auto space-y-4">
|
||||||
<template v-for="(msg, index) in wsStore.roomMessages" :key="messageKey(msg, index)">
|
<template v-for="(msg, index) in wsStore.roomMessages" :key="messageKey(msg, index)">
|
||||||
<SystemMessage
|
<SystemMessage
|
||||||
@@ -92,7 +96,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch, onMounted } from 'vue';
|
import { computed, ref, watch, onMounted, nextTick } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import BaseButton from '@/components/ui/BaseButton.vue';
|
import BaseButton from '@/components/ui/BaseButton.vue';
|
||||||
import RoomPanel from '@/components/RoomPanel.vue';
|
import RoomPanel from '@/components/RoomPanel.vue';
|
||||||
@@ -119,10 +123,34 @@ const joinToken = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const messageListRef = ref<HTMLElement | null>(null);
|
const messageListRef = ref<HTMLElement | null>(null);
|
||||||
|
const shouldAutoScroll = ref(true);
|
||||||
const wsStore = useWsStore();
|
const wsStore = useWsStore();
|
||||||
const downloadToast = ref('');
|
const downloadToast = ref('');
|
||||||
let downloadToastTimer: ReturnType<typeof setTimeout> | null = null;
|
let downloadToastTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function isNearBottom(element: HTMLElement): boolean {
|
||||||
|
const threshold = 72;
|
||||||
|
return element.scrollHeight - element.scrollTop - element.clientHeight <= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottom(behavior: ScrollBehavior = 'smooth'): void {
|
||||||
|
const container = messageListRef.value;
|
||||||
|
if (!container) return;
|
||||||
|
container.scrollTo({ top: container.scrollHeight, behavior });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessageListScroll(): void {
|
||||||
|
const container = messageListRef.value;
|
||||||
|
if (!container) return;
|
||||||
|
shouldAutoScroll.value = isNearBottom(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLastMessageMine(): boolean {
|
||||||
|
const last = wsStore.roomMessages[wsStore.roomMessages.length - 1];
|
||||||
|
if (!last) return false;
|
||||||
|
return !!last.senderId && last.senderId === wsStore.myUserId;
|
||||||
|
}
|
||||||
|
|
||||||
const transferStats = computed(() => {
|
const transferStats = computed(() => {
|
||||||
let sendingCount = 0;
|
let sendingCount = 0;
|
||||||
let receivingCount = 0;
|
let receivingCount = 0;
|
||||||
@@ -160,8 +188,19 @@ function showDownloadToast(message: string) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
wsStore.init(API_BASE, '/ws');
|
wsStore.init(API_BASE, '/ws');
|
||||||
wsStore.connect();
|
wsStore.connect();
|
||||||
|
void nextTick(() => scrollToBottom('auto'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => wsStore.roomMessages.length,
|
||||||
|
async (nextLen, prevLen) => {
|
||||||
|
if (nextLen <= prevLen) return;
|
||||||
|
if (!shouldAutoScroll.value && !isLastMessageMine()) return;
|
||||||
|
await nextTick();
|
||||||
|
scrollToBottom();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => wsStore.status,
|
() => wsStore.status,
|
||||||
(status) => {
|
(status) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user