enhance: 优化页面切换效果

This commit is contained in:
Junhui Chen 2025-08-03 13:58:45 +08:00
parent 4675043d85
commit b7d9186570
6 changed files with 346 additions and 200 deletions

View File

@ -12,6 +12,8 @@ import * as SecureStore from 'expo-secure-store';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import { TransitionPresets } from '@react-navigation/bottom-tabs';
import AskNavbar from '@/components/layout/ask';
interface PollingData { interface PollingData {
title: string; title: string;
@ -19,6 +21,7 @@ interface PollingData {
content: string; content: string;
extra: any; extra: any;
} }
export default function TabLayout() { export default function TabLayout() {
const { t } = useTranslation(); const { t } = useTranslation();
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
@ -171,6 +174,7 @@ export default function TabLayout() {
}, [token]); }, [token]);
return ( return (
<>
<Tabs <Tabs
screenOptions={{ screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint, tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
@ -247,7 +251,8 @@ export default function TabLayout() {
title: 'ask', title: 'ask',
tabBarButton: () => null, // 隐藏底部标签栏 tabBarButton: () => null, // 隐藏底部标签栏
headerShown: false, // 隐藏导航栏 headerShown: false, // 隐藏导航栏
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
...TransitionPresets.ShiftTransition,
}} }}
/> />
{/* memo list */} {/* memo list */}
@ -257,7 +262,8 @@ export default function TabLayout() {
title: 'memo-list', title: 'memo-list',
tabBarButton: () => null, // 隐藏底部标签栏 tabBarButton: () => null, // 隐藏底部标签栏
headerShown: false, // 隐藏导航栏 headerShown: false, // 隐藏导航栏
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
...TransitionPresets.ShiftTransition,
}} }}
/> />
{/* owner */} {/* owner */}
@ -267,7 +273,8 @@ export default function TabLayout() {
title: 'owner', title: 'owner',
tabBarButton: () => null, // 隐藏底部标签栏 tabBarButton: () => null, // 隐藏底部标签栏
headerShown: false, // 隐藏导航栏 headerShown: false, // 隐藏导航栏
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
...TransitionPresets.ShiftTransition,
}} }}
/> />
{/* 排行榜 */} {/* 排行榜 */}
@ -358,5 +365,7 @@ export default function TabLayout() {
}} }}
/> />
</Tabs > </Tabs >
<AskNavbar />
</>
); );
} }

View File

@ -9,7 +9,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import ChatSvg from '@/assets/icons/svg/chat.svg'; import ChatSvg from '@/assets/icons/svg/chat.svg';
import ErrorBoundary from '@/components/common/ErrorBoundary'; import ErrorBoundary from '@/components/common/ErrorBoundary';
import UploaderProgress from '@/components/file-upload/upload-progress/uploader-progress'; import UploaderProgress from '@/components/file-upload/upload-progress/uploader-progress';
import AskNavbar from '@/components/layout/ask';
import SkeletonItem from '@/components/memo/SkeletonItem'; import SkeletonItem from '@/components/memo/SkeletonItem';
// 类型定义 // 类型定义
@ -176,7 +176,7 @@ const MemoList = () => {
</Text> </Text>
{/* 上传进度 */} {/* 上传进度 */}
<UploadProgressSection /> {/* <UploadProgressSection /> */}
</View> </View>
), [insets.top]); ), [insets.top]);
@ -220,7 +220,6 @@ const MemoList = () => {
return ( return (
<View style={[styles.container, { paddingTop: insets.top }]}> <View style={[styles.container, { paddingTop: insets.top }]}>
<SkeletonList /> <SkeletonList />
<AskNavbar />
</View> </View>
); );
} }
@ -257,9 +256,6 @@ const MemoList = () => {
contentContainerStyle={styles.listContent} contentContainerStyle={styles.listContent}
ItemSeparatorComponent={() => <View style={styles.separator} />} ItemSeparatorComponent={() => <View style={styles.separator} />}
/> />
{/* 底部导航栏 */}
<AskNavbar />
</View> </View>
</ErrorBoundary> </ErrorBoundary>
); );

View File

