重构消息处理逻辑以提高同步和性能,并增强ProfileCard组件以实现快速回复功能。
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import React, { useMemo, useRef, useEffect, useCallback } from "react";
|
||||
import { VariableSizeList as List } from "react-window";
|
||||
import { ChatRecord } from "@/pages/pc/ckbox/data";
|
||||
import { MessageGroup } from "@/hooks/weChat/useMessageGrouping";
|
||||
|
||||
export interface VirtualMessageItem {
|
||||
type: "time" | "system" | "message";
|
||||
id: string | number;
|
||||
data: ChatRecord | string;
|
||||
groupIndex?: number;
|
||||
}
|
||||
|
||||
interface VirtualMessageListProps {
|
||||
groupedMessages: MessageGroup[];
|
||||
containerHeight: number;
|
||||
containerRef?: React.RefObject<HTMLDivElement>;
|
||||
renderItem: (item: VirtualMessageItem, index: number) => React.ReactNode;
|
||||
onScroll?: (scrollTop: number) => void;
|
||||
onScrollToTop?: () => void;
|
||||
estimatedItemSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟滚动消息列表组件
|
||||
* 使用 react-window 的 VariableSizeList 实现高性能消息列表
|
||||
*/
|
||||
export const VirtualMessageList: React.FC<VirtualMessageListProps> = ({
|
||||
groupedMessages,
|
||||
containerHeight,
|
||||
containerRef,
|
||||
renderItem,
|
||||
onScroll,
|
||||
onScrollToTop,
|
||||
estimatedItemSize = 80,
|
||||
}) => {
|
||||
const listRef = useRef<List>(null);
|
||||
const itemSizeCache = useRef<Map<number, number>>(new Map());
|
||||
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||
|
||||
// 将分组消息展平为扁平列表
|
||||
const flatItems = useMemo<VirtualMessageItem[]>(() => {
|
||||
const items: VirtualMessageItem[] = [];
|
||||
|
||||
groupedMessages.forEach((group, groupIndex) => {
|
||||
// 添加时间戳
|
||||
items.push({
|
||||
type: "time",
|
||||
id: `time-${groupIndex}`,
|
||||
data: group.time,
|
||||
groupIndex,
|
||||
});
|
||||
|
||||
// 添加系统消息(时间分隔符)
|
||||
const systemMessages = group.messages.filter(v =>
|
||||
[10000, -10001].includes(v.msgType),
|
||||
);
|
||||
systemMessages.forEach(msg => {
|
||||
items.push({
|
||||
type: "system",
|
||||
id: `system-${msg.id}`,
|
||||
data: msg,
|
||||
groupIndex,
|
||||
});
|
||||
});
|
||||
|
||||
// 添加其他系统消息
|
||||
const otherSystemMessages = group.messages.filter(v =>
|
||||
[570425393, 90000].includes(v.msgType),
|
||||
);
|
||||
otherSystemMessages.forEach(msg => {
|
||||
items.push({
|
||||
type: "system",
|
||||
id: `system-${msg.id}`,
|
||||
data: msg,
|
||||
groupIndex,
|
||||
});
|
||||
});
|
||||
|
||||
// 添加普通消息
|
||||
const normalMessages = group.messages.filter(
|
||||
v => ![10000, 570425393, 90000, -10001].includes(v.msgType),
|
||||
);
|
||||
normalMessages.forEach(msg => {
|
||||
items.push({
|
||||
type: "message",
|
||||
id: msg.id,
|
||||
data: msg,
|
||||
groupIndex,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [groupedMessages]);
|
||||
|
||||
// 估算项目高度
|
||||
const getItemSize = useCallback(
|
||||
(index: number): number => {
|
||||
// 如果缓存中有,直接返回
|
||||
if (itemSizeCache.current.has(index)) {
|
||||
return itemSizeCache.current.get(index)!;
|
||||
}
|
||||
|
||||
const item = flatItems[index];
|
||||
if (!item) return estimatedItemSize;
|
||||
|
||||
// 根据类型返回估算高度
|
||||
switch (item.type) {
|
||||
case "time":
|
||||
return 40; // 时间戳高度
|
||||
case "system":
|
||||
return 30; // 系统消息高度
|
||||
case "message":
|
||||
// 根据消息类型估算
|
||||
const msg = item.data as ChatRecord;
|
||||
if (msg.msgType === 3) {
|
||||
// 图片消息
|
||||
return 250;
|
||||
} else if (msg.msgType === 43) {
|
||||
// 视频消息
|
||||
return 250;
|
||||
} else if (msg.msgType === 49) {
|
||||
// 小程序消息
|
||||
return 120;
|
||||
} else {
|
||||
// 文本消息,根据内容长度估算
|
||||
const content = String(msg.content || "");
|
||||
const lines = Math.ceil(content.length / 30);
|
||||
return Math.max(60, lines * 24 + 40);
|
||||
}
|
||||
default:
|
||||
return estimatedItemSize;
|
||||
}
|
||||
},
|
||||
[flatItems, estimatedItemSize],
|
||||
);
|
||||
|
||||
// 测量实际项目高度并更新缓存
|
||||
const measureItem = useCallback(
|
||||
(index: number, element: HTMLDivElement | null) => {
|
||||
if (!element) return;
|
||||
|
||||
itemRefs.current.set(index, element);
|
||||
|
||||
// 使用 ResizeObserver 或直接测量
|
||||
const height = element.getBoundingClientRect().height;
|
||||
if (height > 0 && height !== itemSizeCache.current.get(index)) {
|
||||
itemSizeCache.current.set(index, height);
|
||||
// 通知列表更新该索引的高度
|
||||
listRef.current?.resetAfterIndex(index, false);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = useCallback(
|
||||
({ scrollOffset }: { scrollOffset: number }) => {
|
||||
onScroll?.(scrollOffset);
|
||||
// 检测是否滚动到顶部
|
||||
if (scrollOffset < 50 && onScrollToTop) {
|
||||
onScrollToTop();
|
||||
}
|
||||
},
|
||||
[onScroll, onScrollToTop],
|
||||
);
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = useCallback(() => {
|
||||
if (listRef.current && flatItems.length > 0) {
|
||||
listRef.current.scrollToItem(flatItems.length - 1, "end");
|
||||
}
|
||||
}, [flatItems.length]);
|
||||
|
||||
// 暴露滚动到底部方法
|
||||
useEffect(() => {
|
||||
if (containerRef?.current) {
|
||||
(containerRef.current as any).scrollToBottom = scrollToBottom;
|
||||
}
|
||||
}, [containerRef, scrollToBottom]);
|
||||
|
||||
// 渲染列表项
|
||||
const Row = useCallback(
|
||||
({ index, style }: { index: number; style: React.CSSProperties }) => {
|
||||
const item = flatItems[index];
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
ref={el => measureItem(index, el)}
|
||||
data-index={index}
|
||||
>
|
||||
{renderItem(item, index)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[flatItems, renderItem, measureItem],
|
||||
);
|
||||
|
||||
if (flatItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={listRef}
|
||||
height={containerHeight}
|
||||
itemCount={flatItems.length}
|
||||
itemSize={getItemSize}
|
||||
width="100%"
|
||||
onScroll={handleScroll}
|
||||
overscanCount={5} // 预渲染5个项目
|
||||
style={{ overflowX: "hidden" }}
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default VirtualMessageList;
|
||||
|
||||
Reference in New Issue
Block a user