refactor(ImageGallery, HomeScreen, PostService, UserStore): streamline post type handling and improve tab navigation
Some checks failed
Frontend CI / build-and-push-web (push) Successful in 2m37s
Frontend CI / ota-android (push) Has been cancelled
Frontend CI / build-android-apk (push) Has been cancelled

- Updated ImageGallery to manage loading states more effectively and prevent unnecessary updates.
- Refactored HomeScreen to remove the 'recommend' post type, simplifying the post type options.
- Adjusted PostService and UserStore to eliminate references to 'recommend', ensuring consistency across the application.
- Enhanced tab navigation in SimpleMobileTabNavigator to optimize rendering and manage mounted tabs more efficiently.
- Improved error handling and loading states in various components for better user experience.
This commit is contained in:
lafay
2026-03-24 05:18:22 +08:00
parent 357c1d4995
commit 48339384d2
9 changed files with 72 additions and 34 deletions

View File

@@ -86,7 +86,9 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
}) => {
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isClosingRef = useRef(false);
const currentIndexRef = useRef(initialIndex);
const [currentIndex, setCurrentIndex] = useState(initialIndex);
currentIndexRef.current = currentIndex;
const [showControls, setShowControls] = useState(true);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
@@ -152,16 +154,19 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
};
}, []);
// 图片变化时重置加载状态和缩放
// 图片变化时重置缩放(加载态在 updateIndex / 打开弹窗时同步设置,避免晚一帧仍显示上一张)
useEffect(() => {
setLoading(true);
setError(false);
resetZoom();
}, [currentImage?.id, resetZoom]);
const updateIndex = useCallback(
(newIndex: number) => {
const clampedIndex = Math.max(0, Math.min(validImages.length - 1, newIndex));
if (clampedIndex === currentIndexRef.current) {
return;
}
setLoading(true);
setError(false);
setCurrentIndex(clampedIndex);
onIndexChange?.(clampedIndex);
},
@@ -412,7 +417,8 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
contentFit="contain"
cachePolicy="disk"
priority="high"
recyclingKey={currentImage.id}
recyclingKey={currentImage.url}
transition={null}
allowDownscaling
onLoadStart={() => setLoading(true)}
onLoad={() => {
@@ -556,11 +562,13 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
zIndex: 5,
backgroundColor: '#000',
},
errorContainer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
errorText: {
color: '#999',

View File

@@ -17,8 +17,8 @@ export interface GetPostsParams {
pageSize?: number;
/** 游标 - 用于无限滚动加载 */
cursor?: string;
/** 帖子类型筛选(可选):recommend, follow, hot, latest */
post_type?: 'recommend' | 'follow' | 'hot' | 'latest';
/** 帖子类型筛选可选follow, hot, latest */
post_type?: 'follow' | 'hot' | 'latest';
/** 社区ID过滤 */
communityId?: string;
/** 作者ID过滤 */

View File

@@ -134,7 +134,7 @@ const prefetchService = new PrefetchService();
* 预取帖子数据
* @param types 帖子类型数组
*/
function prefetchPosts(types: string[] = ['recommend', 'hot']): void {
function prefetchPosts(types: string[] = ['latest', 'hot']): void {
types.forEach((type, index) => {
prefetchService.schedule({
key: `posts:${type}:1`,
@@ -230,7 +230,7 @@ function prefetchOnAppLaunch(): void {
prefetchConversations();
// 中优先级:帖子列表
prefetchPosts(['recommend']);
prefetchPosts(['latest']);
}
/**
@@ -257,7 +257,7 @@ function prefetchMessageScreen(): void {
* 进入首页时预取
*/
function prefetchHomeScreen(): void {
prefetchPosts(['recommend', 'hot', 'latest']);
prefetchPosts(['latest', 'hot', 'follow']);
}
// ==================== React Hook ====================

View File

@@ -178,6 +178,12 @@ export function SimpleMobileTabNavigator() {
const insets = useSafeAreaInsets();
const messageUnreadCount = useTotalUnreadCount();
const [activeTab, setActiveTab] = useState<TabName>('HomeTab');
const [mountedTabs, setMountedTabs] = useState<Record<TabName, boolean>>({
HomeTab: true,
MessageTab: false,
ScheduleTab: false,
ProfileTab: false,
});
// 初始化 MessageManager
useEffect(() => {
@@ -186,12 +192,21 @@ export function SimpleMobileTabNavigator() {
// 处理 Tab 切换
const handleTabPress = useCallback((tabName: TabName) => {
setMountedTabs((prev) => {
if (prev[tabName]) {
return prev;
}
return {
...prev,
[tabName]: true,
};
});
setActiveTab(tabName);
}, []);
// 渲染当前 Tab 内容
const renderTabContent = () => {
switch (activeTab) {
// 根据 Tab 名称渲染对应内容
const renderTabView = (tabName: TabName) => {
switch (tabName) {
case 'HomeTab':
return <HomeScreen />;
case 'MessageTab':
@@ -276,7 +291,24 @@ export function SimpleMobileTabNavigator() {
<View style={styles.container}>
{/* 内容区域 */}
<View style={styles.content}>
{renderTabContent()}
{(Object.keys(mountedTabs) as TabName[]).map((tabName) => {
if (!mountedTabs[tabName]) {
return null;
}
const isActive = activeTab === tabName;
return (
<View
key={tabName}
style={[
styles.tabScreen,
!isActive && styles.tabScreenHidden,
]}
pointerEvents={isActive ? 'auto' : 'none'}
>
{renderTabView(tabName)}
</View>
);
})}
</View>
{/* Tab Bar */}
@@ -334,6 +366,13 @@ const styles = StyleSheet.create({
},
content: {
flex: 1,
position: 'relative',
},
tabScreen: {
...StyleSheet.absoluteFillObject,
},
tabScreenHidden: {
display: 'none',
},
bottomSpacer: {
backgroundColor: 'transparent',

View File

@@ -41,8 +41,8 @@ import { navigationService } from '../../infrastructure/navigation/navigationSer
type NavigationProp = NativeStackNavigationProp<HomeStackParamList, 'Home'> & NativeStackNavigationProp<RootStackParamList>;
const TABS = ['推荐', '关注', '热门', '最新'];
const TAB_ICONS = ['compass-outline', 'account-heart-outline', 'fire', 'clock-outline'];
const TABS = ['最新', '关注', '热门'];
const TAB_ICONS = ['clock-outline', 'account-heart-outline', 'fire'];
const DEFAULT_PAGE_SIZE = 20;
const SWIPE_TRANSLATION_THRESHOLD = 40;
const SWIPE_COOLDOWN_MS = 300;
@@ -51,7 +51,7 @@ const MOBILE_TAB_FLOATING_MARGIN = 12;
const MOBILE_FAB_GAP = 12;
type ViewMode = 'list' | 'grid';
type PostType = 'recommend' | 'follow' | 'hot' | 'latest';
type PostType = 'follow' | 'hot' | 'latest';
export const HomeScreen: React.FC = () => {
const navigation = useNavigation<NavigationProp>();
@@ -106,11 +106,10 @@ export const HomeScreen: React.FC = () => {
// 获取当前 tab 对应的帖子类型
const getPostType = useCallback((): PostType => {
switch (activeIndex) {
case 0: return 'recommend';
case 0: return 'latest';
case 1: return 'follow';
case 2: return 'hot';
case 3: return 'latest';
default: return 'recommend';
default: return 'latest';
}
}, [activeIndex]);

View File

@@ -63,11 +63,6 @@ class PostService {
return response.data;
}
// 获取推荐帖子
async getRecommendedPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
return this.getPosts(page, pageSize, 'recommend');
}
// 获取关注帖子
async getFollowingPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
return this.getPosts(page, pageSize, 'follow');
@@ -282,7 +277,7 @@ class PostService {
/**
* 获取帖子列表(游标分页)
* GET /api/v1/posts
* @param params 游标分页请求参数(包含 post_type 可选:recommend, follow, hot, latest
* @param params 游标分页请求参数(包含 post_type 可选follow, hot, latest
*/
async getPostsCursor(
params: CursorPaginationRequest = {}

View File

@@ -63,7 +63,7 @@ class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
}
async getPosts(
type = 'recommend',
type = 'hot',
page = 1,
pageSize = 20,
forceRefresh = false

View File

@@ -34,7 +34,7 @@ interface UserState {
// 操作
fetchUser: (userId: string) => Promise<User | undefined>;
fetchUserPosts: (userId: string, page?: number) => Promise<Post[]>;
fetchPosts: (type: 'recommend' | 'follow' | 'hot' | 'latest', page?: number) => Promise<PaginatedData<Post>>;
fetchPosts: (type: 'follow' | 'hot' | 'latest', page?: number) => Promise<PaginatedData<Post>>;
fetchNotifications: (type?: string, page?: number) => Promise<Notification[]>;
markNotificationAsRead: (notificationId: string) => Promise<void>;
markAllNotificationsAsRead: () => Promise<void>;
@@ -108,16 +108,13 @@ export const useUserStore = create<UserState>((set, get) => {
},
// 获取帖子列表(首页)
fetchPosts: async (type: 'recommend' | 'follow' | 'hot' | 'latest', page = 1) => {
fetchPosts: async (type: 'follow' | 'hot' | 'latest', page = 1) => {
set({ isLoadingPosts: true });
try {
let response;
switch (type) {
case 'recommend':
response = await postService.getRecommendedPosts(page);
break;
case 'follow':
response = await postService.getFollowingPosts(page);
break;
@@ -332,7 +329,7 @@ export const useUserStore = create<UserState>((set, get) => {
// 刷新所有数据
refreshAll: async () => {
await Promise.all([
get().fetchPosts('recommend', 1),
get().fetchPosts('latest', 1),
get().fetchNotificationBadge(),
]);
},

View File

@@ -482,8 +482,8 @@ export interface CursorPaginationRequest {
direction?: 'forward' | 'backward';
/** 每页数量(默认 20最大 100 */
page_size?: number;
/** 帖子类型筛选(可选):recommend, follow, hot, latest */
post_type?: 'recommend' | 'follow' | 'hot' | 'latest';
/** 帖子类型筛选可选follow, hot, latest */
post_type?: 'follow' | 'hot' | 'latest';
}
/**