import React, { memo } from 'react'; import { View, TouchableOpacity, Image, StyleSheet, Pressable } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useAppColors, spacing, borderRadius, fontSizes, type AppColors } from '../../../theme'; import { Text } from '../../common'; import type { TradeItemDTO } from '../../../types/trade'; import { TRADE_CATEGORY_MAP, TRADE_CONDITION_MAP, TRADE_TYPE_MAP } from '../../../types/trade'; export interface TradeCardProps { item: TradeItemDTO; variant?: 'list' | 'grid'; onPress?: (id: string) => void; onFavorite?: (id: string) => void; } function formatPrice(price?: number | null): string { if (price == null) return '面议'; return `¥${price.toFixed(price % 1 === 0 ? 0 : 2)}`; } function createTradeCardStyles(colors: AppColors) { return StyleSheet.create({ gridCard: { backgroundColor: colors.background.paper, borderRadius: borderRadius.xl, overflow: 'hidden', // subtle shadow for depth shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.06, shadowRadius: 3, elevation: 2, }, listCard: { backgroundColor: colors.background.paper, borderRadius: borderRadius.lg, overflow: 'hidden', padding: spacing.md, }, imageContainer: { position: 'relative', aspectRatio: 1, backgroundColor: colors.background.default, minHeight: 140, }, listImageContainer: { width: 120, height: 120, borderRadius: borderRadius.md, backgroundColor: colors.background.default, overflow: 'hidden', }, image: { width: '100%', height: '100%', resizeMode: 'cover', }, // 包邮/标签 badge 放在图片左下角,黄色背景 shippingBadge: { position: 'absolute', bottom: 6, left: 6, paddingHorizontal: 5, paddingVertical: 2, borderRadius: borderRadius.sm, backgroundColor: '#FFE066', }, shippingBadgeText: { color: '#5C4B00', fontSize: 10, fontWeight: '700', }, typeBadge: { position: 'absolute', top: 6, left: 6, paddingHorizontal: 6, paddingVertical: 2, borderRadius: borderRadius.sm, overflow: 'hidden', }, sellBadge: { backgroundColor: `${colors.success.main}CC`, }, buyBadge: { backgroundColor: `${colors.primary.main}CC`, }, typeBadgeText: { color: '#fff', fontSize: fontSizes.xs, fontWeight: '600', }, statusBadge: { position: 'absolute', top: 6, right: 6, paddingHorizontal: 6, paddingVertical: 2, borderRadius: borderRadius.sm, backgroundColor: `${colors.warning.main}BB`, }, statusBadgeText: { color: '#fff', fontSize: fontSizes.xs, fontWeight: '600', }, gridContent: { paddingHorizontal: spacing.sm, paddingTop: spacing.xs, paddingBottom: spacing.sm, }, titleRow: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', gap: spacing.xs, }, title: { fontSize: fontSizes.md, fontWeight: '500', color: colors.text.primary, lineHeight: 20, flex: 1, }, priceInline: { fontSize: fontSizes.sm, fontWeight: '700', color: '#FF4D4F', flexShrink: 0, }, priceRow: { flexDirection: 'row', alignItems: 'baseline', gap: 4, marginTop: spacing.xs, }, price: { fontSize: fontSizes['2xl'], fontWeight: '700', color: '#FF4D4F', }, originalPrice: { fontSize: fontSizes.xs, color: colors.text.hint, textDecorationLine: 'line-through', }, negotiable: { fontSize: fontSizes.lg, fontWeight: '600', color: colors.text.secondary, }, wantCount: { fontSize: fontSizes.xs, color: colors.text.hint, marginLeft: 4, }, tagsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 4, marginTop: 2, }, tag: { paddingHorizontal: 5, paddingVertical: 1, borderRadius: borderRadius.sm, backgroundColor: `${colors.primary.main}10`, }, tagText: { fontSize: 10, color: colors.primary.main, }, conditionTag: { backgroundColor: `${colors.success.main}10`, }, conditionTagText: { fontSize: 10, color: colors.success.main, }, // 促销标签样式(如"24小时发货") promoTag: { paddingHorizontal: 5, paddingVertical: 1, borderRadius: borderRadius.sm, backgroundColor: '#FFF2F0', borderWidth: 0.5, borderColor: '#FFCCC7', }, promoTagText: { fontSize: 10, color: '#FF4D4F', }, bottomRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: spacing.xs, }, authorRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs, flex: 1, }, avatar: { width: 18, height: 18, borderRadius: 9, backgroundColor: colors.background.default, }, authorName: { fontSize: 11, color: colors.text.secondary, flexShrink: 1, }, statsRow: { flexDirection: 'row', alignItems: 'center', gap: 4, }, statItem: { flexDirection: 'row', alignItems: 'center', gap: 2, }, statText: { fontSize: 11, color: colors.text.hint, }, favButton: { padding: 2, }, listContent: { flex: 1, justifyContent: 'space-between', }, listLayout: { flexDirection: 'row', gap: spacing.md, }, imagePlaceholder: { width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', backgroundColor: colors.background.default, }, }); } const TradeCardBase: React.FC = ({ item, variant = 'list', onPress, onFavorite }) => { const colors = useAppColors(); const styles = createTradeCardStyles(colors); const firstImage = item.images?.[0]; const isSell = item.trade_type === 'sell'; const isActive = item.status === 'active'; const conditionLabel = item.condition ? TRADE_CONDITION_MAP[item.condition as keyof typeof TRADE_CONDITION_MAP] : null; const categoryLabel = TRADE_CATEGORY_MAP[item.category] || item.category; const renderImage = (containerStyle: any, imageStyle: any) => ( {firstImage ? ( ) : ( )} {/* 包邮标签 */} {item.is_free_shipping && ( 包邮 )} {TRADE_TYPE_MAP[item.trade_type]} {!isActive && ( {item.status === 'sold' ? '已售' : item.status === 'bought' ? '已收' : item.status === 'offline' ? '下架' : ''} )} ); const renderTitleRow = () => ( {item.title} {item.price != null ? formatPrice(item.price) : '面议'} ); const renderTags = () => { if (!conditionLabel && !isSell && !item.is_quick_ship) return null; return ( {conditionLabel && isSell && ( {conditionLabel} )} {item.is_quick_ship && ( 24小时发货 )} ); }; const renderBottom = () => ( {item.author?.avatar ? ( ) : ( )} {item.author?.nickname || '匿名'} {(item.good_reputation || 0) > 0 && ( 回头客好评过百 )} {categoryLabel} ); if (variant === 'grid') { return ( onPress?.(item.id)} activeOpacity={0.7} > {renderImage(styles.imageContainer, styles.image)} {renderTitleRow()} {renderTags()} {renderBottom()} ); } // grid variant is used in masonry layout; list variant below is unused but kept for compatibility return ( onPress?.(item.id)} activeOpacity={0.7} > {renderImage(styles.listImageContainer, styles.image)} {renderTitleRow()} {renderTags()} {renderBottom()} ); }; export const TradeCard = memo(TradeCardBase); export default TradeCard;