2025-08-04 15:26:39 +08:00

258 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ConversationsSvg from '@/assets/icons/svg/conversations.svg';
import StoriesSvg from '@/assets/icons/svg/stories.svg';
import CarouselComponent from '@/components/owner/carousel';
import CreateCountComponent from '@/components/owner/createCount';
import Ranking from '@/components/owner/ranking';
import MemberCard from '@/components/owner/rights/memberCard';
import SkeletonOwner from '@/components/owner/SkeletonOwner';
import UserInfo from '@/components/owner/userName';
import { checkAuthStatus } from '@/lib/auth';
import { fetchApi } from '@/lib/server-api-util';
import { CountData, UserInfoDetails } from '@/types/user';
import { useFocusEffect, useRouter } from 'expo-router';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function OwnerPage() {
const insets = useSafeAreaInsets();
const { t } = useTranslation();
const router = useRouter();
// 添加页面挂载状态
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
const checkAuth = async () => {
const authStatus = await checkAuthStatus(router);
if (!authStatus) {
router.push('/login');
}
};
checkAuth();
}, [router]);
// 数据统计
const [countData, setCountData] = useState<CountData>({} as CountData);
// 获取数量统计 --- 需要轮询
const getCountData = () => {
fetchApi("/material/statistics").then((res) => {
setCountData(res as CountData);
});
}
// 获取用户信息
const [userInfoDetails, setUserInfoDetails] = useState<UserInfoDetails>({} as UserInfoDetails);
// 优化getUserInfo函数添加挂载状态检查
const getUserInfo = useCallback(() => {
fetchApi("/membership/personal-center-info").then((res) => {
// 只有在组件挂载时才更新状态
if (isMounted) {
setUserInfoDetails(res as UserInfoDetails);
}
})
}, [isMounted]);
// 设计轮询获取数量统计
useFocusEffect(
useCallback(() => {
// 当页面获取焦点时开始轮询
const interval = setInterval(() => {
getCountData();
}, 5000);
// 立即执行一次
getCountData();
// 当页面失去焦点时清除定时器
return () => clearInterval(interval);
}, []) // 空依赖数组,因为 getCountData 是稳定的
);
// 初始化获取用户信息
useEffect(() => {
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 (
<View style={[styles.container, { paddingTop: insets.top }]}>
<FlatList
data={[]} // 空数据,因为我们只需要渲染一次
renderItem={null} // 不需要渲染项目
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false}
ListHeaderComponent={
<View style={{ gap: 16 }}>
{/* 用户信息 */}
<UserInfo userInfo={userInfoDetails} />
{/* 会员卡 */}
<MemberCard pro={userInfoDetails?.membership_level} />
{/* 分类 */}
<View style={{ marginHorizontal: -16, marginBottom: -16 }}>
<CarouselComponent data={userInfoDetails?.material_counter} />
</View>
{/* 作品数据 */}
<View className='flex flex-row justify-between gap-[1rem]'>
<CreateCountComponent title={t("generalSetting.storiesCreated", { ns: "personal" })} icon={<StoriesSvg width={30} height={30} />} number={userInfoDetails.stories_count} />
<CreateCountComponent title={t("generalSetting.conversationsWithMemo", { ns: "personal" })} icon={<ConversationsSvg width={30} height={30} />} number={userInfoDetails.conversations_count} />
</View>
{/* 排行榜 */}
<Ranking data={userInfoDetails.title_rankings} />
</View>
}
// 优化性能:添加 getItemLayout
getItemLayout={(data, index) => (
{ length: 1000, offset: 1000 * index, index }
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
paddingBottom: 86,
},
contentContainer: {
paddingHorizontal: 16,
},
resourceContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: "#4C320C",
paddingVertical: 8,
paddingHorizontal: 32,
borderRadius: 18,
},
text: {
fontSize: 12,
fontWeight: '700',
color: '#FFB645',
},
secondText: {
fontSize: 16,
fontWeight: '700',
color: '#fff',
},
userInfo: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
},
medal: {
marginLeft: 16,
},
backButton: {
padding: 4,
},
headerTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
content: {
flex: 1,
},
profileSection: {
backgroundColor: '#fff',
padding: 20,
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
avatarContainer: {
marginRight: 16,
},
avatar: {
width: 70,
height: 70,
borderRadius: 35,
borderWidth: 2,
borderColor: '#FFD38D',
},
profileInfo: {
flex: 1,
},
userName: {
fontSize: 18,
fontWeight: '600',
color: '#333',
marginBottom: 4,
},
userPhone: {
fontSize: 14,
color: '#666',
},
editButton: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderWidth: 1,
borderColor: '#eee',
borderRadius: 15,
},
editButtonText: {
marginLeft: 4,
color: '#666',
fontSize: 14,
},
menuContainer: {
backgroundColor: '#fff',
paddingHorizontal: 16,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: '#f5f5f5',
},
menuItemText: {
flex: 1,
marginLeft: 12,
fontSize: 16,
color: '#333',
},
arrowIcon: {
marginLeft: 'auto',
},
});