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:
lafay
2026-03-20 23:00:27 +08:00
parent a005fb0a15
commit 8a0aea1c59
20 changed files with 2464 additions and 2166 deletions

View File

@@ -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,
},
});