@ -1,9 +1,10 @@
import ConversationsSvg from '@/assets/icons/svg/conversations.svg'; import ConversationsSvg from '@/assets/icons/svg/conversations.svg';
import StoriesSvg from '@/assets/icons/svg/stories.svg'; import StoriesSvg from '@/assets/icons/svg/stories.svg';
import AskNavbar from '@/components/layout/ask';
import CreateCountComponent from '@/components/owner/createCount'; import CreateCountComponent from '@/components/owner/createCount';
import MemberCard from '@/components/owner/rights/memberCard'; import MemberCard from '@/components/owner/rights/memberCard';
import SettingModal from '@/components/owner/setting'; import SettingModal from '@/components/owner/setting';
import SkeletonOwner from '@/components/owner/SkeletonOwner';
import UserInfo from '@/components/owner/userName'; import UserInfo from '@/components/owner/userName';
import { checkAuthStatus } from '@/lib/auth'; import { checkAuthStatus } from '@/lib/auth';
import { fetchApi } from '@/lib/server-api-util'; import { fetchApi } from '@/lib/server-api-util';
@ -20,6 +21,9 @@ export default function OwnerPage() {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
// 添加页面挂载状态
const [isMounted, setIsMounted] = useState(false);
useEffect(() => { useEffect(() => {
const checkAuth = async () => { const checkAuth = async () => {
const authStatus = await checkAuthStatus(router); const authStatus = await checkAuthStatus(router);
@ -47,11 +51,17 @@ export default function OwnerPage() {
// 获取用户信息 // 获取用户信息
const [userInfoDetails, setUserInfoDetails] = useState<UserInfoDetails>({} as UserInfoDetails); const [userInfoDetails, setUserInfoDetails] = useState<UserInfoDetails>({} as UserInfoDetails);
const getUserInfo = () => {
// 优化getUserInfo函数添加挂载状态检查
const getUserInfo = useCallback(() => {
fetchApi("/membership/personal-center-info").then((res) => { fetchApi("/membership/personal-center-info").then((res) => {
// 只有在组件挂载时才更新状态
if (isMounted) {
setUserInfoDetails(res as UserInfoDetails); setUserInfoDetails(res as UserInfoDetails);
})
} }
})
}, [isMounted]);
// 设计轮询获取数量统计 // 设计轮询获取数量统计
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@ -70,8 +80,35 @@ export default function OwnerPage() {
// 初始化获取用户信息 // 初始化获取用户信息
useEffect(() => { useEffect(() => {
getUserInfo(); let isActive = true;
}, []);
const initialize = async () => {
try {
await getUserInfo();
} catch (error) {
console.error('初始化失败:', error);
} finally {
if (isActive) {
setIsMounted(true);
}
}
};
initialize();
return () => {
isActive = false;
};
}, [getUserInfo]);
// 如果组件未完全加载,显示骨架屏
if (!isMounted) {
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
<SkeletonOwner />
</View>
);
}
return ( return (
<View style={[styles.container, { paddingTop: insets.top }]}> <View style={[styles.container, { paddingTop: insets.top }]}>
@ -112,9 +149,6 @@ export default function OwnerPage() {
{modalVisible && ( {modalVisible && (
<SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} /> <SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} />
)} )}
{/* 导航栏 */}
<AskNavbar />
</View> </View>
); );
} }

View File

@ -131,6 +131,11 @@ const AskNavbar = () => {
} }
}), [width]); }), [width]);
// 如果当前路径是ask页面则不渲染导航栏
if (pathname != '/memo-list' && pathname != '/owner') {
return null;
}
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Image source={require('@/assets/images/png/owner/ask.png')} style={{ width: width, height: 80, resizeMode: 'cover' }} /> <Image source={require('@/assets/images/png/owner/ask.png')} style={{ width: width, height: 80, resizeMode: 'cover' }} />

View File

@ -0,0 +1,92 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
// 骨架屏占位组件
const SkeletonItem = () => (
<View style={styles.skeletonItem} />
);
const SkeletonOwner = () => {
const insets = useSafeAreaInsets();
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
{/* 用户信息骨架屏 */}
<View style={styles.section}>
<View style={styles.userInfoHeader}>
<SkeletonItem />
<View style={styles.userInfoTextContainer}>
<SkeletonItem />
<SkeletonItem />
</View>
</View>
<View style={styles.userInfoStats}>
<SkeletonItem />
<SkeletonItem />
</View>
</View>
{/* 会员卡骨架屏 */}
<View style={styles.section}>
<SkeletonItem />
</View>
{/* 作品数据骨架屏 */}
<View style={styles.section}>
<View style={styles.countContainer}>
<SkeletonItem />
<SkeletonItem />
</View>
</View>
{/* 排行榜骨架屏 */}
<View style={styles.section}>
<SkeletonItem />
<View style={styles.rankingList}>
{Array(3).fill(0).map((_, index) => (
<SkeletonItem key={`ranking-${index}`} />
))}
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
section: {
marginBottom: 16,
},
skeletonItem: {
backgroundColor: '#E1E1E1',
borderRadius: 8,
overflow: 'hidden',
},
userInfoHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
},
userInfoTextContainer: {
flex: 1,
marginLeft: 16,
},
userInfoStats: {
flexDirection: 'row',
justifyContent: 'space-around',
},
countContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 16,
},
rankingList: {
marginTop: 16,
},
});
export default SkeletonOwner;

View File

@ -1,3 +1,4 @@
import React, { useMemo } from "react";
import { StyleProp, StyleSheet, View, ViewStyle } from "react-native"; import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
import { ThemedText } from "../ThemedText"; import { ThemedText } from "../ThemedText";
@ -7,9 +8,17 @@ interface CreateCountProps {
number: number; number: number;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
} }
const CreateCountComponent = (props: CreateCountProps) => {
// 使用React.memo优化组件避免不必要的重渲染
const CreateCountComponent = React.memo((props: CreateCountProps) => {
// 预计算样式以提高性能
const containerStyle = useMemo(() => [
styles.container,
props.style
], [props.style]);
return ( return (
<View style={styles.container}> <View style={containerStyle}>
<ThemedText style={styles.number} className="!text-textSecondary">{props.number}</ThemedText> <ThemedText style={styles.number} className="!text-textSecondary">{props.number}</ThemedText>
<View style={styles.header}> <View style={styles.header}>
<View className="mt-1"> <View className="mt-1">
@ -19,7 +28,7 @@ const CreateCountComponent = (props: CreateCountProps) => {
</View> </View>
</View> </View>
); );
}; });
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -39,7 +48,7 @@ const styles = StyleSheet.create({
}, },
shadowOpacity: 0.25, shadowOpacity: 0.25,
shadowRadius: 3.84, shadowRadius: 3.84,
elevation: 5, // elevation: 1,
}, },
header: { header: {
width: "100%", width: "100%",
@ -63,5 +72,6 @@ const styles = StyleSheet.create({
textAlign: "center", textAlign: "center",
color: "#4C320C" color: "#4C320C"
}, },
}) });
export default CreateCountComponent; export default CreateCountComponent;