refactor(core): introduce EventBus and refactor store infrastructure
- Add EventBus for decoupled event-driven communication between services - Add EventSubscriber component for centralized event handling - Add requestDedupe utility for shared request deduplication - Refactor api/wsService to use eventBus instead of direct router navigation - Extract MessageMapper.toCachedMessages for consistent message mapping - Remove deprecated BaseManager and CacheBus classes - Improve @mention rendering with memberMap support in segment rendering - Update hooks to use useRef instead of useState for fetch tracking
This commit is contained in:
@@ -34,6 +34,8 @@ export const LongPressMenu: React.FC<LongPressMenuProps> = ({
|
||||
onRecall,
|
||||
onDelete,
|
||||
onAddSticker,
|
||||
onReport,
|
||||
memberMap,
|
||||
}) => {
|
||||
const styles = useChatScreenStyles();
|
||||
const scaleAnimation = useRef(new Animated.Value(0)).current;
|
||||
@@ -90,7 +92,7 @@ export const LongPressMenu: React.FC<LongPressMenuProps> = ({
|
||||
|
||||
// 复制文本到剪贴板
|
||||
const handleCopy = () => {
|
||||
const text = extractTextFromSegments(message.segments);
|
||||
const text = extractTextFromSegments(message.segments, memberMap);
|
||||
if (text) {
|
||||
Clipboard.setString(text);
|
||||
onClose();
|
||||
@@ -99,7 +101,7 @@ export const LongPressMenu: React.FC<LongPressMenuProps> = ({
|
||||
|
||||
// 多选功能(这里简化为复制全部)
|
||||
const handleMultiSelect = () => {
|
||||
const text = extractTextFromSegments(message.segments);
|
||||
const text = extractTextFromSegments(message.segments, memberMap);
|
||||
if (text) {
|
||||
Clipboard.setString(text);
|
||||
onClose();
|
||||
|
||||
@@ -141,7 +141,7 @@ export const renderSegment = (
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染文本 Segment
|
||||
* 渲染文本 Segment(非内联,块级使用)
|
||||
*/
|
||||
const TextSegmentBody: React.FC<{ data: TextSegmentData; isMe: boolean }> = ({ data, isMe }) => {
|
||||
const styles = useSegmentStyles();
|
||||
@@ -392,7 +392,7 @@ const AtSegmentBody: React.FC<{
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.atText,
|
||||
styles.textContent,
|
||||
isMe ? styles.atTextMe : styles.atTextOther,
|
||||
isAtMe && styles.atHighlight,
|
||||
]}
|
||||
@@ -773,15 +773,47 @@ const MessageSegmentsRendererInner: React.FC<{
|
||||
{chunks.map((chunk, i) => {
|
||||
if (chunk.kind === 'inline') {
|
||||
return (
|
||||
<View key={`inl-${i}`} style={segStyles.inlineChunk}>
|
||||
{chunk.parts.map((segment, j) => (
|
||||
<React.Fragment
|
||||
key={`segment-${segment.type}-${(segment as any)?.data?.url || (segment as any)?.data?.id || (segment as any)?.data?.user_id || j}`}
|
||||
>
|
||||
{renderSegment(segment, renderProps)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</View>
|
||||
<Text key={`inl-${i}`} style={[segStyles.inlineChunkBase, isMe ? segStyles.textMe : segStyles.textOther]} selectable={true}>
|
||||
{chunk.parts.map((segment, j) => {
|
||||
if (segment.type === 'text') {
|
||||
const data = segment.data as TextSegmentData;
|
||||
return data.content ?? data.text ?? '';
|
||||
}
|
||||
if (segment.type === 'at') {
|
||||
const data = segment.data as AtSegmentData;
|
||||
const isAtMe = data.user_id === currentUserId || data.user_id === 'all';
|
||||
let displayName: string;
|
||||
if (data.user_id === 'all') {
|
||||
displayName = '所有人';
|
||||
} else {
|
||||
const member = memberMap?.get(data.user_id);
|
||||
displayName = member?.nickname || data.nickname || '用户';
|
||||
}
|
||||
return (
|
||||
<Text
|
||||
key={`at-${j}`}
|
||||
style={[
|
||||
isMe ? segStyles.atTextMe : segStyles.atTextOther,
|
||||
isAtMe && segStyles.atHighlight,
|
||||
]}
|
||||
onPress={() => data.user_id !== 'all' && onAtPress?.(data.user_id)}
|
||||
>
|
||||
@{displayName}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (segment.type === 'face') {
|
||||
const data = segment.data as FaceSegmentData;
|
||||
if (data.url) {
|
||||
return (
|
||||
<Image key={`face-${j}`} source={{ uri: data.url }} style={segStyles.faceImage} />
|
||||
);
|
||||
}
|
||||
return `[${data.name || `表情${data.id}`}]`;
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (chunk.kind === 'images') {
|
||||
@@ -836,11 +868,11 @@ function createSegmentStyles(colors: AppColors, fontSize: number = 16) {
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
},
|
||||
inlineChunk: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'flex-end',
|
||||
width: '100%',
|
||||
inlineChunkBase: {
|
||||
fontSize,
|
||||
lineHeight: Math.round(fontSize * 1.43),
|
||||
letterSpacing: 0.1,
|
||||
fontWeight: '400',
|
||||
},
|
||||
imagesChunk: {
|
||||
width: '100%',
|
||||
@@ -874,11 +906,7 @@ function createSegmentStyles(colors: AppColors, fontSize: number = 16) {
|
||||
color: colors.chat.textPrimary,
|
||||
},
|
||||
|
||||
// @提及 - 与普通文本字号一致,仅颜色区分
|
||||
atText: {
|
||||
fontSize,
|
||||
lineHeight: Math.round(fontSize * 1.43),
|
||||
},
|
||||
// @提及 - 仅颜色区分,继承父元素字号
|
||||
atTextMe: {
|
||||
color: '#1A5F9E',
|
||||
},
|
||||
|
||||
@@ -166,7 +166,8 @@ export interface LongPressMenuProps {
|
||||
onRecall: (messageId: string) => void;
|
||||
onDelete: (messageId: string) => void;
|
||||
onAddSticker?: (imageUrl: string) => void;
|
||||
onReport?: (message: GroupMessage) => void; // 举报消息回调
|
||||
onReport?: (message: GroupMessage) => void;
|
||||
memberMap?: Map<string, { nickname: string; user_id: string }>;
|
||||
}
|
||||
|
||||
// 聊天头部 Props
|
||||
|
||||
@@ -405,6 +405,17 @@ export const EmbeddedChat: React.FC<EmbeddedChatProps> = ({ conversation, onBack
|
||||
|
||||
// 群成员列表
|
||||
const [groupMembers, setGroupMembers] = useState<GroupMemberResponse[]>([]);
|
||||
|
||||
const embeddedMemberMap = useMemo(() => {
|
||||
const map = new Map<string, { nickname: string; user_id: string }>();
|
||||
groupMembers.forEach(m => {
|
||||
map.set(m.user_id, {
|
||||
nickname: m.nickname || m.user?.nickname || '用户',
|
||||
user_id: m.user_id,
|
||||
});
|
||||
});
|
||||
return map;
|
||||
}, [groupMembers]);
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = useCallback((dateString: string): string => {
|
||||
@@ -1023,6 +1034,7 @@ export const EmbeddedChat: React.FC<EmbeddedChatProps> = ({ conversation, onBack
|
||||
onReply={handleReply}
|
||||
onRecall={handleRecall}
|
||||
onDelete={handleDeleteMessage}
|
||||
memberMap={embeddedMemberMap}
|
||||
/>
|
||||
|
||||
{/* 图片查看器 */}
|
||||
|
||||
Reference in New Issue
Block a user