This commit implements a major architectural refactor to improve modularity, type safety, and maintainability across the codebase.
Key changes include:
- **Responsive System Refactor**: Migrated from a monolithic `useResponsive` hook to a set of specialized, granular hooks (`useBreakpoint`, `useOrientation`, `usePlatform`, etc.) located in `src/presentation/hooks/responsive`. This reduces unnecessary re-renders and improves developer experience.
- **Core Service Restructuring**: Introduced a new directory structure for services, including dedicated folders for `datasources`, `mappers`, and domain-specific types (`message`, `post`).
- **Data Layer Improvements**:
- Centralized JSON parsing logic in `src/database/core/jsonUtils.ts`.
- Cleaned up repository implementations by removing redundant local utility functions.
- Refactored `MessageMapper` to use the new centralized JSON utility.
- **Type System Cleanup**:
- Decomposed the large `src/types/dto.ts` into a modular `src/types/dto/` directory.
- Simplified `src/types/index.ts` and introduced backward-compatible aliases for core entities.
- **Utility Consolidation**:
- Created a centralized `src/utils/formatTime.ts` to replace fragmented date formatting logic across various screens and components.
- Removed deprecated responsive utility files in favor of the new hook-based system.
- **Service Logic Refinement**: Refactored `ApiClient` and `WebSocketService` to use a centralized `showVerificationModal` service, removing duplicated state management for verification prompts.
674 lines
20 KiB
TypeScript
674 lines
20 KiB
TypeScript
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';
|
||
|
||
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<TradeItemDetailDTO | null>(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 <Loading fullScreen />;
|
||
}
|
||
|
||
if (!item) {
|
||
return (
|
||
<SafeAreaView style={styles.container}>
|
||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||
<Text style={{ color: colors.text.hint }}>商品不存在或已删除</Text>
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||
<View style={styles.header}>
|
||
<View style={styles.headerLeft}>
|
||
<AppBackButton onPress={() => router.back()} />
|
||
</View>
|
||
<Text style={styles.headerTitle}>商品详情</Text>
|
||
<View style={styles.headerRight} />
|
||
</View>
|
||
|
||
<ScrollView>
|
||
<View style={[styles.content, { paddingHorizontal: responsivePadding, paddingTop: responsivePadding }]}>
|
||
{/* 卖家信息头部 */}
|
||
{item.author && (
|
||
<View>
|
||
<View style={styles.sellerHeader}>
|
||
{item.author.avatar ? (
|
||
<Image source={{ uri: item.author.avatar }} style={styles.sellerAvatar} />
|
||
) : (
|
||
<View style={[styles.sellerAvatar, { backgroundColor: colors.primary.main + '33', alignItems: 'center', justifyContent: 'center' }]}>
|
||
<MaterialCommunityIcons name="account" size={18} color={colors.primary.main} />
|
||
</View>
|
||
)}
|
||
<View style={styles.sellerInfo}>
|
||
<View style={styles.sellerNameRow}>
|
||
<Text style={styles.sellerName} numberOfLines={1}>
|
||
{item.author.nickname && item.author.nickname.length > 10
|
||
? `${item.author.nickname.slice(0, 10)}...`
|
||
: item.author.nickname || '匿名'}
|
||
</Text>
|
||
{(item.author?.credit_score || 0) > 80 && (
|
||
<View style={styles.sellerCreditBadge}>
|
||
<Text style={styles.sellerCreditText}>卖家信用优秀</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
{item.price != null ? (
|
||
<Text style={styles.priceInHeader}>
|
||
价格:<Text style={styles.priceSymbol}>¥</Text>{item.price % 1 === 0 ? item.price.toFixed(0) : item.price.toFixed(2)}
|
||
</Text>
|
||
) : (
|
||
<Text style={styles.priceInHeader}>价格:面议</Text>
|
||
)}
|
||
</View>
|
||
|
||
</View>
|
||
)}
|
||
|
||
{/* 标题 - 与帖子同步 */}
|
||
<View style={styles.titleRow}>
|
||
<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,
|
||
{
|
||
fontSize: isDesktop ? fontSizes.xl : isTablet ? fontSizes.lg : fontSizes.lg,
|
||
lineHeight: isDesktop ? 32 : isTablet ? 30 : 28,
|
||
},
|
||
]}
|
||
>
|
||
{item.content}
|
||
</Text>
|
||
)}
|
||
</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: responsivePadding, paddingBottom: responsivePadding }}>
|
||
<View style={styles.contentAttrs}>
|
||
<Text style={styles.contentAttrText}>品牌:{item.brand || '其他'}</Text>
|
||
<Text style={styles.contentAttrText}>
|
||
成色:{TRADE_CONDITION_MAP[item.condition as keyof typeof TRADE_CONDITION_MAP] || item.condition || '未知'}
|
||
</Text>
|
||
<Text style={styles.contentAttrText}>功能状态:{item.function_status || '正常使用'}</Text>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</ScrollView>
|
||
|
||
{/* 底部操作栏 */}
|
||
<View style={styles.bottomBar}>
|
||
{isOwner ? (
|
||
/* 卖家管理按钮 */
|
||
<>
|
||
<View style={{ flex: 1 }} />
|
||
<Pressable style={styles.manageButton} onPress={() => setManageModalVisible(true)}>
|
||
<MaterialCommunityIcons name="cog-outline" size={18} color={colors.text.primary} />
|
||
<Text style={styles.manageButtonText}>管理</Text>
|
||
</Pressable>
|
||
</>
|
||
) : (
|
||
<>
|
||
{!isOwner && item.status === 'active' && (
|
||
<>
|
||
<View style={{ flex: 1 }} />
|
||
<Pressable style={styles.chatButton} onPress={handleChat}>
|
||
<MaterialCommunityIcons name="chat-outline" size={18} color={colors.text.inverse} />
|
||
<Text style={styles.chatButtonText}>聊一聊</Text>
|
||
</Pressable>
|
||
</>
|
||
)}
|
||
{!isOwner && item.status !== 'active' && (
|
||
<View style={{ flex: 1, alignItems: 'flex-end' }}>
|
||
<View style={[styles.chatButton, styles.chatButtonDisabled]}>
|
||
<Text style={styles.chatButtonText}>
|
||
{item.status === 'sold' ? '已售出' : item.status === 'bought' ? '已收得' : '已下架'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</>
|
||
)}
|
||
</View>
|
||
|
||
{/* 管理弹窗 */}
|
||
<Modal
|
||
visible={manageModalVisible}
|
||
transparent
|
||
animationType="slide"
|
||
onRequestClose={() => setManageModalVisible(false)}
|
||
>
|
||
<Pressable style={styles.modalOverlay} onPress={() => setManageModalVisible(false)}>
|
||
<View style={styles.modalContent}>
|
||
<Pressable style={styles.modalItem} onPress={handleEdit}>
|
||
<Text style={styles.modalItemText}>编辑</Text>
|
||
</Pressable>
|
||
<Pressable style={[styles.modalItem, { borderBottomWidth: 0 }]} onPress={handleDelete}>
|
||
<Text style={[styles.modalItemText, { color: colors.error.main }]}>删除</Text>
|
||
</Pressable>
|
||
<Pressable style={styles.modalCancel} onPress={() => setManageModalVisible(false)}>
|
||
<Text style={styles.modalCancelText}>取消</Text>
|
||
</Pressable>
|
||
</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>
|
||
);
|
||
} |