269 lines
6.7 KiB
TypeScript
269 lines
6.7 KiB
TypeScript
import React from 'react'
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
Pressable,
|
|
Image,
|
|
} from 'react-native'
|
|
|
|
export interface MessageData {
|
|
subType?: string
|
|
templateId?: string
|
|
templateTitle?: string
|
|
generationId?: string
|
|
resultUrl?: string[]
|
|
webpPreviewUrl?: string
|
|
likerId?: string
|
|
likerName?: string
|
|
likerAvatar?: string
|
|
commentId?: string
|
|
credits?: number
|
|
balanceAfter?: number
|
|
originalComment?: string
|
|
}
|
|
|
|
export interface Message {
|
|
id: string
|
|
type: 'SYSTEM' | 'ACTIVITY' | 'BILLING' | 'MARKETING'
|
|
title: string
|
|
content: string
|
|
data?: MessageData
|
|
link?: string
|
|
priority?: 'URGENT' | 'HIGH' | 'NORMAL' | 'LOW'
|
|
isRead: boolean
|
|
readAt?: string
|
|
createdAt: Date | string
|
|
}
|
|
|
|
interface MessageCardProps {
|
|
message: Message
|
|
onPress: (message: Message) => void
|
|
onDelete?: (id: string) => void
|
|
}
|
|
|
|
const MESSAGE_TYPE_CONFIG: Record<string, {
|
|
icon: string
|
|
showPreview: boolean
|
|
showAvatar: boolean
|
|
showQuote: boolean
|
|
}> = {
|
|
TEMPLATE_GENERATION_SUCCESS: { icon: '🎉', showPreview: true, showAvatar: false, showQuote: false },
|
|
TEMPLATE_GENERATION_FAILED: { icon: '❌', showPreview: false, showAvatar: false, showQuote: false },
|
|
TEMPLATE_LIKED: { icon: '👍', showPreview: false, showAvatar: true, showQuote: false },
|
|
TEMPLATE_FAVORITED: { icon: '⭐', showPreview: false, showAvatar: true, showQuote: false },
|
|
TEMPLATE_COMMENTED: { icon: '💬', showPreview: false, showAvatar: true, showQuote: false },
|
|
COMMENT_REPLIED: { icon: '💬', showPreview: false, showAvatar: true, showQuote: true },
|
|
CREDITS_DEDUCTED: { icon: '💰', showPreview: false, showAvatar: false, showQuote: false },
|
|
CREDITS_REFUNDED: { icon: '💰', showPreview: false, showAvatar: false, showQuote: false },
|
|
CREDITS_RECHARGED: { icon: '💰', showPreview: false, showAvatar: false, showQuote: false },
|
|
ANNOUNCEMENT: { icon: '📢', showPreview: false, showAvatar: false, showQuote: false },
|
|
}
|
|
|
|
const DEFAULT_CONFIG = { icon: '📩', showPreview: false, showAvatar: false, showQuote: false }
|
|
|
|
export const getMessageIcon = (subType?: string): string => {
|
|
if (!subType) return DEFAULT_CONFIG.icon
|
|
return MESSAGE_TYPE_CONFIG[subType]?.icon ?? DEFAULT_CONFIG.icon
|
|
}
|
|
|
|
export const getMessageConfig = (subType?: string) => {
|
|
if (!subType) return DEFAULT_CONFIG
|
|
return MESSAGE_TYPE_CONFIG[subType] ?? DEFAULT_CONFIG
|
|
}
|
|
|
|
export const formatRelativeTime = (date: Date | string): string => {
|
|
const now = new Date()
|
|
const targetDate = typeof date === 'string' ? new Date(date) : date
|
|
const diffMs = now.getTime() - targetDate.getTime()
|
|
const diffMinutes = Math.floor(diffMs / (1000 * 60))
|
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
|
|
|
if (diffMinutes < 1) {
|
|
return '刚刚'
|
|
}
|
|
|
|
if (diffMinutes < 60) {
|
|
return `${diffMinutes}分钟前`
|
|
}
|
|
|
|
if (diffHours < 24) {
|
|
return `${diffHours}小时前`
|
|
}
|
|
|
|
if (diffDays < 2) {
|
|
const hours = String(targetDate.getHours()).padStart(2, '0')
|
|
const minutes = String(targetDate.getMinutes()).padStart(2, '0')
|
|
return `昨天 ${hours}:${minutes}`
|
|
}
|
|
|
|
const year = targetDate.getFullYear()
|
|
const month = String(targetDate.getMonth() + 1).padStart(2, '0')
|
|
const day = String(targetDate.getDate()).padStart(2, '0')
|
|
const hours = String(targetDate.getHours()).padStart(2, '0')
|
|
const minutes = String(targetDate.getMinutes()).padStart(2, '0')
|
|
return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
}
|
|
|
|
export const MessageCard: React.FC<MessageCardProps> = ({
|
|
message,
|
|
onPress,
|
|
onDelete,
|
|
}) => {
|
|
const config = getMessageConfig(message.data?.subType)
|
|
const showQuote = config.showQuote && message.data?.originalComment
|
|
|
|
const handlePress = () => {
|
|
onPress(message)
|
|
}
|
|
|
|
const handleDelete = () => {
|
|
if (onDelete) {
|
|
onDelete(message.id)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Pressable
|
|
testID="message-card"
|
|
style={styles.container}
|
|
onPress={handlePress}
|
|
>
|
|
{/* Unread Indicator */}
|
|
{!message.isRead && (
|
|
<View testID="unread-indicator" style={styles.unreadIndicatorContainer}>
|
|
<View style={styles.unreadIndicator} />
|
|
</View>
|
|
)}
|
|
|
|
<View style={styles.contentWrapper}>
|
|
{/* Middle: Content */}
|
|
<View style={styles.middleSection}>
|
|
<Text style={styles.title}>{message.title}</Text>
|
|
<Text style={styles.content} numberOfLines={2}>
|
|
{message.content}
|
|
</Text>
|
|
|
|
{/* Quote */}
|
|
{showQuote && (
|
|
<View testID="quote-container" style={styles.quoteContainer}>
|
|
<Text style={styles.quoteText} numberOfLines={2}>
|
|
{message.data?.originalComment}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
<Text style={styles.time}>
|
|
{formatRelativeTime(message.createdAt)}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Right: Delete Button (if onDelete provided) */}
|
|
{onDelete && (
|
|
<Pressable
|
|
testID="delete-button"
|
|
style={styles.deleteButton}
|
|
onPress={handleDelete}
|
|
>
|
|
<Text style={styles.deleteText}>删除</Text>
|
|
</Pressable>
|
|
)}
|
|
</View>
|
|
</Pressable>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
backgroundColor: '#16181B',
|
|
borderRadius: 16,
|
|
padding: 16,
|
|
marginBottom: 12,
|
|
marginHorizontal: 4,
|
|
position: 'relative',
|
|
},
|
|
unreadIndicatorContainer: {
|
|
position: 'absolute',
|
|
top: -4,
|
|
right: -2,
|
|
width: 16,
|
|
height: 16,
|
|
borderWidth: 4,
|
|
borderColor: '#090A0B',
|
|
borderRadius: 8,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
zIndex: 1,
|
|
},
|
|
unreadIndicator: {
|
|
width: 6,
|
|
height: 6,
|
|
borderRadius: 4,
|
|
backgroundColor: '#00FF66',
|
|
},
|
|
contentWrapper: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
},
|
|
leftSection: {
|
|
marginRight: 12,
|
|
},
|
|
icon: {
|
|
fontSize: 24,
|
|
},
|
|
avatar: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
},
|
|
middleSection: {
|
|
flex: 1,
|
|
},
|
|
title: {
|
|
color: '#FFFFFF',
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
marginBottom: 4,
|
|
},
|
|
content: {
|
|
color: '#ABABAB',
|
|
fontSize: 13,
|
|
marginBottom: 8,
|
|
lineHeight: 18,
|
|
},
|
|
previewImage: {
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 8,
|
|
marginBottom: 8,
|
|
},
|
|
quoteContainer: {
|
|
backgroundColor: '#26292E',
|
|
borderRadius: 8,
|
|
padding: 8,
|
|
marginBottom: 8,
|
|
},
|
|
quoteText: {
|
|
color: '#8A8A8A',
|
|
fontSize: 12,
|
|
},
|
|
time: {
|
|
color: '#8A8A8A',
|
|
fontSize: 11,
|
|
},
|
|
deleteButton: {
|
|
backgroundColor: '#FF3B30',
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 6,
|
|
borderRadius: 6,
|
|
marginLeft: 8,
|
|
},
|
|
deleteText: {
|
|
color: '#FFFFFF',
|
|
fontSize: 12,
|
|
},
|
|
})
|
|
|
|
export default MessageCard
|