Files
vf_react/src/views/Profile/index.js
2025-11-23 21:42:48 +08:00

384 lines
11 KiB
JavaScript
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 React, { useState, useEffect } from 'react';
import {
Box,
Container,
Grid,
GridItem,
Heading,
Text,
VStack,
HStack,
Avatar,
Button,
Card,
CardBody,
CardHeader,
Stat,
StatLabel,
StatNumber,
StatHelpText,
StatArrow,
Badge,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
Icon,
useToast,
Spinner,
Divider,
} from '@chakra-ui/react';
import {
Wallet,
TrendingUp,
Gift,
History,
Award,
Calendar,
DollarSign,
Activity,
} from 'lucide-react';
import { useAuth } from '@contexts/AuthContext';
import { getUserAccount, claimDailyBonus } from '@services/predictionMarketService.api';
import { forumColors } from '@theme/forumTheme';
const ProfilePage = () => {
const toast = useToast();
const { user } = useAuth();
// 状态管理
const [account, setAccount] = useState(null);
const [loading, setLoading] = useState(true);
const [claiming, setClaiming] = useState(false);
// 加载用户积分账户
useEffect(() => {
const fetchAccount = async () => {
if (!user) return;
try {
setLoading(true);
const response = await getUserAccount();
if (response.success) {
setAccount(response.data);
}
} catch (error) {
console.error('获取账户失败:', error);
toast({
title: '加载失败',
description: '无法加载账户信息',
status: 'error',
duration: 3000,
});
} finally {
setLoading(false);
}
};
fetchAccount();
}, [user, toast]);
// 领取每日奖励
const handleClaimDailyBonus = async () => {
try {
setClaiming(true);
const response = await claimDailyBonus();
if (response.success) {
toast({
title: '领取成功!',
description: `获得 ${response.data.bonus_amount} 积分`,
status: 'success',
duration: 3000,
});
// 刷新账户数据
const accountResponse = await getUserAccount();
if (accountResponse.success) {
setAccount(accountResponse.data);
}
}
} catch (error) {
toast({
title: '领取失败',
description: error.response?.data?.error || '今日奖励已领取或系统错误',
status: 'error',
duration: 3000,
});
} finally {
setClaiming(false);
}
};
if (loading) {
return (
<Box minH="80vh" display="flex" alignItems="center" justifyContent="center">
<VStack spacing="4">
<Spinner size="xl" color={forumColors.primary[500]} />
<Text color={forumColors.text.secondary}>加载中...</Text>
</VStack>
</Box>
);
}
return (
<Box bg={forumColors.background.main} minH="100vh" py="8">
<Container maxW="container.xl">
{/* 用户信息头部 */}
<Card
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.default}
mb="6"
>
<CardBody>
<HStack spacing="6" align="start">
<Avatar
size="2xl"
name={user?.nickname || user?.username}
src={user?.avatar_url}
bg={forumColors.gradients.goldPrimary}
color={forumColors.background.main}
/>
<VStack align="start" spacing="3" flex="1">
<Heading size="lg" color={forumColors.text.primary}>
{user?.nickname || user?.username}
</Heading>
<HStack spacing="4">
<Badge
bg={forumColors.gradients.goldSubtle}
color={forumColors.primary[500]}
px="3"
py="1"
borderRadius="full"
>
<Icon as={Award} boxSize="14px" mr="1" />
会员
</Badge>
<Text fontSize="sm" color={forumColors.text.secondary}>
{user?.email}
</Text>
</HStack>
</VStack>
</HStack>
</CardBody>
</Card>
{/* 积分概览 */}
<Grid templateColumns={{ base: '1fr', md: 'repeat(4, 1fr)' }} gap="6" mb="6">
{/* 总余额 */}
<GridItem>
<Card
bg={forumColors.gradients.goldSubtle}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.gold}
>
<CardBody>
<Stat>
<StatLabel fontSize="sm" color={forumColors.text.secondary}>
<Icon as={Wallet} boxSize="16px" mr="1" />
总余额
</StatLabel>
<StatNumber fontSize="3xl" fontWeight="bold" color={forumColors.primary[500]}>
{account?.balance?.toFixed(0) || 0}
</StatNumber>
<StatHelpText color={forumColors.text.tertiary}>积分</StatHelpText>
</Stat>
</CardBody>
</Card>
</GridItem>
{/* 可用余额 */}
<GridItem>
<Card
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.default}
>
<CardBody>
<Stat>
<StatLabel fontSize="sm" color={forumColors.text.secondary}>
<Icon as={DollarSign} boxSize="16px" mr="1" />
可用余额
</StatLabel>
<StatNumber fontSize="2xl" color={forumColors.text.primary}>
{account?.available_balance?.toFixed(0) || 0}
</StatNumber>
<StatHelpText color={forumColors.text.tertiary}>积分</StatHelpText>
</Stat>
</CardBody>
</Card>
</GridItem>
{/* 累计收益 */}
<GridItem>
<Card
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.default}
>
<CardBody>
<Stat>
<StatLabel fontSize="sm" color={forumColors.text.secondary}>
<Icon as={TrendingUp} boxSize="16px" mr="1" />
累计收益
</StatLabel>
<StatNumber fontSize="2xl" color={forumColors.success[500]}>
+{account?.total_earned?.toFixed(0) || 0}
</StatNumber>
<StatHelpText>
<StatArrow type="increase" />
积分
</StatHelpText>
</Stat>
</CardBody>
</Card>
</GridItem>
{/* 累计消费 */}
<GridItem>
<Card
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.default}
>
<CardBody>
<Stat>
<StatLabel fontSize="sm" color={forumColors.text.secondary}>
<Icon as={Activity} boxSize="16px" mr="1" />
累计消费
</StatLabel>
<StatNumber fontSize="2xl" color={forumColors.text.primary}>
{account?.total_spent?.toFixed(0) || 0}
</StatNumber>
<StatHelpText color={forumColors.text.tertiary}>积分</StatHelpText>
</Stat>
</CardBody>
</Card>
</GridItem>
</Grid>
{/* 每日签到 */}
<Card
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.default}
mb="6"
>
<CardHeader>
<HStack justify="space-between">
<HStack spacing="2">
<Icon as={Gift} boxSize="20px" color={forumColors.primary[500]} />
<Heading size="md" color={forumColors.text.primary}>
每日签到
</Heading>
</HStack>
<Button
bg={forumColors.gradients.goldPrimary}
color={forumColors.background.main}
fontWeight="bold"
onClick={handleClaimDailyBonus}
isLoading={claiming}
loadingText="领取中..."
_hover={{ opacity: 0.9 }}
leftIcon={<Icon as={Calendar} />}
>
领取今日奖励
</Button>
</HStack>
</CardHeader>
<CardBody>
<VStack align="start" spacing="3">
<HStack spacing="2">
<Icon as={Calendar} boxSize="16px" color={forumColors.text.secondary} />
<Text fontSize="sm" color={forumColors.text.secondary}>
每日登录可领取 100 积分奖励
</Text>
</HStack>
{account?.last_daily_bonus_at && (
<Text fontSize="xs" color={forumColors.text.tertiary}>
上次领取时间{new Date(account.last_daily_bonus_at).toLocaleString('zh-CN')}
</Text>
)}
</VStack>
</CardBody>
</Card>
{/* 详细信息标签页 */}
<Card
bg={forumColors.background.card}
borderRadius="xl"
border="1px solid"
borderColor={forumColors.border.default}
>
<CardBody>
<Tabs colorScheme="yellow" variant="soft-rounded">
<TabList mb="4">
<Tab
_selected={{
bg: forumColors.gradients.goldPrimary,
color: forumColors.background.main,
}}
>
<Icon as={History} boxSize="16px" mr="2" />
交易记录
</Tab>
<Tab
_selected={{
bg: forumColors.gradients.goldPrimary,
color: forumColors.background.main,
}}
>
<Icon as={Activity} boxSize="16px" mr="2" />
积分明细
</Tab>
</TabList>
<TabPanels>
{/* 交易记录 */}
<TabPanel>
<Box>
<Text color={forumColors.text.secondary} textAlign="center" py="10">
暂无交易记录
</Text>
</Box>
</TabPanel>
{/* 积分明细 */}
<TabPanel>
<Box>
<Text color={forumColors.text.secondary} textAlign="center" py="10">
暂无积分明细
</Text>
</Box>
</TabPanel>
</TabPanels>
</Tabs>
</CardBody>
</Card>
</Container>
</Box>
);
};
export default ProfilePage;