From 6b91a7ead1a972b60a36f6508c1c0366790d5a75 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Sat, 25 Apr 2026 15:25:42 +0800 Subject: [PATCH] fix(message): add robust NaN validation for date handling across components Add comprehensive date validation using Number.isNaN() checks to prevent crashes when timestamps are invalid or empty. Apply defensive formatting across CommentItem, SystemMessageItem, MessageBubble, LongPressMenu, and store utilities. Refine message bubble styling: move sender name display to both sides in group chat with distinct `mySenderName` styling, adjust bubble corner radius to top-right for consistent arrow placement, and align message rows to flex-start for improved layout. Simplify group avatar rendering in ConversationListRow. --- src/components/business/CommentItem.tsx | 5 ++++- src/components/business/SystemMessageItem.tsx | 5 ++++- src/components/business/UserProfileHeader.tsx | 7 +++++-- src/screens/message/GroupInfoScreen.tsx | 5 ++++- src/screens/message/MessageListScreen.tsx | 7 ++++++- .../components/ChatScreen/GroupInfoPanel.tsx | 10 ++++++++-- .../ChatScreen/GroupInfoPanel.web.tsx | 10 ++++++++-- .../components/ChatScreen/LongPressMenu.tsx | 4 +++- .../components/ChatScreen/MessageBubble.tsx | 6 +++--- .../message/components/ChatScreen/styles.ts | 14 +++++++++----- .../components/ChatScreen/useChatScreen.ts | 9 ++++++--- .../message/components/ConversationListRow.tsx | 17 ++--------------- src/stores/message/services/WSMessageHandler.ts | 8 +++++--- src/stores/message/store.ts | 1 + 14 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/components/business/CommentItem.tsx b/src/components/business/CommentItem.tsx index c0b4c45..d5f781f 100644 --- a/src/components/business/CommentItem.tsx +++ b/src/components/business/CommentItem.tsx @@ -223,8 +223,11 @@ const CommentItem: React.FC = ({ const [isDeleting, setIsDeleting] = useState(false); // 格式化时间 const formatTime = (dateString: string): string => { + if (!dateString) return ''; try { - return formatDistanceToNow(new Date(dateString), { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) return ''; + return formatDistanceToNow(date, { addSuffix: true, locale: zhCN, }); diff --git a/src/components/business/SystemMessageItem.tsx b/src/components/business/SystemMessageItem.tsx index 094d4af..87a9fbb 100644 --- a/src/components/business/SystemMessageItem.tsx +++ b/src/components/business/SystemMessageItem.tsx @@ -331,8 +331,11 @@ const SystemMessageItem: React.FC = ({ // 格式化时间 const formatTime = (dateString: string): string => { + if (!dateString) return ''; try { - return formatDistanceToNow(new Date(dateString), { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) return ''; + return formatDistanceToNow(date, { addSuffix: true, locale: zhCN, }); diff --git a/src/components/business/UserProfileHeader.tsx b/src/components/business/UserProfileHeader.tsx index 0b4e81b..f68ecc7 100644 --- a/src/components/business/UserProfileHeader.tsx +++ b/src/components/business/UserProfileHeader.tsx @@ -274,8 +274,11 @@ const UserProfileHeader: React.FC = ({ - 加入于 {new Date(user.created_at || Date.now()).getFullYear()}年 - {new Date(user.created_at || Date.now()).getMonth() + 1}月 + {(() => { + const d = new Date(user.created_at || Date.now()); + if (Number.isNaN(d.getTime())) return '加入于 未知时间'; + return `加入于 ${d.getFullYear()}年${d.getMonth() + 1}月`; + })()} diff --git a/src/screens/message/GroupInfoScreen.tsx b/src/screens/message/GroupInfoScreen.tsx index 54fa78d..2321602 100644 --- a/src/screens/message/GroupInfoScreen.tsx +++ b/src/screens/message/GroupInfoScreen.tsx @@ -661,7 +661,10 @@ const GroupInfoScreen: React.FC = () => { {announcements[0].content} - {new Date(announcements[0].created_at).toLocaleDateString()} + {(() => { + const d = new Date(announcements[0].created_at); + return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString(); + })()} diff --git a/src/screens/message/MessageListScreen.tsx b/src/screens/message/MessageListScreen.tsx index b7d5eab..344c0ab 100644 --- a/src/screens/message/MessageListScreen.tsx +++ b/src/screens/message/MessageListScreen.tsx @@ -245,8 +245,10 @@ export const MessageListScreen: React.FC = () => { // 格式化时间(稳定引用,配合会话行 memo) const formatTime = useCallback((dateString: string): string => { + if (!dateString) return ''; try { const date = new Date(dateString); + if (Number.isNaN(date.getTime())) return ''; const now = new Date(); const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60); @@ -483,7 +485,10 @@ export const MessageListScreen: React.FC = () => { const aPinned = a.is_pinned ? 1 : 0; const bPinned = b.is_pinned ? 1 : 0; if (aPinned !== bPinned) return bPinned - aPinned; - return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(); + const aTime = new Date(a.updated_at || 0).getTime(); + const bTime = new Date(b.updated_at || 0).getTime(); + if (Number.isNaN(aTime) || Number.isNaN(bTime)) return 0; + return bTime - aTime; }); const listConvRef = useRef(new Map()); diff --git a/src/screens/message/components/ChatScreen/GroupInfoPanel.tsx b/src/screens/message/components/ChatScreen/GroupInfoPanel.tsx index 52d3c8f..8c6f42c 100644 --- a/src/screens/message/components/ChatScreen/GroupInfoPanel.tsx +++ b/src/screens/message/components/ChatScreen/GroupInfoPanel.tsx @@ -241,7 +241,10 @@ export const GroupInfoPanel: React.FC = ({ {announcements[0].content} - {new Date(announcements[0].created_at).toLocaleDateString()} + {(() => { + const d = new Date(announcements[0].created_at); + return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString(); + })()} @@ -311,7 +314,10 @@ export const GroupInfoPanel: React.FC = ({ 创建时间 {groupInfo?.created_at - ? new Date(groupInfo.created_at).toLocaleDateString('zh-CN') + ? (() => { + const d = new Date(groupInfo.created_at); + return Number.isNaN(d.getTime()) ? '-' : d.toLocaleDateString('zh-CN'); + })() : '-'} diff --git a/src/screens/message/components/ChatScreen/GroupInfoPanel.web.tsx b/src/screens/message/components/ChatScreen/GroupInfoPanel.web.tsx index 939f995..ee30172 100644 --- a/src/screens/message/components/ChatScreen/GroupInfoPanel.web.tsx +++ b/src/screens/message/components/ChatScreen/GroupInfoPanel.web.tsx @@ -246,7 +246,10 @@ export const GroupInfoPanel: React.FC = ({ {announcements[0].content} - {new Date(announcements[0].created_at).toLocaleDateString()} + {(() => { + const d = new Date(announcements[0].created_at); + return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString(); + })()} @@ -316,7 +319,10 @@ export const GroupInfoPanel: React.FC = ({ 创建时间 {groupInfo?.created_at - ? new Date(groupInfo.created_at).toLocaleDateString('zh-CN') + ? (() => { + const d = new Date(groupInfo.created_at); + return Number.isNaN(d.getTime()) ? '-' : d.toLocaleDateString('zh-CN'); + })() : '-'} diff --git a/src/screens/message/components/ChatScreen/LongPressMenu.tsx b/src/screens/message/components/ChatScreen/LongPressMenu.tsx index eef96b5..bbe7ace 100644 --- a/src/screens/message/components/ChatScreen/LongPressMenu.tsx +++ b/src/screens/message/components/ChatScreen/LongPressMenu.tsx @@ -78,10 +78,12 @@ export const LongPressMenu: React.FC = ({ if (!message) return null; const isMyMessage = message.sender_id === currentUserId; - const messageTime = new Date(message.created_at).getTime(); + const messageDate = new Date(message.created_at); + const messageTime = Number.isNaN(messageDate.getTime()) ? 0 : messageDate.getTime(); const now = Date.now(); const canRecall = isMyMessage && message.status !== 'recalled' && + messageTime > 0 && (now - messageTime) < RECALL_TIME_LIMIT; // 检查是否是图片消息 diff --git a/src/screens/message/components/ChatScreen/MessageBubble.tsx b/src/screens/message/components/ChatScreen/MessageBubble.tsx index a9aaefc..2ec28da 100644 --- a/src/screens/message/components/ChatScreen/MessageBubble.tsx +++ b/src/screens/message/components/ChatScreen/MessageBubble.tsx @@ -426,10 +426,10 @@ const MessageBubbleInner: React.FC = ({ {/* 群聊模式:显示发送者昵称 */} - {isGroupChat && !isMe && senderInfo && ( - {senderInfo.nickname} + {isGroupChat && senderInfo && ( + {senderInfo.nickname} )} - + {renderMessageContent()} {/* 私聊模式:显示已读标记 */} diff --git a/src/screens/message/components/ChatScreen/styles.ts b/src/screens/message/components/ChatScreen/styles.ts index 88b0bb0..f0b3249 100644 --- a/src/screens/message/components/ChatScreen/styles.ts +++ b/src/screens/message/components/ChatScreen/styles.ts @@ -262,14 +262,13 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: { messageRow: { flexDirection: 'row', marginBottom: spacing.md, - alignItems: 'flex-end', + alignItems: 'flex-start', }, myMessageRow: { justifyContent: 'flex-end', }, theirMessageRow: { justifyContent: 'flex-start', - alignItems: 'flex-start', }, // 头像 - 更大一点 @@ -303,6 +302,11 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: { marginLeft: 4, fontWeight: '600', }, + mySenderName: { + marginLeft: 0, + marginRight: 4, + alignSelf: 'flex-end', + }, // 消息气泡 - 动态圆角支持 messageBubbleOuter: { @@ -315,7 +319,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: { elevation: 1, }, myBubbleOuter: { - borderBottomRightRadius: Math.max(4, messageRadius * 0.3), // 微信的小尾巴更尖 + borderTopRightRadius: Math.max(4, messageRadius * 0.3), // 箭头在右上 }, theirBubbleOuter: { borderTopLeftRadius: Math.max(4, messageRadius * 0.3), @@ -330,7 +334,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: { }, myBubbleInner: { backgroundColor: myBubbleBg, - borderBottomRightRadius: Math.max(4, messageRadius * 0.3), + borderTopRightRadius: Math.max(4, messageRadius * 0.3), }, theirBubbleInner: { backgroundColor: theirBubbleBg, @@ -394,7 +398,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: { elevation: 4, }, myImageBubble: { - borderBottomRightRadius: 8, + borderTopRightRadius: 8, }, theirImageBubble: { borderTopLeftRadius: 8, diff --git a/src/screens/message/components/ChatScreen/useChatScreen.ts b/src/screens/message/components/ChatScreen/useChatScreen.ts index 9522eeb..0031600 100644 --- a/src/screens/message/components/ChatScreen/useChatScreen.ts +++ b/src/screens/message/components/ChatScreen/useChatScreen.ts @@ -657,8 +657,10 @@ export const useChatScreen = (props?: ChatScreenProps) => { // 格式化时间 const formatTime = useCallback((dateString: string): string => { + if (!dateString) return ''; try { const date = new Date(dateString); + if (Number.isNaN(date.getTime())) return ''; const now = new Date(); const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60); @@ -680,9 +682,10 @@ export const useChatScreen = (props?: ChatScreenProps) => { if (index === 0) return true; const currentMsg = messages[index]; const prevMsg = messages[index - 1]; - const currentTime = new Date(currentMsg.created_at).getTime(); - const prevTime = new Date(prevMsg.created_at).getTime(); - return (currentTime - prevTime) > TIME_SEPARATOR_INTERVAL; + const currentDate = new Date(currentMsg.created_at); + const prevDate = new Date(prevMsg.created_at); + if (Number.isNaN(currentDate.getTime()) || Number.isNaN(prevDate.getTime())) return false; + return (currentDate.getTime() - prevDate.getTime()) > TIME_SEPARATOR_INTERVAL; }, [messages]); // 处理输入变化,检测@符号 diff --git a/src/screens/message/components/ConversationListRow.tsx b/src/screens/message/components/ConversationListRow.tsx index 5ce9ebb..a83a4af 100644 --- a/src/screens/message/components/ConversationListRow.tsx +++ b/src/screens/message/components/ConversationListRow.tsx @@ -223,14 +223,7 @@ function createConversationRowStyles(colors: AppColors) { width: 56, height: 56, }, - groupAvatarPlaceholder: { - width: 56, - height: 56, - borderRadius: borderRadius.md, - backgroundColor: colors.primary.main, - alignItems: 'center', - justifyContent: 'center', - }, + timeText: { color: colors.text.hint, fontSize: 12, @@ -324,13 +317,7 @@ const ConversationListRowInner: React.FC = ({ ) : isGroupChat ? ( - {displayAvatar ? ( - - ) : ( - - - - )} + ) : ( diff --git a/src/stores/message/services/WSMessageHandler.ts b/src/stores/message/services/WSMessageHandler.ts index c1ce9dd..fa4260a 100644 --- a/src/stores/message/services/WSMessageHandler.ts +++ b/src/stores/message/services/WSMessageHandler.ts @@ -137,7 +137,9 @@ export class WSMessageHandler implements IWSMessageHandler { if (message.created_at) { const store = useMessageStore.getState(); const currentLast = store.lastSystemMessageAt; - if (!currentLast || new Date(message.created_at) > new Date(currentLast)) { + const msgDate = new Date(message.created_at); + if (Number.isNaN(msgDate.getTime())) return; + if (!currentLast || msgDate > new Date(currentLast)) { store.setLastSystemMessageAt(message.created_at); } } @@ -335,7 +337,7 @@ export class WSMessageHandler implements IWSMessageHandler { content: textContent, type: segments?.find((s: any) => s.type === 'image') ? 'image' : 'text', isRead: isActiveConversation, - createdAt: new Date(created_at).toISOString(), + createdAt: (() => { const d = new Date(created_at); return Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString(); })(), seq, status: 'normal', segments, @@ -451,7 +453,7 @@ export class WSMessageHandler implements IWSMessageHandler { : [], status: 'normal', category: 'notification', - created_at: new Date(timestamp || Date.now()).toISOString(), + created_at: (() => { const d = new Date(timestamp || Date.now()); return Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString(); })(), }; const updatedMessages = [...existingMessages, systemNoticeMessage].sort((a, b) => a.seq - b.seq); diff --git a/src/stores/message/store.ts b/src/stores/message/store.ts index abba7e0..b7ff030 100644 --- a/src/stores/message/store.ts +++ b/src/stores/message/store.ts @@ -134,6 +134,7 @@ function sortConversationList(conversations: Map): } const aTime = new Date(a.last_message_at || a.updated_at || 0).getTime(); const bTime = new Date(b.last_message_at || b.updated_at || 0).getTime(); + if (Number.isNaN(aTime) || Number.isNaN(bTime)) return 0; return bTime - aTime; }); }