/** * SystemMessageItem 系统消息项组件 * 根据系统消息类型显示不同图标和内容 * 参考 QQ 10000 系统消息和微信服务通知样式 */ import React from 'react'; import { View, TouchableOpacity, StyleSheet } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { formatDistanceToNow } from 'date-fns'; import { zhCN } from 'date-fns/locale'; import { colors, spacing, borderRadius } from '../../theme'; import { SystemMessageResponse, SystemMessageType } from '../../types/dto'; import Text from '../common/Text'; import Avatar from '../common/Avatar'; interface SystemMessageItemProps { message: SystemMessageResponse; onPress?: () => void; onAvatarPress?: () => void; // 头像点击回调 onRequestAction?: (approve: boolean) => void; requestActionLoading?: boolean; } // 系统消息类型到图标和颜色的映射 const getSystemMessageIcon = ( systemType: SystemMessageType ): { icon: keyof typeof MaterialCommunityIcons.glyphMap; color: string; bgColor: string } => { switch (systemType) { case 'like_post': case 'like_comment': case 'like_reply': case 'favorite_post': return { icon: 'heart', color: colors.error.main, bgColor: colors.error.light + '20', }; case 'comment': return { icon: 'comment', color: colors.info.main, bgColor: colors.info.light + '20', }; case 'reply': return { icon: 'reply', color: colors.success.main, bgColor: colors.success.light + '20', }; case 'follow': return { icon: 'account-plus', color: '#9C27B0', // 紫色 bgColor: '#9C27B020', }; case 'mention': return { icon: 'at', color: colors.warning.main, bgColor: colors.warning.light + '20', }; case 'system': return { icon: 'cog', color: colors.text.secondary, bgColor: colors.background.disabled, }; case 'announcement': case 'announce': return { icon: 'bullhorn', color: colors.primary.main, bgColor: colors.primary.light + '20', }; case 'group_invite': return { icon: 'account-multiple-plus', color: colors.primary.main, bgColor: colors.primary.light + '20', }; case 'group_join_apply': return { icon: 'account-clock', color: colors.warning.main, bgColor: colors.warning.light + '20', }; case 'group_join_approved': return { icon: 'check-circle', color: colors.success.main, bgColor: colors.success.light + '20', }; case 'group_join_rejected': return { icon: 'close-circle', color: colors.error.main, bgColor: colors.error.light + '20', }; default: return { icon: 'bell', color: colors.text.secondary, bgColor: colors.background.disabled, }; } }; // 获取系统消息标题 const getSystemMessageTitle = (message: SystemMessageResponse): string => { const { system_type, extra_data } = message; // 兼容后端返回的 actor_name 和 operator_name const operatorName = extra_data?.actor_name || extra_data?.operator_name; const groupName = extra_data?.group_name || extra_data?.target_title; switch (system_type) { case 'like_post': return operatorName ? `${operatorName} 赞了你的帖子` : '有人赞了你的帖子'; case 'like_comment': return operatorName ? `${operatorName} 赞了你的评论` : '有人赞了你的评论'; case 'like_reply': return operatorName ? `${operatorName} 赞了你的回复` : '有人赞了你的回复'; case 'favorite_post': return operatorName ? `${operatorName} 收藏了你的帖子` : '有人收藏了你的帖子'; case 'comment': return operatorName ? `${operatorName} 评论了你的帖子` : '有人评论了你的帖子'; case 'reply': return operatorName ? `${operatorName} 回复了你` : '有人回复了你'; case 'follow': return operatorName ? `${operatorName} 关注了你` : '有人关注了你'; case 'mention': return operatorName ? `${operatorName} @提到了你` : '有人@提到了你'; case 'system': return '系统通知'; case 'announcement': return '公告'; case 'group_invite': return operatorName ? `${operatorName} 邀请加入群聊 ${groupName || ''}`.trim() : `收到群邀请${groupName ? `:${groupName}` : ''}`; case 'group_join_apply': if (extra_data?.request_status === 'accepted') { return operatorName ? `${operatorName} 已同意` : '该请求已同意'; } if (extra_data?.request_status === 'rejected') { return operatorName ? `${operatorName} 已拒绝` : '该请求已拒绝'; } return operatorName ? `${operatorName} 申请加入群聊 ${groupName || ''}`.trim() : `收到加群申请${groupName ? `:${groupName}` : ''}`; case 'group_join_approved': return '加群申请已通过'; case 'group_join_rejected': return '加群申请被拒绝'; default: return '通知'; } }; const SystemMessageItem: React.FC = ({ message, onPress, onAvatarPress, onRequestAction, requestActionLoading = false, }) => { // 格式化时间 const formatTime = (dateString: string): string => { try { return formatDistanceToNow(new Date(dateString), { addSuffix: true, locale: zhCN, }); } catch { return ''; } }; const { icon, color, bgColor } = getSystemMessageIcon(message.system_type); const title = getSystemMessageTitle(message); const { extra_data } = message; // 兼容后端返回的 actor_name 和 operator_name const operatorName = extra_data?.actor_name || extra_data?.operator_name; const operatorAvatar = extra_data?.avatar_url || extra_data?.operator_avatar; const groupAvatar = extra_data?.group_avatar; const requestStatus = extra_data?.request_status; const isActionable = (message.system_type === 'group_invite' || message.system_type === 'group_join_apply') && requestStatus === 'pending' && !!onRequestAction; // 使用固定的响应式值,兼容移动端和Web端 const containerPadding = spacing.md; const avatarSize = 44; const iconSize = 22; const contentFontSize = 14; const titleFontSize = 14; const timeFontSize = 12; const styles = StyleSheet.create({ container: { flexDirection: 'row', alignItems: 'flex-start', padding: containerPadding, backgroundColor: colors.background.paper, borderBottomWidth: 1, borderBottomColor: colors.divider, }, iconContainer: { marginRight: spacing.md, }, iconWrapper: { width: avatarSize, height: avatarSize, borderRadius: avatarSize / 2, justifyContent: 'center', alignItems: 'center', }, content: { flex: 1, justifyContent: 'center', minWidth: 0, }, titleRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: spacing.xs, }, title: { fontWeight: '600', flex: 1, marginRight: spacing.sm, fontSize: titleFontSize, }, time: { fontSize: timeFontSize, }, messageContent: { lineHeight: 20, fontSize: contentFontSize, }, extraInfo: { flexDirection: 'row', alignItems: 'center', marginTop: spacing.xs, backgroundColor: colors.background.default, paddingHorizontal: spacing.sm, paddingVertical: spacing.xs, borderRadius: borderRadius.sm, alignSelf: 'flex-start', }, extraText: { marginLeft: spacing.xs, flex: 1, }, actionsRow: { flexDirection: 'row', marginTop: spacing.sm, }, actionBtn: { paddingVertical: spacing.xs, paddingHorizontal: spacing.md, borderRadius: borderRadius.sm, borderWidth: 1, marginRight: spacing.sm, }, rejectBtn: { borderColor: colors.error.light, backgroundColor: colors.error.light + '18', }, approveBtn: { borderColor: colors.success.light, backgroundColor: colors.success.light + '18', }, arrowContainer: { marginLeft: spacing.sm, justifyContent: 'center', alignItems: 'center', height: 20, width: 24, }, }); // 判断是否显示操作者头像 const showOperatorAvatar = ['follow', 'like_post', 'like_comment', 'like_reply', 'favorite_post', 'comment', 'reply', 'mention', 'group_join_apply'].includes( message.system_type ); const showGroupAvatar = message.system_type === 'group_invite'; return ( {/* 图标/头像区域 */} {showGroupAvatar ? ( ) : showOperatorAvatar ? ( ) : ( )} {/* 内容区域 */} {/* 标题行 */} {title} {formatTime(message.created_at)} {/* 消息内容 */} {message.content} {/* 附加信息 - 优先显示 target_title,图标根据 target_type 区分 */} {(extra_data?.target_title || extra_data?.post_title || extra_data?.comment_preview) && !['group_invite', 'group_join_apply', 'group_join_approved', 'group_join_rejected'].includes(message.system_type) && (() => { const isCommentType = ['comment', 'reply'].includes(extra_data?.target_type ?? ''); const previewText = extra_data?.target_title || extra_data?.post_title || extra_data?.comment_preview; const iconName = isCommentType ? 'comment-outline' : 'file-document-outline'; return ( {previewText} ); })()} {isActionable && ( onRequestAction?.(false)} disabled={requestActionLoading} > 拒绝 onRequestAction?.(true)} disabled={requestActionLoading} > 同意 )} {/* 右侧箭头(如果有跳转) */} {onPress && ( )} ); }; export default SystemMessageItem;