refactor(ui): enhance TradeDetailScreen with responsive design and image gallery
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 2m44s
Frontend CI / ota-android (push) Successful in 10m26s
Frontend CI / build-android-apk (push) Successful in 39m50s

- Add responsive hooks (useResponsive, useResponsiveValue, useResponsiveSpacing)
- Replace custom image handling with ImageGrid and ImageGallery components
- Update styling with modern typography and spacing adjustments
- Add image modal for fullscreen gallery view
This commit is contained in:
lafay
2026-04-27 10:32:24 +08:00
parent e0b43ce60d
commit f478fa3fd3

View File

@@ -1,22 +1,23 @@
import React, { useState, useEffect, useCallback } from 'react';
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 } from '../../components/common';
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.paper,
backgroundColor: colors.background.default,
},
header: {
flexDirection: 'row',
@@ -39,17 +40,6 @@ function createStyles(colors: AppColors) {
headerRight: {
width: 40,
},
imageContainer: {
width: '100%',
aspectRatio: 1,
backgroundColor: colors.background.paper,
position: 'relative',
},
image: {
width: '100%',
height: '100%',
resizeMode: 'contain',
},
// 降价标签
discountBadge: {
position: 'absolute',
@@ -130,7 +120,7 @@ function createStyles(colors: AppColors) {
flexDirection: 'row',
alignItems: 'flex-start',
gap: spacing.sm,
marginBottom: spacing.md,
marginBottom: spacing.lg,
},
conditionBadge: {
paddingHorizontal: 8,
@@ -189,10 +179,9 @@ function createStyles(colors: AppColors) {
color: '#FF7875',
},
title: {
fontSize: fontSizes['3xl'],
fontWeight: '600',
fontWeight: '800',
color: colors.text.primary,
lineHeight: 34,
letterSpacing: -0.3,
flex: 1,
},
// 商品属性行
@@ -217,12 +206,11 @@ function createStyles(colors: AppColors) {
color: colors.text.primary,
fontWeight: '500',
},
// 描述
// 描述 - 与帖子正文同步
contentText: {
fontSize: fontSizes.lg,
color: colors.text.primary,
lineHeight: 28,
marginBottom: spacing.md,
letterSpacing: 0.2,
marginBottom: spacing.lg,
},
// 底部操作栏
bottomBar: {
@@ -362,12 +350,23 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
const [item, setItem] = useState<TradeItemDetailDTO | null>(null);
const [loading, setLoading] = useState(true);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
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 {
@@ -456,6 +455,28 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
}
}, [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 <Loading fullScreen />;
}
@@ -470,9 +491,6 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
);
}
const images = item.images || [];
const currentImage = images[currentImageIndex];
return (
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
<View style={styles.header}>
@@ -484,7 +502,7 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
</View>
<ScrollView>
<View style={styles.content}>
<View style={[styles.content, { paddingHorizontal: responsivePadding, paddingTop: responsivePadding }]}>
{/* 卖家信息头部 */}
{item.author && (
<View>
@@ -522,34 +540,56 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
</View>
)}
{/* 标题 */}
{/* 标题 - 与帖子同步 */}
<View style={styles.titleRow}>
<Text style={styles.title} numberOfLines={2}>{item.title}</Text>
<Text
style={[
styles.title,
{
fontSize: isDesktop ? fontSizes['3xl'] : isTablet ? fontSizes['2xl'] : fontSizes['2xl'],
lineHeight: isDesktop ? 40 : isTablet ? 36 : 32,
},
]}
numberOfLines={2}
>
{item.title}
</Text>
</View>
{/* 商品描述 */}
{/* 商品描述 - 与帖子正文同步 */}
{item.content && (
<Text style={styles.contentText}>{item.content}</Text>
<Text
style={[
styles.contentText,
{
fontSize: isDesktop ? fontSizes.xl : isTablet ? fontSizes.lg : fontSizes.lg,
lineHeight: isDesktop ? 32 : isTablet ? 30 : 28,
},
]}
>
{item.content}
</Text>
)}
</View>
{/* 图片放在文字下方 */}
{images.length > 0 && (
<View style={{ paddingHorizontal: spacing.md, paddingBottom: spacing.md }}>
{images.map((img, idx) => (
<View key={img.id || idx} style={[styles.imageContainer, { marginBottom: spacing.md, borderRadius: borderRadius.lg, overflow: 'hidden' }]}>
<Image
source={{ uri: img.url || img.preview_url_large || img.preview_url || '' }}
style={styles.image}
/>
</View>
))}
{/* 图片 - 使用 ImageGrid 与帖子同步 */}
{tradeImages.length > 0 && (
<View style={{ paddingHorizontal: responsivePadding, paddingBottom: responsivePadding }}>
<ImageGrid
images={tradeImages}
mode="auto"
gap={imageGap}
borderRadius={imageBorderRadius}
maxDisplayCount={isWideScreen ? 20 : 100}
showMoreOverlay={false}
onImagePress={handleImagePress}
/>
</View>
)}
{/* 内容区右下角属性文字(仅出售商品显示) */}
{item.trade_type === 'sell' && (
<View style={{ paddingHorizontal: spacing.md, paddingBottom: spacing.md }}>
<View style={{ paddingHorizontal: responsivePadding, paddingBottom: responsivePadding }}>
<View style={styles.contentAttrs}>
<Text style={styles.contentAttrText}>{item.brand || '其他'}</Text>
<Text style={styles.contentAttrText}>
@@ -617,6 +657,18 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
</View>
</Pressable>
</Modal>
{/* 图片预览 */}
<ImageGallery
visible={showImageModal}
images={tradeImages.map(img => ({
id: img.id || img.url || String(Math.random()),
url: img.url || '',
}))}
initialIndex={selectedImageIndex}
onClose={() => setShowImageModal(false)}
enableSave
/>
</SafeAreaView>
);
}