168 lines
5.1 KiB
Vue
Raw Normal View History

2025-05-30 18:05:17 +08:00
<template>
2025-06-27 09:53:36 +08:00
<div v-if="isScrollButtonVisible" class="scroll-button" @click="scrollToMessageListBottom">
<Icon width="10px" height="10px" :file="doubleArrowIcon" />
2025-05-30 18:05:17 +08:00
<div class="scroll-button-text">
{{ scrollButtonContent }}
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed, watch } from '../../../../adapter-vue';
2025-06-27 09:53:36 +08:00
import { TUIStore, StoreName, IMessageModel, IConversationModel, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
2025-05-30 18:05:17 +08:00
import Icon from '../../../common/Icon.vue';
import doubleArrowIcon from '../../../../assets/icon/double-arrow.svg';
import { getBoundingClientRect } from '@tencentcloud/universal-api';
import { JSONToObject } from '../../../../utils';
interface IEmits {
(key: 'scrollToLatestMessage'): void;
}
const emits = defineEmits<IEmits>();
const messageList = ref<IMessageModel[]>([]);
const currentConversationID = ref<string>('');
const currentLastMessageTime = ref<number>(0);
const newMessageCount = ref<number>(0);
const isScrollOverOneScreen = ref<boolean>(false);
const isExistLastMessage = ref<boolean>(false);
const isScrollButtonVisible = ref<boolean>(false);
const scrollButtonContent = computed(() =>
2025-06-27 09:53:36 +08:00
newMessageCount.value ? `${newMessageCount.value}${TUITranslateService.t('TUIChat.条新消息')}` : TUITranslateService.t('TUIChat.回到最新位置')
2025-05-30 18:05:17 +08:00
);
2025-06-27 09:53:36 +08:00
watch(
() => [isScrollOverOneScreen.value, isExistLastMessage.value],
2025-05-30 18:05:17 +08:00
() => {
isScrollButtonVisible.value = isScrollOverOneScreen.value || isExistLastMessage.value;
if (!isScrollButtonVisible.value) {
resetNewMessageCount();
}
},
2025-06-27 09:53:36 +08:00
{ immediate: true }
2025-05-30 18:05:17 +08:00
);
onMounted(() => {
TUIStore.watch(StoreName.CHAT, {
messageList: onMessageListUpdated,
2025-06-27 09:53:36 +08:00
newMessageList: onNewMessageListUpdated
2025-05-30 18:05:17 +08:00
});
TUIStore.watch(StoreName.CONV, {
2025-06-27 09:53:36 +08:00
currentConversation: onCurrentConversationUpdated
2025-05-30 18:05:17 +08:00
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CHAT, {
messageList: onMessageListUpdated,
2025-06-27 09:53:36 +08:00
newMessageList: onNewMessageListUpdated
2025-05-30 18:05:17 +08:00
});
TUIStore.unwatch(StoreName.CONV, {
2025-06-27 09:53:36 +08:00
currentConversation: onCurrentConversationUpdated
2025-05-30 18:05:17 +08:00
});
});
function isTypingMessage(message: IMessageModel): boolean {
return JSONToObject(message.payload?.data)?.businessID === 'user_typing_status';
}
function onMessageListUpdated(newMessageList: IMessageModel[]) {
messageList.value = newMessageList || [];
const lastMessage = messageList.value?.[messageList.value?.length - 1];
2025-06-27 09:53:36 +08:00
isExistLastMessage.value = !!(lastMessage && lastMessage?.time < currentLastMessageTime?.value);
2025-05-30 18:05:17 +08:00
}
function onNewMessageListUpdated(newMessageList: IMessageModel[]) {
if (Array.isArray(newMessageList) && isScrollButtonVisible.value) {
newMessageList.forEach((message: IMessageModel) => {
2025-06-27 09:53:36 +08:00
if (
message &&
message.conversationID === currentConversationID.value &&
!message.isDeleted &&
!message.isRevoked &&
!isTypingMessage(message)
) {
2025-05-30 18:05:17 +08:00
newMessageCount.value += 1;
}
});
}
}
function onCurrentConversationUpdated(conversation: IConversationModel | undefined) {
if (conversation?.conversationID !== currentConversationID.value) {
resetNewMessageCount();
}
currentConversationID.value = conversation?.conversationID || '';
currentLastMessageTime.value = conversation?.lastMessage?.lastTime || 0;
}
// When the scroll height of the message list upwards is greater than one screen, show scrolling to the latest tips.
async function judgeScrollOverOneScreen(e: Event) {
if (e.target) {
try {
2025-06-27 09:53:36 +08:00
const { height } = (await getBoundingClientRect(`#${(e.target as HTMLElement)?.id}`, 'messageList')) || {};
2025-05-30 18:05:17 +08:00
const scrollHeight = (e.target as HTMLElement)?.scrollHeight || (e.detail as HTMLElement)?.scrollHeight;
const scrollTop = (e.target as HTMLElement)?.scrollTop || (e.detail as HTMLElement)?.scrollTop || 0;
// while scroll over one screen show this scroll button.
if (scrollHeight - scrollTop > 2 * height) {
isScrollOverOneScreen.value = true;
return;
}
isScrollOverOneScreen.value = false;
} catch (error) {
isScrollOverOneScreen.value = false;
}
}
}
// reset messageSource
function resetMessageSource() {
if (TUIStore.getData(StoreName.CHAT, 'messageSource') !== undefined) {
TUIStore.update(StoreName.CHAT, 'messageSource', undefined);
}
}
// reset newMessageCount
function resetNewMessageCount() {
newMessageCount.value = 0;
}
function scrollToMessageListBottom() {
resetMessageSource();
resetNewMessageCount();
emits('scrollToLatestMessage');
}
defineExpose({
judgeScrollOverOneScreen,
2025-06-27 09:53:36 +08:00
isScrollButtonVisible
2025-05-30 18:05:17 +08:00
});
</script>
<style scoped lang="scss">
.scroll-button {
position: absolute;
bottom: 10px;
right: 10px;
width: 92px;
height: 28px;
background: #fff;
border: 1px solid #e0e0e0;
box-shadow: 0 4px 12px -5px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 3px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
&-text {
font-family: PingFangSC-Regular, system-ui;
font-size: 10px;
color: #147aff;
margin-left: 3px;
}
}
</style>