import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { View, ScrollView, Image, StyleSheet, Pressable, Alert, Modal } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { useAppColors, spacing, borderRadius, fontSizes, shadows, type AppColors } from '../../theme'; import { Text, Loading, AppBackButton, ImageGrid, ImageGallery } from '../../components/common'; import { tradeService } from '../../services/trade/tradeService'; import { messageService } from '../../services/message/messageService'; import { useIsAuthenticated, useCurrentUser } from '../../stores/auth'; import type { TradeItemDetailDTO } from '../../types/trade'; import { TRADE_CONDITION_MAP } from '../../types/trade'; import { hrefChat, hrefEditTrade } from '../../navigation/hrefs'; import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks/useResponsive'; function createStyles(colors: AppColors) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background.default, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: spacing.md, paddingVertical: spacing.md, backgroundColor: colors.background.paper, ...shadows.sm, }, headerLeft: { width: 40, alignItems: 'flex-start', }, headerTitle: { fontSize: fontSizes.xl, fontWeight: '700', color: colors.text.primary, }, headerRight: { width: 40, }, // 降价标签 discountBadge: { position: 'absolute', bottom: 12, left: 12, flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 10, paddingVertical: 5, borderRadius: borderRadius.lg, backgroundColor: 'rgba(0,0,0,0.6)', }, discountBadgeText: { color: '#fff', fontSize: fontSizes.sm, fontWeight: '600', }, content: { padding: spacing.md, backgroundColor: colors.background.paper, }, // 顶部卖家信息栏 sellerHeader: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md, }, sellerAvatar: { width: 36, height: 36, borderRadius: 18, backgroundColor: colors.background.default, }, sellerInfo: { flex: 1, }, sellerNameRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs, }, sellerName: { fontSize: fontSizes.lg, fontWeight: '600', color: colors.text.primary, }, sellerCreditBadge: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: borderRadius.sm, backgroundColor: '#FFE066', }, sellerCreditText: { fontSize: 10, color: '#5C4B00', fontWeight: '700', }, sellerMeta: { fontSize: fontSizes.sm, color: colors.text.hint, marginTop: 2, paddingLeft: 36 + spacing.sm, }, priceInHeader: { fontSize: fontSizes['2xl'], fontWeight: '700', color: '#FF4D4F', }, priceSymbol: { fontSize: fontSizes.md, fontWeight: '700', color: '#FF4D4F', }, titleRow: { flexDirection: 'row', alignItems: 'flex-start', gap: spacing.sm, marginBottom: spacing.lg, }, conditionBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: borderRadius.sm, backgroundColor: '#FFE066', flexShrink: 0, }, conditionBadgeText: { fontSize: fontSizes.sm, color: '#5C4B00', fontWeight: '600', }, // 副标题信息行(包邮、自提) subInfoRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md, flexWrap: 'wrap', }, subInfoItem: { fontSize: fontSizes.sm, color: colors.text.secondary, }, shippingTag: { paddingHorizontal: 5, paddingVertical: 1, borderRadius: borderRadius.sm, borderWidth: 0.5, borderColor: '#FFCCC7', backgroundColor: '#FFF2F0', }, shippingTagText: { fontSize: fontSizes.xs, color: '#FF4D4F', }, // 促销横幅 promoBanner: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, backgroundColor: '#FFF5F5', borderRadius: borderRadius.md, marginBottom: spacing.md, }, promoBannerText: { fontSize: fontSizes.md, color: '#FF4D4F', fontWeight: '700', }, promoBannerSub: { fontSize: fontSizes.md, color: '#FF7875', }, title: { fontWeight: '800', color: colors.text.primary, letterSpacing: -0.3, flex: 1, }, // 商品属性行 attrGrid: { flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', marginBottom: spacing.md, gap: spacing.lg, }, attrItem: { flexDirection: 'row', alignItems: 'center', gap: 4, }, attrLabel: { fontSize: fontSizes.sm, color: colors.text.hint, }, attrValue: { fontSize: fontSizes.sm, color: colors.text.primary, fontWeight: '500', }, // 描述 - 与帖子正文同步 contentText: { color: colors.text.primary, letterSpacing: 0.2, marginBottom: spacing.lg, }, // 底部操作栏 bottomBar: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: spacing.md, paddingVertical: 4, backgroundColor: colors.background.paper, borderTopWidth: 1, borderTopColor: colors.divider, gap: spacing.md, }, bottomAction: { alignItems: 'center', justifyContent: 'center', gap: 2, }, bottomActionText: { fontSize: 10, color: colors.text.secondary, }, chatButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.xs, height: 36, paddingHorizontal: spacing.lg, borderRadius: 14, backgroundColor: colors.primary.main, }, chatButtonText: { color: colors.text.inverse, fontSize: fontSizes.md, fontWeight: '600', }, chatButtonDisabled: { backgroundColor: colors.text.hint, }, // 管理按钮 editButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.sm, height: 48, borderRadius: 14, borderWidth: 1, borderColor: colors.divider, paddingHorizontal: spacing.lg, backgroundColor: colors.background.paper, }, editButtonText: { fontSize: fontSizes.md, color: colors.text.primary, fontWeight: '600', }, statusSection: { marginBottom: spacing.md, }, divider: { height: 1, backgroundColor: colors.divider, marginVertical: spacing.md, }, // 底部弹窗 modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end', }, modalContent: { backgroundColor: colors.background.paper, borderTopLeftRadius: borderRadius.xl, borderTopRightRadius: borderRadius.xl, paddingBottom: spacing.xl, }, modalItem: { paddingVertical: spacing.md, alignItems: 'center', borderBottomWidth: 1, borderBottomColor: colors.divider, }, modalItemText: { fontSize: fontSizes.lg, color: colors.text.primary, }, modalCancel: { paddingVertical: spacing.md, alignItems: 'center', marginTop: spacing.sm, }, modalCancelText: { fontSize: fontSizes.lg, color: colors.text.hint, }, // 管理按钮 manageButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.xs, height: 36, paddingHorizontal: spacing.lg, borderRadius: 14, backgroundColor: 'transparent', }, manageButtonText: { fontSize: fontSizes.md, color: colors.text.primary, fontWeight: '600', }, contentAttrs: { flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', gap: spacing.md, flexWrap: 'wrap', }, contentAttrText: { fontSize: fontSizes.sm, color: colors.text.secondary, }, }); } interface TradeDetailScreenProps { tradeId: string; } export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) { const colors = useAppColors(); const styles = createStyles(colors); const router = useRouter(); const isAuth = useIsAuthenticated(); const currentUser = useCurrentUser(); const [item, setItem] = useState(null); const [loading, setLoading] = useState(true); const [manageModalVisible, setManageModalVisible] = useState(false); const [showImageModal, setShowImageModal] = useState(false); const [selectedImageIndex, setSelectedImageIndex] = useState(0); const isOwner = item?.user_id === currentUser?.id; // 响应式 hook(与帖子同步) const { width, isMobile, isTablet, isDesktop, isWideScreen, } = useResponsive(); const responsivePadding = useResponsiveSpacing({ xs: 12, sm: 14, md: 16, lg: 20, xl: 24, '2xl': 28, '3xl': 32, '4xl': 40 }); const responsiveGap = useResponsiveSpacing({ xs: 8, sm: 10, md: 12, lg: 16, xl: 20, '2xl': 24, '3xl': 28, '4xl': 32 }); const fetchItem = useCallback(async () => { try { setLoading(true); const data = await tradeService.getTradeItem(tradeId); setItem(data); } catch (e) { console.error('Failed to fetch trade item:', e); Alert.alert('错误', '加载失败'); } finally { setLoading(false); } }, [tradeId]); useEffect(() => { fetchItem(); }, [fetchItem]); useEffect(() => { if (tradeId) { tradeService.recordView(tradeId).catch(() => {}); } }, [tradeId]); const handleFavorite = useCallback(async () => { if (!isAuth || !item) return; try { if (item.is_favorited) { await tradeService.unfavorite(item.id); } else { await tradeService.favorite(item.id); } setItem(prev => prev ? { ...prev, is_favorited: !prev.is_favorited, favorites_count: prev.favorites_count + (prev.is_favorited ? -1 : 1), } : prev); } catch (e) { console.error('Favorite failed:', e); } }, [isAuth, item]); const handleDelete = useCallback(() => { if (!item) return; setManageModalVisible(false); Alert.alert('删除确认', '确定要删除这个商品吗?此操作不可恢复。', [ { text: '取消', style: 'cancel' }, { text: '删除', style: 'destructive', onPress: async () => { try { await tradeService.deleteTradeItem(item.id); router.back(); } catch (e) { Alert.alert('删除失败', '请稍后重试'); } }, }, ]); }, [item, router]); const handleEdit = useCallback(() => { setManageModalVisible(false); router.push(hrefEditTrade(item?.id || '') as any); }, [router, item?.id]); const handleChat = useCallback(async () => { if (!isAuth || !item?.author?.id) { Alert.alert('提示', '请先登录'); return; } try { const conversation = await messageService.createConversation(item.author.id); if (conversation) { router.push( hrefChat({ conversationId: conversation.id.toString(), userId: item.author.id, }) as any ); } } catch (error) { console.error('创建会话失败:', error); Alert.alert('错误', '无法发起聊天,请稍后重试'); } }, [isAuth, item?.author?.id, router]); // 响应式图片间距和圆角(与帖子同步) const imageGap = isDesktop ? 12 : isTablet ? 10 : 8; const imageBorderRadius = isDesktop ? borderRadius.xl : borderRadius.lg; const images = item?.images || []; // 转换图片格式给 ImageGrid 使用 const tradeImages = useMemo(() => { return images.map(img => ({ id: img.id, url: img.url || img.preview_url_large || img.preview_url || '', thumbnail_url: img.preview_url || img.preview_url_large || img.url || '', width: img.width, height: img.height, })); }, [images]); const handleImagePress = useCallback((allImages: any[], index: number) => { setSelectedImageIndex(index); setShowImageModal(true); }, []); if (loading) { return ; } if (!item) { return ( 商品不存在或已删除 ); } return ( router.back()} /> 商品详情 {/* 卖家信息头部 */} {item.author && ( {item.author.avatar ? ( ) : ( )} {item.author.nickname && item.author.nickname.length > 10 ? `${item.author.nickname.slice(0, 10)}...` : item.author.nickname || '匿名'} {(item.author?.credit_score || 0) > 80 && ( 卖家信用优秀 )} {item.price != null ? ( 价格:¥{item.price % 1 === 0 ? item.price.toFixed(0) : item.price.toFixed(2)} ) : ( 价格:面议 )} )} {/* 标题 - 与帖子同步 */} {item.title} {/* 商品描述 - 与帖子正文同步 */} {item.content && ( {item.content} )} {/* 图片 - 使用 ImageGrid 与帖子同步 */} {tradeImages.length > 0 && ( )} {/* 内容区右下角属性文字(仅出售商品显示) */} {item.trade_type === 'sell' && ( 品牌:{item.brand || '其他'} 成色:{TRADE_CONDITION_MAP[item.condition as keyof typeof TRADE_CONDITION_MAP] || item.condition || '未知'} 功能状态:{item.function_status || '正常使用'} )} {/* 底部操作栏 */} {isOwner ? ( /* 卖家管理按钮 */ <> setManageModalVisible(true)}> 管理 ) : ( <> {!isOwner && item.status === 'active' && ( <> 聊一聊 )} {!isOwner && item.status !== 'active' && ( {item.status === 'sold' ? '已售出' : item.status === 'bought' ? '已收得' : '已下架'} )} )} {/* 管理弹窗 */} setManageModalVisible(false)} > setManageModalVisible(false)}> 编辑 删除 setManageModalVisible(false)}> 取消 {/* 图片预览 */} ({ id: img.id || img.url || String(Math.random()), url: img.url || '', }))} initialIndex={selectedImageIndex} onClose={() => setShowImageModal(false)} enableSave /> ); }