feat(pagination): implement cursor-based pagination across the app
Add useCursorPagination hook and update multiple screens and services to use cursor-based pagination for better performance and consistency. - Add useCursorPagination hook with deduplication and caching support - Add cursor pagination types to infrastructure layer - Refactor HomeScreen, PostDetailScreen, SearchScreen for posts/comments - Refactor GroupMembersScreen, JoinGroupScreen for groups/members - Refactor MessageListScreen, NotificationsScreen for messages - Update post, message, group, comment, notification services with cursor endpoints - Add CursorPaginationRequest/Response DTOs - Remove deprecated OPTIMIZATION_DESIGN.md documentation
This commit is contained in:
@@ -32,6 +32,7 @@ import { Post, Comment, VoteResultDTO } from '../../types';
|
||||
import { useUserStore } from '../../stores';
|
||||
import { useCurrentUser } from '../../stores/authStore';
|
||||
import { postService, commentService, uploadService, authService, showPrompt, voteService } from '../../services';
|
||||
import { useCursorPagination } from '../../hooks/useCursorPagination';
|
||||
import { CommentItem, VoteCard } from '../../components/business';
|
||||
import { Avatar, Button, Loading, EmptyState, Text, ImageGallery, ImageGrid, ImageGridItem, AdaptiveLayout } from '../../components/common';
|
||||
import { RootStackParamList } from '../../navigation/types';
|
||||
@@ -76,6 +77,30 @@ export const PostDetailScreen: React.FC = () => {
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// 使用游标分页 Hook 管理评论列表
|
||||
const {
|
||||
items: paginatedComments,
|
||||
isLoading: isCommentsLoading,
|
||||
isRefreshing: isCommentsRefreshing,
|
||||
hasMore: hasMoreComments,
|
||||
loadMore: loadMoreComments,
|
||||
refresh: refreshComments,
|
||||
error: commentsError,
|
||||
} = useCursorPagination(
|
||||
async ({ cursor, pageSize }) => {
|
||||
return await commentService.getPostCommentsCursor(postId, {
|
||||
cursor,
|
||||
page_size: pageSize,
|
||||
});
|
||||
},
|
||||
{ pageSize: 20 }
|
||||
);
|
||||
|
||||
// 同步分页评论到本地状态
|
||||
useEffect(() => {
|
||||
setComments(paginatedComments);
|
||||
}, [paginatedComments]);
|
||||
const [commentText, setCommentText] = useState('');
|
||||
const [showImageModal, setShowImageModal] = useState(false);
|
||||
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
|
||||
@@ -165,9 +190,8 @@ export const PostDetailScreen: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载评论
|
||||
const commentsData = await commentService.getPostComments(postId);
|
||||
setComments(commentsData.list);
|
||||
// 加载评论(使用游标分页刷新)
|
||||
await refreshComments();
|
||||
} catch (error) {
|
||||
console.error('加载帖子详情失败:', error);
|
||||
} finally {
|
||||
@@ -261,12 +285,13 @@ export const PostDetailScreen: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = useCallback(() => {
|
||||
// 下拉刷新 - 同时刷新帖子和评论
|
||||
const onRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
loadPostDetail();
|
||||
await loadPostDetail();
|
||||
await refreshComments();
|
||||
setRefreshing(false);
|
||||
}, [loadPostDetail]);
|
||||
}, [loadPostDetail, refreshComments]);
|
||||
|
||||
const formatDateTime = (dateString?: string | null): string => {
|
||||
if (!dateString) return '';
|
||||
@@ -1375,12 +1400,34 @@ export const PostDetailScreen: React.FC = () => {
|
||||
}}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
refreshing={refreshing || isCommentsRefreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
/>
|
||||
}
|
||||
onEndReached={loadMoreComments}
|
||||
onEndReachedThreshold={0.3}
|
||||
ListFooterComponent={
|
||||
isCommentsLoading ? (
|
||||
<View style={styles.commentsLoadingFooter}>
|
||||
<Loading size="sm" />
|
||||
</View>
|
||||
) : hasMoreComments ? (
|
||||
<TouchableOpacity
|
||||
style={styles.loadMoreButton}
|
||||
onPress={loadMoreComments}
|
||||
>
|
||||
<Text variant="caption" color={colors.primary.main}>
|
||||
加载更多评论
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : comments.length > 0 ? (
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.noMoreComments}>
|
||||
没有更多评论了
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1433,7 +1480,7 @@ export const PostDetailScreen: React.FC = () => {
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
refreshing={refreshing || isCommentsRefreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
@@ -1465,7 +1512,7 @@ export const PostDetailScreen: React.FC = () => {
|
||||
// 移动端单栏布局
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||||
<KeyboardAvoidingView
|
||||
<KeyboardAvoidingView
|
||||
style={styles.flex}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={88}
|
||||
@@ -1486,12 +1533,34 @@ export const PostDetailScreen: React.FC = () => {
|
||||
}}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
refreshing={refreshing || isCommentsRefreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[colors.primary.main]}
|
||||
tintColor={colors.primary.main}
|
||||
/>
|
||||
}
|
||||
onEndReached={loadMoreComments}
|
||||
onEndReachedThreshold={0.3}
|
||||
ListFooterComponent={
|
||||
isCommentsLoading ? (
|
||||
<View style={styles.commentsLoadingFooter}>
|
||||
<Loading size="sm" />
|
||||
</View>
|
||||
) : hasMoreComments ? (
|
||||
<TouchableOpacity
|
||||
style={styles.loadMoreButton}
|
||||
onPress={loadMoreComments}
|
||||
>
|
||||
<Text variant="caption" color={colors.primary.main}>
|
||||
加载更多评论
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : comments.length > 0 ? (
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.noMoreComments}>
|
||||
没有更多评论了
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 评论输入框 - 跟随键盘 */}
|
||||
@@ -1905,4 +1974,22 @@ const styles = StyleSheet.create({
|
||||
marginTop: spacing.md,
|
||||
textAlign: 'center',
|
||||
},
|
||||
// 评论加载更多样式
|
||||
commentsLoadingFooter: {
|
||||
paddingVertical: spacing.md,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
loadMoreButton: {
|
||||
paddingVertical: spacing.md,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.background.paper,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: colors.divider,
|
||||
},
|
||||
noMoreComments: {
|
||||
textAlign: 'center',
|
||||
paddingVertical: spacing.md,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user