2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
2026-04-04 08:01:45 +08:00
|
|
|
|
* SystemMessageItem 系统消息项组件 - 扁平化风格
|
2026-03-09 21:29:03 +08:00
|
|
|
|
* 根据系统消息类型显示不同图标和内容
|
2026-04-04 08:01:45 +08:00
|
|
|
|
* 与登录、注册、设置页面风格保持一致
|
2026-03-09 21:29:03 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
import React, { useMemo } from 'react';
|
|
|
|
|
|
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
|
|
|
|
import { formatDistanceToNow } from 'date-fns';
|
|
|
|
|
|
import { zhCN } from 'date-fns/locale';
|
2026-04-04 08:01:45 +08:00
|
|
|
|
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
import { SystemMessageResponse, SystemMessageType } from '../../types/dto';
|
|
|
|
|
|
import Text from '../common/Text';
|
|
|
|
|
|
import Avatar from '../common/Avatar';
|
|
|
|
|
|
|
|
|
|
|
|
interface SystemMessageItemProps {
|
|
|
|
|
|
message: SystemMessageResponse;
|
|
|
|
|
|
onPress?: () => void;
|
2026-03-21 12:08:45 +08:00
|
|
|
|
onAvatarPress?: () => void;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onRequestAction?: (approve: boolean) => void;
|
|
|
|
|
|
requestActionLoading?: boolean;
|
2026-03-21 12:08:45 +08:00
|
|
|
|
index?: number; // 用于交错动画
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getSystemMessageIcon = (
|
2026-03-25 05:16:54 +08:00
|
|
|
|
systemType: SystemMessageType,
|
|
|
|
|
|
colors: AppColors
|
2026-03-21 12:08:45 +08:00
|
|
|
|
): { icon: keyof typeof MaterialCommunityIcons.glyphMap; color: string; bgColor: string; gradient: string[] } => {
|
2026-03-09 21:29:03 +08:00
|
|
|
|
switch (systemType) {
|
|
|
|
|
|
case 'like_post':
|
|
|
|
|
|
case 'like_comment':
|
|
|
|
|
|
case 'like_reply':
|
|
|
|
|
|
case 'favorite_post':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'heart',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#FF4757',
|
|
|
|
|
|
bgColor: '#FFE4E6',
|
|
|
|
|
|
gradient: ['#FF4757', '#FF6B7A'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'comment':
|
|
|
|
|
|
return {
|
2026-03-21 12:08:45 +08:00
|
|
|
|
icon: 'comment-text',
|
|
|
|
|
|
color: '#2196F3',
|
|
|
|
|
|
bgColor: '#E3F2FD',
|
|
|
|
|
|
gradient: ['#2196F3', '#64B5F6'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'reply':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'reply',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#4CAF50',
|
|
|
|
|
|
bgColor: '#E8F5E9',
|
|
|
|
|
|
gradient: ['#4CAF50', '#81C784'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'follow':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'account-plus',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#9C27B0',
|
|
|
|
|
|
bgColor: '#F3E5F5',
|
|
|
|
|
|
gradient: ['#9C27B0', '#BA68C8'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'mention':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'at',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#FF9800',
|
|
|
|
|
|
bgColor: '#FFF3E0',
|
|
|
|
|
|
gradient: ['#FF9800', '#FFB74D'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'system':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'cog',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#607D8B',
|
|
|
|
|
|
bgColor: '#ECEFF1',
|
|
|
|
|
|
gradient: ['#607D8B', '#90A4AE'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'announcement':
|
|
|
|
|
|
case 'announce':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'bullhorn',
|
|
|
|
|
|
color: colors.primary.main,
|
2026-03-21 12:08:45 +08:00
|
|
|
|
bgColor: colors.primary.light + '30',
|
|
|
|
|
|
gradient: [colors.primary.main, colors.primary.light],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'group_invite':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'account-multiple-plus',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#00BCD4',
|
|
|
|
|
|
bgColor: '#E0F7FA',
|
|
|
|
|
|
gradient: ['#00BCD4', '#4DD0E1'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'group_join_apply':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'account-clock',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#FF9800',
|
|
|
|
|
|
bgColor: '#FFF3E0',
|
|
|
|
|
|
gradient: ['#FF9800', '#FFB74D'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'group_join_approved':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'check-circle',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#4CAF50',
|
|
|
|
|
|
bgColor: '#E8F5E9',
|
|
|
|
|
|
gradient: ['#4CAF50', '#81C784'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
case 'group_join_rejected':
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'close-circle',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#F44336',
|
|
|
|
|
|
bgColor: '#FFEBEE',
|
|
|
|
|
|
gradient: ['#F44336', '#E57373'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
default:
|
|
|
|
|
|
return {
|
|
|
|
|
|
icon: 'bell',
|
2026-03-21 12:08:45 +08:00
|
|
|
|
color: '#757575',
|
|
|
|
|
|
bgColor: '#F5F5F5',
|
|
|
|
|
|
gradient: ['#757575', '#9E9E9E'],
|
2026-03-09 21:29:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
function createSystemMessageStyles(colors: AppColors) {
|
|
|
|
|
|
return StyleSheet.create({
|
|
|
|
|
|
container: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'flex-start',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
padding: 16,
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
borderBottomWidth: 1,
|
|
|
|
|
|
borderBottomColor: colors.divider || '#E5E5EA',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
unreadContainer: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
backgroundColor: '#FFF8F6',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
unreadIndicator: {
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
left: 6,
|
|
|
|
|
|
top: '50%',
|
|
|
|
|
|
marginTop: -4,
|
|
|
|
|
|
width: 8,
|
|
|
|
|
|
height: 8,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
borderRadius: 4,
|
|
|
|
|
|
backgroundColor: '#FF6B35',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
iconContainer: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginRight: 14,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
avatarWrapper: {
|
|
|
|
|
|
position: 'relative',
|
|
|
|
|
|
},
|
|
|
|
|
|
iconWrapper: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
width: 44,
|
|
|
|
|
|
height: 44,
|
|
|
|
|
|
borderRadius: 12,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
},
|
|
|
|
|
|
typeIconBadge: {
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
bottom: -2,
|
|
|
|
|
|
right: -2,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
width: 18,
|
|
|
|
|
|
height: 18,
|
|
|
|
|
|
borderRadius: 9,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
borderWidth: 2,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
borderColor: '#fff',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
content: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
minWidth: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
titleRow: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginBottom: 4,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
titleLeft: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
flex: 1,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginRight: 8,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
title: {
|
|
|
|
|
|
fontWeight: '500',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
fontSize: 15,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
},
|
|
|
|
|
|
unreadTitle: {
|
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
},
|
|
|
|
|
|
statusBadge: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
paddingHorizontal: 6,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
paddingVertical: 2,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
borderRadius: 4,
|
|
|
|
|
|
marginLeft: 6,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
statusText: {
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
|
marginLeft: 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
time: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
fontSize: 13,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
flexShrink: 0,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
color: colors.text.hint,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
messageContent: {
|
|
|
|
|
|
lineHeight: 20,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
color: colors.text.secondary,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
extraInfo: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginTop: 8,
|
|
|
|
|
|
backgroundColor: '#F5F5F7',
|
|
|
|
|
|
paddingHorizontal: 10,
|
|
|
|
|
|
paddingVertical: 6,
|
|
|
|
|
|
borderRadius: 8,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
alignSelf: 'flex-start',
|
|
|
|
|
|
},
|
|
|
|
|
|
extraText: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginLeft: 6,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
flex: 1,
|
|
|
|
|
|
fontWeight: '500',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
fontSize: 13,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
actionsRow: {
|
|
|
|
|
|
flexDirection: 'row',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginTop: 12,
|
|
|
|
|
|
gap: 10,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
actionBtn: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
paddingVertical: 8,
|
|
|
|
|
|
paddingHorizontal: 16,
|
|
|
|
|
|
borderRadius: 10,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
borderWidth: 1,
|
2026-04-04 08:01:45 +08:00
|
|
|
|
gap: 4,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
rejectBtn: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
borderColor: colors.error.main + '40',
|
|
|
|
|
|
backgroundColor: colors.error.light + '20',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
approveBtn: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
borderColor: colors.success.main + '40',
|
|
|
|
|
|
backgroundColor: colors.success.light + '20',
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
actionBtnText: {
|
|
|
|
|
|
fontWeight: '500',
|
2026-04-04 08:01:45 +08:00
|
|
|
|
fontSize: 13,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
},
|
|
|
|
|
|
arrowContainer: {
|
2026-04-04 08:01:45 +08:00
|
|
|
|
marginLeft: 8,
|
2026-03-25 05:16:54 +08:00
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
height: 20,
|
|
|
|
|
|
width: 20,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
// 获取系统消息标题
|
|
|
|
|
|
const getSystemMessageTitle = (message: SystemMessageResponse): string => {
|
|
|
|
|
|
const { system_type, extra_data } = message;
|
|
|
|
|
|
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 '通知';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-04 08:01:45 +08:00
|
|
|
|
// 胡萝卜橙主题色
|
|
|
|
|
|
const THEME_COLORS = {
|
|
|
|
|
|
primary: '#FF6B35',
|
|
|
|
|
|
primaryLight: '#FF8C5A',
|
|
|
|
|
|
primaryDark: '#E55A2B',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const SystemMessageItem: React.FC<SystemMessageItemProps> = ({
|
|
|
|
|
|
message,
|
|
|
|
|
|
onPress,
|
|
|
|
|
|
onAvatarPress,
|
|
|
|
|
|
onRequestAction,
|
|
|
|
|
|
requestActionLoading = false,
|
2026-03-21 12:08:45 +08:00
|
|
|
|
index = 0,
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}) => {
|
2026-03-25 05:16:54 +08:00
|
|
|
|
const colors = useAppColors();
|
|
|
|
|
|
const styles = useMemo(() => createSystemMessageStyles(colors), [colors]);
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
// 格式化时间
|
|
|
|
|
|
const formatTime = (dateString: string): string => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return formatDistanceToNow(new Date(dateString), {
|
|
|
|
|
|
addSuffix: true,
|
|
|
|
|
|
locale: zhCN,
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 05:16:54 +08:00
|
|
|
|
const { icon, color, bgColor } = getSystemMessageIcon(message.system_type, colors);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const title = getSystemMessageTitle(message);
|
|
|
|
|
|
const { extra_data } = message;
|
|
|
|
|
|
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;
|
2026-03-21 12:08:45 +08:00
|
|
|
|
const isUnread = message.is_read !== true;
|
2026-03-16 17:47:10 +08:00
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
// 判断是否显示操作者头像
|
|
|
|
|
|
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';
|
|
|
|
|
|
|
2026-03-21 12:08:45 +08:00
|
|
|
|
// 获取状态标签
|
|
|
|
|
|
const getStatusBadge = () => {
|
|
|
|
|
|
if (requestStatus === 'accepted') {
|
|
|
|
|
|
return (
|
2026-03-25 05:16:54 +08:00
|
|
|
|
<View style={[styles.statusBadge, { backgroundColor: `${colors.success.main}22` }]}>
|
|
|
|
|
|
<MaterialCommunityIcons name="check" size={10} color={colors.success.main} />
|
|
|
|
|
|
<Text variant="caption" color={colors.success.main} style={styles.statusText}>已同意</Text>
|
2026-03-21 12:08:45 +08:00
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (requestStatus === 'rejected') {
|
|
|
|
|
|
return (
|
2026-03-25 05:16:54 +08:00
|
|
|
|
<View style={[styles.statusBadge, { backgroundColor: `${colors.error.main}22` }]}>
|
|
|
|
|
|
<MaterialCommunityIcons name="close" size={10} color={colors.error.main} />
|
|
|
|
|
|
<Text variant="caption" color={colors.error.main} style={styles.statusText}>已拒绝</Text>
|
2026-03-21 12:08:45 +08:00
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<TouchableOpacity
|
2026-03-21 12:08:45 +08:00
|
|
|
|
style={[
|
|
|
|
|
|
styles.container,
|
|
|
|
|
|
isUnread && styles.unreadContainer,
|
|
|
|
|
|
]}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onPress={onPress}
|
2026-03-21 12:08:45 +08:00
|
|
|
|
activeOpacity={onPress ? 0.85 : 1}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
disabled={!onPress}
|
|
|
|
|
|
>
|
2026-03-21 12:08:45 +08:00
|
|
|
|
{/* 左侧未读指示器 */}
|
|
|
|
|
|
{isUnread && <View style={styles.unreadIndicator} />}
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
{/* 图标/头像区域 */}
|
|
|
|
|
|
<View style={styles.iconContainer}>
|
|
|
|
|
|
{showGroupAvatar ? (
|
2026-03-21 12:08:45 +08:00
|
|
|
|
<View style={styles.avatarWrapper}>
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
source={groupAvatar || ''}
|
|
|
|
|
|
size={48}
|
|
|
|
|
|
name={extra_data?.group_name || '群聊'}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<View style={[styles.typeIconBadge, { backgroundColor: bgColor }]}>
|
|
|
|
|
|
<MaterialCommunityIcons name={icon} size={12} color={color} />
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
) : showOperatorAvatar ? (
|
2026-03-21 12:08:45 +08:00
|
|
|
|
<View style={styles.avatarWrapper}>
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
source={operatorAvatar || ''}
|
|
|
|
|
|
size={48}
|
|
|
|
|
|
name={operatorName}
|
|
|
|
|
|
onPress={onAvatarPress}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<View style={[styles.typeIconBadge, { backgroundColor: bgColor }]}>
|
|
|
|
|
|
<MaterialCommunityIcons name={icon} size={12} color={color} />
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
) : (
|
|
|
|
|
|
<View style={[styles.iconWrapper, { backgroundColor: bgColor }]}>
|
2026-03-21 12:08:45 +08:00
|
|
|
|
<MaterialCommunityIcons name={icon} size={24} color={color} />
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 内容区域 */}
|
|
|
|
|
|
<View style={styles.content}>
|
|
|
|
|
|
{/* 标题行 */}
|
|
|
|
|
|
<View style={styles.titleRow}>
|
2026-03-21 12:08:45 +08:00
|
|
|
|
<View style={styles.titleLeft}>
|
|
|
|
|
|
<Text
|
|
|
|
|
|
variant="body"
|
|
|
|
|
|
style={[styles.title, ...(isUnread ? [styles.unreadTitle] : [])]}
|
|
|
|
|
|
numberOfLines={1}
|
|
|
|
|
|
>
|
|
|
|
|
|
{title}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
{getStatusBadge()}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<Text variant="caption" color={colors.text.hint} style={styles.time}>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
{formatTime(message.created_at)}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 消息内容 */}
|
2026-03-21 12:08:45 +08:00
|
|
|
|
<Text
|
|
|
|
|
|
variant="caption"
|
|
|
|
|
|
color={colors.text.secondary}
|
|
|
|
|
|
numberOfLines={2}
|
|
|
|
|
|
style={styles.messageContent}
|
|
|
|
|
|
>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
{message.content}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
|
2026-03-21 12:08:45 +08:00
|
|
|
|
{/* 附加信息 */}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
{(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) &&
|
|
|
|
|
|
(() => {
|
2026-03-21 12:08:45 +08:00
|
|
|
|
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-text-outline' : 'file-document-outline';
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View style={styles.extraInfo}>
|
|
|
|
|
|
<MaterialCommunityIcons name={iconName} size={12} color={colors.primary.main} />
|
|
|
|
|
|
<Text variant="caption" color={colors.primary.main} numberOfLines={1} style={styles.extraText}>
|
|
|
|
|
|
{previewText}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
})()}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-21 12:08:45 +08:00
|
|
|
|
{/* 操作按钮 */}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
{isActionable && (
|
|
|
|
|
|
<View style={styles.actionsRow}>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={[styles.actionBtn, styles.rejectBtn]}
|
|
|
|
|
|
onPress={() => onRequestAction?.(false)}
|
|
|
|
|
|
disabled={requestActionLoading}
|
|
|
|
|
|
>
|
2026-03-25 05:16:54 +08:00
|
|
|
|
<MaterialCommunityIcons name="close" size={14} color={colors.error.main} />
|
|
|
|
|
|
<Text variant="caption" color={colors.error.main} style={styles.actionBtnText}>拒绝</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={[styles.actionBtn, styles.approveBtn]}
|
|
|
|
|
|
onPress={() => onRequestAction?.(true)}
|
|
|
|
|
|
disabled={requestActionLoading}
|
|
|
|
|
|
>
|
2026-03-25 05:16:54 +08:00
|
|
|
|
<MaterialCommunityIcons name="check" size={14} color={colors.success.main} />
|
|
|
|
|
|
<Text variant="caption" color={colors.success.main} style={styles.actionBtnText}>同意</Text>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
2026-03-21 12:08:45 +08:00
|
|
|
|
{/* 右侧箭头 */}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
{onPress && (
|
|
|
|
|
|
<View style={styles.arrowContainer}>
|
|
|
|
|
|
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
|
|
|
|
|
</View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default SystemMessageItem;
|