From fa92f94508fe197debaa1ddb48d769d42ab44928 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Thu, 28 Oct 2021 18:11:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=91=E4=B8=8A=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=8E=86=E5=8F=B2=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/redux/hooks/useConverseMessage.ts | 49 +++++++++++++++++-- shared/redux/slices/chat.ts | 40 +++++++++++++++ .../ChatMessageList/VirtualizedList.tsx | 33 +++++++++++-- .../ChatBox/ChatMessageList/index.tsx | 5 +- web/src/components/ChatBox/index.tsx | 13 ++++- 5 files changed, 127 insertions(+), 13 deletions(-) diff --git a/shared/redux/hooks/useConverseMessage.ts b/shared/redux/hooks/useConverseMessage.ts index aceb9e44..4f7ac67b 100644 --- a/shared/redux/hooks/useConverseMessage.ts +++ b/shared/redux/hooks/useConverseMessage.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { ensureDMConverse } from '../../helper/converse-helper'; import { useAsync } from '../../hooks/useAsync'; import { showErrorToasts } from '../../manager/ui'; @@ -9,8 +9,15 @@ import { } from '../../model/message'; import { chatActions } from '../slices'; import { useAppDispatch, useAppSelector } from './useAppSelector'; +import _get from 'lodash/get'; import _isNil from 'lodash/isNil'; -import { t, useChatBoxContext } from '../..'; +import { + ChatConverseState, + isValidStr, + t, + useAsyncRequest, + useChatBoxContext, +} from '../..'; import { MessageHelper } from '../../utils/message-helper'; import { ChatConverseType } from '../../model/converse'; @@ -71,7 +78,10 @@ interface ConverseContext { } export function useConverseMessage(context: ConverseContext) { const { converseId, isGroup } = context; - const converse = useAppSelector((state) => state.chat.converses[converseId]); + const converse = useAppSelector( + (state) => state.chat.converses[converseId] + ); + const hasMoreMessage = converse?.hasMoreMessage; const dispatch = useAppDispatch(); const messages = converse?.messages ?? []; @@ -132,7 +142,38 @@ export function useConverseMessage(context: ConverseContext) { } }, [converseId]); + // 加载更多消息 + const [{ loading: isLoadingMore }, handleFetchMoreMessage] = + useAsyncRequest(async () => { + const firstMessageId = _get(messages, [0, '_id']); + if (!isValidStr(firstMessageId)) { + return; + } + + if (hasMoreMessage === false) { + return; + } + + const olderMessages = await fetchConverseMessage( + converseId, + firstMessageId + ); + dispatch( + chatActions.appendHistoryMessage({ + converseId, + historyMessages: olderMessages, + }) + ); + }, [converseId, hasMoreMessage, _get(messages, [0, '_id'])]); + const handleSendMessage = useHandleSendMessage(context); - return { messages, loading, error, handleSendMessage }; + return { + messages, + loading, + error, + isLoadingMore, + handleFetchMoreMessage, + handleSendMessage, + }; } diff --git a/shared/redux/slices/chat.ts b/shared/redux/slices/chat.ts index bf972cef..3527fdc6 100644 --- a/shared/redux/slices/chat.ts +++ b/shared/redux/slices/chat.ts @@ -9,6 +9,10 @@ import { isValidStr } from '../../utils/string-helper'; export interface ChatConverseState extends ChatConverseInfo { messages: ChatMessage[]; hasFetchedHistory: boolean; + /** + * 判定是否还有更多的信息 + */ + hasMoreMessage: boolean; } export interface ChatState { @@ -42,12 +46,14 @@ const chatSlice = createSlice({ state.converses[converseId] = { messages: [], hasFetchedHistory: false, + hasMoreMessage: true, ...action.payload, }; }, /** * 追加消息 + * 会根据id进行一次排序以确保顺序 */ appendConverseMessage( state, @@ -80,6 +86,9 @@ const chatSlice = createSlice({ } }, + /** + * 初始化历史信息 + */ initialHistoryMessage( state, action: PayloadAction<{ @@ -105,6 +114,37 @@ const chatSlice = createSlice({ state.converses[converseId].hasFetchedHistory = true; }, + /** + * 追加历史信息 + */ + appendHistoryMessage( + state, + action: PayloadAction<{ + converseId: string; + historyMessages: ChatMessage[]; + }> + ) { + const { converseId, historyMessages } = action.payload; + if (!state.converses[converseId]) { + // 没有会话信息, 请先设置会话信息 + console.error('没有会话信息, 请先设置会话信息'); + return; + } + + chatSlice.caseReducers.appendConverseMessage( + state, + chatSlice.actions.appendConverseMessage({ + converseId, + messages: [...historyMessages], + }) + ); + + if (historyMessages.length === 0) { + state.converses[converseId].hasMoreMessage = false; + } + state.converses[converseId].hasFetchedHistory = true; + }, + /** * 设置已读消息 */ diff --git a/web/src/components/ChatBox/ChatMessageList/VirtualizedList.tsx b/web/src/components/ChatBox/ChatMessageList/VirtualizedList.tsx index d1b27da3..0652835f 100644 --- a/web/src/components/ChatBox/ChatMessageList/VirtualizedList.tsx +++ b/web/src/components/ChatBox/ChatMessageList/VirtualizedList.tsx @@ -16,6 +16,7 @@ import { ChatMessageItem } from './Item'; const OVERSCAN_COUNT_BACKWARD = 80; const OVERSCAN_COUNT_FORWARD = 80; +const HEIGHT_TRIGGER_FOR_MORE_POSTS = 200; // 触发加载更多的方法 const postListStyle = { padding: '14px 0px 7px', @@ -38,7 +39,9 @@ function findMessageIndexWithId( export interface VirtualizedMessageListProps { messages: ChatMessage[]; + isLoadingMore: boolean; onUpdateReadedMessage: (lastMessageId: string) => void; + onLoadMore: () => void; } export const VirtualizedMessageList: React.FC = React.memo((props) => { @@ -46,8 +49,32 @@ export const VirtualizedMessageList: React.FC = const postListRef = useRef(null); const [isBottom, setIsBottom] = useState(true); - const onScroll = (info: OnScrollInfo) => { - if (info.clientHeight + info.scrollOffset === info.scrollHeight) { + const handleScroll = (info: OnScrollInfo) => { + const { + clientHeight, + scrollOffset, + scrollHeight, + scrollDirection, + scrollUpdateWasRequested, + } = info; + if (scrollHeight <= 0) { + return; + } + + const didUserScrollBackwards = + scrollDirection === 'backward' && !scrollUpdateWasRequested; + const isOffsetWithInRange = scrollOffset < HEIGHT_TRIGGER_FOR_MORE_POSTS; + + if ( + didUserScrollBackwards && + isOffsetWithInRange && + !props.isLoadingMore + ) { + // 加载更多历史信息 + props.onLoadMore(); + } + + if (clientHeight + scrollOffset === scrollHeight) { // 当前滚动条位于底部 setIsBottom(true); props.onUpdateReadedMessage( @@ -140,7 +167,7 @@ export const VirtualizedMessageList: React.FC = itemData={props.messages.map((m) => m._id).reverse()} overscanCountForward={OVERSCAN_COUNT_FORWARD} overscanCountBackward={OVERSCAN_COUNT_BACKWARD} - onScroll={onScroll} + onScroll={handleScroll} initScrollToIndex={initScrollToIndex} canLoadMorePosts={() => {}} innerRef={postListRef} diff --git a/web/src/components/ChatBox/ChatMessageList/index.tsx b/web/src/components/ChatBox/ChatMessageList/index.tsx index 8158fc62..0a39e868 100644 --- a/web/src/components/ChatBox/ChatMessageList/index.tsx +++ b/web/src/components/ChatBox/ChatMessageList/index.tsx @@ -8,10 +8,7 @@ export const ChatMessageList: React.FC = React.memo((props) => { return (
- +
); }); diff --git a/web/src/components/ChatBox/index.tsx b/web/src/components/ChatBox/index.tsx index b377a866..a9b6c82c 100644 --- a/web/src/components/ChatBox/index.tsx +++ b/web/src/components/ChatBox/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React from 'react'; import { ChatBoxContextProvider, useConverseMessage } from 'tailchat-shared'; import { AlertErrorView } from '../AlertErrorView'; import { ChatBoxPlaceholder } from './ChatBoxPlaceholder'; @@ -20,7 +20,14 @@ type ChatBoxProps = }; const ChatBoxInner: React.FC = React.memo((props) => { const { converseId, isGroup } = props; - const { messages, loading, error, handleSendMessage } = useConverseMessage({ + const { + messages, + loading, + error, + isLoadingMore, + handleFetchMoreMessage, + handleSendMessage, + } = useConverseMessage({ converseId, isGroup, }); @@ -38,7 +45,9 @@ const ChatBoxInner: React.FC = React.memo((props) => {