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:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 通知页 NotificationsScreen
|
||||
* 胡萝卜BBS - 系统消息列表
|
||||
* 使用新的系统消息API
|
||||
* 【游标分页】使用 messageService.getSystemMessagesCursor
|
||||
* 支持响应式布局
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@ import { messageService } from '../../services/messageService';
|
||||
import { commentService } from '../../services/commentService';
|
||||
import { SystemMessageItem } from '../../components/business';
|
||||
import { Text, EmptyState, ResponsiveContainer } from '../../components/common';
|
||||
import { useCursorPagination } from '../../hooks/useCursorPagination';
|
||||
import { RootStackParamList } from '../../navigation/types';
|
||||
import { useMessageManagerSystemUnreadCount, useUserStore } from '../../stores';
|
||||
|
||||
@@ -70,14 +71,32 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
// Web端使用更大的容器宽度
|
||||
const containerMaxWidth = isDesktop ? 1200 : isTablet ? 1000 : 900;
|
||||
|
||||
const [messages, setMessages] = useState<SystemMessageResponse[]>([]);
|
||||
const [activeType, setActiveType] = useState('all');
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
|
||||
// 【游标分页】使用 useCursorPagination hook 获取系统消息列表
|
||||
const {
|
||||
items: messages,
|
||||
isLoading,
|
||||
isRefreshing,
|
||||
hasMore,
|
||||
loadMore,
|
||||
refresh,
|
||||
error: paginationError,
|
||||
} = useCursorPagination(
|
||||
async ({ cursor, pageSize }) => {
|
||||
return await messageService.getSystemMessagesCursor({
|
||||
cursor,
|
||||
page_size: pageSize,
|
||||
});
|
||||
},
|
||||
{ pageSize: 20 }
|
||||
);
|
||||
|
||||
// 本地刷新状态(仅用于下拉刷新的UI显示)
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
|
||||
// 同一 flag 只要有人审批过,就将待处理消息同步展示为已处理状态
|
||||
const displayMessages = useMemo(() => {
|
||||
const reviewedByFlag = new Map<string, SystemMessageResponse>();
|
||||
@@ -116,21 +135,6 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
});
|
||||
}, [messages]);
|
||||
|
||||
// 获取系统消息数据
|
||||
const fetchMessages = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await messageService.getSystemMessages(50, 1);
|
||||
// 添加防御性检查,确保 messages 数组存在
|
||||
setMessages(response.messages || []);
|
||||
setHasMore(response.has_more ?? false);
|
||||
} catch (error) {
|
||||
console.error('获取系统消息失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 获取未读数
|
||||
const fetchUnreadCount = useCallback(async () => {
|
||||
try {
|
||||
@@ -145,27 +149,25 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
const handleMarkAllRead = useCallback(async () => {
|
||||
try {
|
||||
await messageService.markAllSystemMessagesRead();
|
||||
setMessages(prev => prev.map(m => ({ ...m, is_read: true })));
|
||||
// 刷新消息列表
|
||||
await refresh();
|
||||
setUnreadCount(0);
|
||||
setSystemUnreadCount(0);
|
||||
// 同步更新全局 TabBar 红点
|
||||
fetchMessageUnreadCount();
|
||||
// 刷新消息列表
|
||||
fetchMessages();
|
||||
} catch (error) {
|
||||
console.error('一键已读失败:', error);
|
||||
}
|
||||
}, [fetchMessages, fetchMessageUnreadCount, setSystemUnreadCount]);
|
||||
}, [refresh, fetchMessageUnreadCount, setSystemUnreadCount]);
|
||||
|
||||
// 页面加载和获得焦点时刷新,并自动标记所有消息为已读
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
fetchMessages();
|
||||
fetchUnreadCount();
|
||||
// 进入界面自动标记所有消息为已读
|
||||
handleMarkAllRead();
|
||||
}
|
||||
}, [isFocused, fetchMessages, fetchUnreadCount, handleMarkAllRead]);
|
||||
}, [isFocused, fetchUnreadCount, handleMarkAllRead]);
|
||||
|
||||
// 屏幕失去焦点时,如果有 onBack 回调则调用它(用于内嵌模式)
|
||||
useEffect(() => {
|
||||
@@ -190,29 +192,17 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
// 下拉刷新
|
||||
const onRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
await Promise.all([fetchMessages(), fetchUnreadCount()]);
|
||||
await Promise.all([refresh(), fetchUnreadCount()]);
|
||||
setRefreshing(false);
|
||||
}, [fetchMessages, fetchUnreadCount]);
|
||||
}, [refresh, fetchUnreadCount]);
|
||||
|
||||
// 加载更多
|
||||
const loadMore = useCallback(async () => {
|
||||
if (loadingMore || !hasMore || messages.length === 0) return;
|
||||
|
||||
try {
|
||||
setLoadingMore(true);
|
||||
// 使用时间戳或seq作为游标分页(后端使用page分页)
|
||||
const nextPage = Math.floor((messages.length / 20)) + 1;
|
||||
const response = await messageService.getSystemMessages(20, nextPage);
|
||||
// 添加防御性检查
|
||||
const newMessages = response.messages || [];
|
||||
setMessages(prev => [...prev, ...newMessages]);
|
||||
setHasMore(response.has_more ?? false);
|
||||
} catch (error) {
|
||||
console.error('加载更多失败:', error);
|
||||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
}, [loadingMore, hasMore, messages]);
|
||||
const onEndReached = useCallback(async () => {
|
||||
if (isLoading || loadingMore || !hasMore) return;
|
||||
setLoadingMore(true);
|
||||
await loadMore();
|
||||
setLoadingMore(false);
|
||||
}, [isLoading, loadingMore, hasMore, loadMore]);
|
||||
|
||||
// 标记单条消息已读并处理导航
|
||||
const extractPostIdFromActionUrl = (actionUrl?: string): string | null => {
|
||||
@@ -262,9 +252,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
const messageId = String(message.id);
|
||||
const wasUnread = message.is_read !== true;
|
||||
await messageService.markSystemMessageRead(messageId);
|
||||
setMessages(prev =>
|
||||
prev.map(m => (String(m.id) === messageId ? { ...m, is_read: true } : m))
|
||||
);
|
||||
// 【游标分页】不再直接修改 messages 状态,而是通过刷新获取最新数据
|
||||
if (wasUnread) {
|
||||
setUnreadCount(prev => Math.max(0, prev - 1));
|
||||
decrementSystemUnreadCount(1);
|
||||
@@ -431,7 +419,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
</View>
|
||||
|
||||
{/* 消息列表 */}
|
||||
{loading ? (
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
@@ -444,7 +432,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
showsVerticalScrollIndicator={false}
|
||||
ListEmptyComponent={renderEmpty}
|
||||
ListFooterComponent={renderFooter}
|
||||
onEndReached={loadMore}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={0.3}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
@@ -505,7 +493,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
</View>
|
||||
|
||||
{/* 消息列表 */}
|
||||
{loading ? (
|
||||
{isLoading && messages.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
@@ -518,7 +506,7 @@ export const NotificationsScreen: React.FC<{ onBack?: () => void }> = ({ onBack
|
||||
showsVerticalScrollIndicator={false}
|
||||
ListEmptyComponent={renderEmpty}
|
||||
ListFooterComponent={renderFooter}
|
||||
onEndReached={loadMore}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={0.3}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
|
||||
Reference in New Issue
Block a user