import { Asset } from 'expo-asset'; import { useRouter } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { FlatList, InteractionManager, PixelRatio, Platform, RefreshControl, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; // 懒加载组件 const ChatSvg = React.lazy(() => import('@/assets/icons/svg/chat.svg')); const AskNavbar = React.lazy(() => import('@/components/layout/ask')); const UploaderProgress = React.lazy(() => import('@/components/file-upload/upload-progress/uploader-progress')); const SkeletonItem = React.lazy(() => import('@/components/memo/SkeletonItem')); const ErrorBoundary = React.lazy(() => import('@/components/common/ErrorBoundary')); // 类型定义 import { useUploadManager } from '@/hooks/useUploadManager'; import { getCachedData, prefetchChatDetail, prefetchChats } from '@/lib/prefetch'; import { fetchApi } from '@/lib/server-api-util'; import { Chat } from '@/types/ask'; import { useTranslation } from 'react-i18next'; // 预加载资源 const preloadAssets = async () => { try { await Asset.loadAsync([ require('@/assets/icons/svg/chat.svg'), ]); } catch (error) { // console.error('资源预加载失败:', error); } }; // 骨架屏占位 const SkeletonList = () => ( {Array(5).fill(0).map((_, index) => ( ))} ); const MemoList = () => { const router = useRouter(); const insets = useSafeAreaInsets(); const [isMounted, setIsMounted] = useState(false); const [historyList, setHistoryList] = useState([]); const [isRefreshing, setIsRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const flatListRef = useRef(null); const { t } = useTranslation(); // 从缓存或API获取数据 const fetchHistoryList = useCallback(async (forceRefresh = false) => { try { setIsLoading(true); // 先检查缓存 const cachedData = getCachedData('/chats'); if (cachedData && !forceRefresh) { setHistoryList(cachedData); } // 总是从服务器获取最新数据 const data = await fetchApi('/chats'); setHistoryList(data); // 预加载第一个聊天的详情 if (data.length > 0) { InteractionManager.runAfterInteractions(() => { prefetchChatDetail(data[0].session_id).catch(console.error); }); } return data; } catch (error) { console.error('获取历史记录失败:', error); throw error; } finally { setIsLoading(false); setIsRefreshing(false); } }, []); // 处理下拉刷新 const handleRefresh = useCallback(() => { if (isRefreshing) return; setIsRefreshing(true); fetchHistoryList(true).finally(() => { setIsRefreshing(false); }); }, [fetchHistoryList, isRefreshing]); // 处理聊天项点击 const handleMemoPress = useCallback((item: Chat) => { router.push({ pathname: '/ask', params: { sessionId: item.session_id }, }); }, [router]); // 初始加载和预加载 useEffect(() => { let isActive = true; const initialize = async () => { try { // 并行预加载资源和数据 await Promise.all([ preloadAssets(), prefetchChats().then((data) => { if (isActive && data) { setHistoryList(data as Chat[]); } }), ]); // 主数据加载 await fetchHistoryList(); } catch (error) { console.error('初始化失败:', error); } finally { if (isActive) { setIsMounted(true); // 延迟隐藏启动画面 setTimeout(SplashScreen.hideAsync, 500); } } }; initialize(); return () => { isActive = false; }; }, [fetchHistoryList]); // 渲染列表项 const renderItem = useCallback(({ item }: { item: Chat }) => ( handleMemoPress(item)} activeOpacity={0.7} > }> {item.title || t('ask:ask.unNamed')} {item.latest_message?.content?.text || t('ask:ask.noMessage')} ), [handleMemoPress]); // 渲染列表头部 const renderHeader = useCallback(() => ( {t('ask:ask.memoList')} {/* 上传进度 */} ), [insets.top]); // 上传进度组件 const UploadProgressSection = () => { const { progressInfo, uploadSessionStartTime } = useUploadManager(); if (!uploadSessionStartTime || progressInfo.total <= 0) { return null; } return ( ); }; // 空状态 const renderEmptyComponent = useCallback(() => ( {t('ask:ask.noChat')} {isRefreshing ? t('ask:ask.loading') : t('ask:ask.refresh')} ), [handleRefresh, isRefreshing]); // 如果组件未完全加载,显示骨架屏 if (!isMounted) { return ( ); } return ( item.session_id} ListHeaderComponent={renderHeader} ListEmptyComponent={!isLoading ? renderEmptyComponent : null} refreshControl={ } initialNumToRender={10} maxToRenderPerBatch={5} updateCellsBatchingPeriod={50} windowSize={11} // 5 screens in each direction (5 + 1 + 5) removeClippedSubviews={Platform.OS === 'android'} getItemLayout={(data, index) => ({ length: 80, offset: 80 * index, index, })} contentContainerStyle={styles.listContent} ItemSeparatorComponent={() => } /> {/* 底部导航栏 */} ); }; // 使用React.memo优化组件 const MemoizedMemoList = React.memo(MemoList); export default MemoizedMemoList; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', }, headerContainer: { paddingBottom: 16, backgroundColor: '#fff', }, title: { fontSize: 20, fontWeight: 'bold', color: '#4C320C', textAlign: 'center', marginBottom: 16, }, listContent: { paddingBottom: Platform.select({ ios: 30, android: 20, }), }, skeletonContainer: { flex: 1, backgroundColor: '#fff', paddingTop: 20, }, memoItem: { flexDirection: 'row', alignItems: 'center', padding: 16, backgroundColor: '#fff', }, placeholderIcon: { width: 48, height: 48, backgroundColor: '#f0f0f0', borderRadius: 24, }, memoContent: { flex: 1, marginLeft: 12, justifyContent: 'center', }, memoTitle: { fontSize: 16, fontWeight: '500', color: '#4C320C', marginBottom: 4, }, memoSubtitle: { fontSize: 14, color: '#AC7E35', }, separator: { height: 1 / PixelRatio.get(), backgroundColor: '#f0f0f0', marginLeft: 60, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 40, }, emptyText: { fontSize: 16, color: '#999', marginBottom: 20, }, refreshButton: { backgroundColor: '#FFB645', paddingHorizontal: 24, paddingVertical: 10, borderRadius: 20, }, refreshText: { color: '#fff', fontSize: 14, fontWeight: '500', }, });