Initial commit

This commit is contained in:
2025-10-11 11:55:25 +08:00
parent 467dad8449
commit 8107dee8d3
2879 changed files with 610575 additions and 0 deletions

View File

@@ -0,0 +1,655 @@
// src/views/Profile/ProfilePage.js
import React, { useState, useRef } from 'react';
import {
Box,
Container,
VStack,
HStack,
Text,
Heading,
Avatar,
Button,
Input,
Textarea,
FormControl,
FormLabel,
SimpleGrid,
Card,
CardBody,
CardHeader,
Stat,
StatLabel,
StatNumber,
StatHelpText,
Badge,
Divider,
Select,
useToast,
IconButton,
Flex,
Progress,
Tag,
TagLabel,
TagCloseButton,
Wrap,
WrapItem,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
useDisclosure
} from '@chakra-ui/react';
import { EditIcon, CheckIcon, CloseIcon, AddIcon } from '@chakra-ui/icons';
import { useAuth } from '../../contexts/AuthContext';
export default function ProfilePage() {
const { user, updateUser } = useAuth();
const [isEditing, setIsEditing] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [newTag, setNewTag] = useState('');
const { isOpen, onOpen, onClose } = useDisclosure();
const fileInputRef = useRef();
const toast = useToast();
// 表单数据状态
const [formData, setFormData] = useState({
nickname: user?.nickname || '',
bio: user?.bio || '',
location: user?.location || '',
gender: user?.gender || '',
birth_date: user?.birth_date || '',
trading_experience: user?.trading_experience || '',
investment_style: user?.investment_style || '',
risk_preference: user?.risk_preference || '',
investment_amount: user?.investment_amount || '',
preferred_markets: user?.preferred_markets ? user.preferred_markets.split(',') : [],
creator_tags: user?.creator_tags ? user.creator_tags.split(',') : []
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSaveProfile = async () => {
setIsLoading(true);
try {
// 这里应该调用后端API更新用户信息
const updatedData = {
...formData,
preferred_markets: formData.preferred_markets.join(','),
creator_tags: formData.creator_tags.join(',')
};
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000));
updateUser(updatedData);
setIsEditing(false);
toast({
title: "个人资料更新成功",
status: "success",
duration: 3000,
isClosable: true,
});
} catch (error) {
toast({
title: "更新失败",
description: error.message,
status: "error",
duration: 3000,
isClosable: true,
});
} finally {
setIsLoading(false);
}
};
const handleAvatarUpload = (event) => {
const file = event.target.files[0];
if (file) {
// 这里应该上传文件到服务器
const reader = new FileReader();
reader.onload = (e) => {
updateUser({ avatar_url: e.target.result });
toast({
title: "头像更新成功",
status: "success",
duration: 3000,
isClosable: true,
});
};
reader.readAsDataURL(file);
}
};
const addMarketTag = () => {
if (newTag && !formData.preferred_markets.includes(newTag)) {
setFormData(prev => ({
...prev,
preferred_markets: [...prev.preferred_markets, newTag]
}));
setNewTag('');
}
};
const removeMarketTag = (tagToRemove) => {
setFormData(prev => ({
...prev,
preferred_markets: prev.preferred_markets.filter(tag => tag !== tagToRemove)
}));
};
const getProgressColor = (score) => {
if (score >= 800) return 'green';
if (score >= 500) return 'blue';
if (score >= 200) return 'yellow';
return 'red';
};
return (
<Container maxW="container.xl" py={8}>
<VStack spacing={8} align="stretch">
{/* 页面标题 */}
<HStack justify="space-between">
<Heading size="lg" color="gray.800">个人资料</Heading>
{!isEditing ? (
<Button
leftIcon={<EditIcon />}
colorScheme="blue"
onClick={() => setIsEditing(true)}
>
编辑资料
</Button>
) : (
<HStack>
<Button
leftIcon={<CheckIcon />}
colorScheme="green"
onClick={handleSaveProfile}
isLoading={isLoading}
>
保存
</Button>
<Button
leftIcon={<CloseIcon />}
variant="outline"
onClick={() => {
setIsEditing(false);
setFormData({
nickname: user?.nickname || '',
bio: user?.bio || '',
location: user?.location || '',
gender: user?.gender || '',
birth_date: user?.birth_date || '',
trading_experience: user?.trading_experience || '',
investment_style: user?.investment_style || '',
risk_preference: user?.risk_preference || '',
investment_amount: user?.investment_amount || '',
preferred_markets: user?.preferred_markets ? user.preferred_markets.split(',') : [],
creator_tags: user?.creator_tags ? user.creator_tags.split(',') : []
});
}}
>
取消
</Button>
</HStack>
)}
</HStack>
<SimpleGrid columns={{ base: 1, lg: 3 }} spacing={8}>
{/* 左侧:基本信息 */}
<Box gridColumn={{ base: "1", lg: "1 / 3" }}>
<Card>
<CardHeader>
<Heading size="md">基本信息</Heading>
</CardHeader>
<CardBody>
<VStack spacing={6}>
{/* 头像和基本信息 */}
<HStack spacing={6} align="start" w="full">
<VStack>
<Avatar
size="2xl"
src={user?.avatar_url}
name={user?.nickname || user?.username}
/>
{isEditing && (
<Button
size="sm"
onClick={() => fileInputRef.current?.click()}
>
更换头像
</Button>
)}
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handleAvatarUpload}
/>
</VStack>
<VStack flex="1" align="start" spacing={4}>
<HStack w="full">
<Badge colorScheme="blue" variant="subtle">
用户名: {user?.username}
</Badge>
{user?.is_verified && (
<Badge colorScheme="green">已实名认证</Badge>
)}
{user?.has_wechat && (
<Badge colorScheme="green">微信已绑定</Badge>
)}
{user?.is_creator && (
<Badge colorScheme="purple">创作者</Badge>
)}
</HStack>
<SimpleGrid columns={2} spacing={4} w="full">
<FormControl>
<FormLabel>昵称</FormLabel>
{isEditing ? (
<Input
name="nickname"
value={formData.nickname}
onChange={handleInputChange}
placeholder="请输入昵称"
/>
) : (
<Text>{user?.nickname || '未设置'}</Text>
)}
</FormControl>
<FormControl>
<FormLabel>性别</FormLabel>
{isEditing ? (
<Select
name="gender"
value={formData.gender}
onChange={handleInputChange}
>
<option value="">请选择</option>
<option value="male"></option>
<option value="female"></option>
<option value="other">其他</option>
</Select>
) : (
<Text>
{user?.gender === 'male' ? '男' :
user?.gender === 'female' ? '女' :
user?.gender === 'other' ? '其他' : '未设置'}
</Text>
)}
</FormControl>
<FormControl>
<FormLabel>所在地</FormLabel>
{isEditing ? (
<Input
name="location"
value={formData.location}
onChange={handleInputChange}
placeholder="请输入所在地"
/>
) : (
<Text>{user?.location || '未设置'}</Text>
)}
</FormControl>
<FormControl>
<FormLabel>生日</FormLabel>
{isEditing ? (
<Input
name="birth_date"
type="date"
value={formData.birth_date}
onChange={handleInputChange}
/>
) : (
<Text>{user?.birth_date || '未设置'}</Text>
)}
</FormControl>
</SimpleGrid>
<FormControl>
<FormLabel>个人简介</FormLabel>
{isEditing ? (
<Textarea
name="bio"
value={formData.bio}
onChange={handleInputChange}
placeholder="介绍一下自己..."
rows={3}
/>
) : (
<Text color="gray.600">
{user?.bio || '这个人很懒,什么都没留下...'}
</Text>
)}
</FormControl>
</VStack>
</HStack>
<Divider />
{/* 投资偏好 */}
<Box w="full">
<Heading size="sm" mb={4}>投资偏好</Heading>
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
<FormControl>
<FormLabel>交易经验</FormLabel>
{isEditing ? (
<Select
name="trading_experience"
value={formData.trading_experience}
onChange={handleInputChange}
>
<option value="">请选择</option>
<option value="beginner">新手 (0-1)</option>
<option value="intermediate">中级 (1-3)</option>
<option value="advanced">高级 (3-5)</option>
<option value="expert">专家 (5年以上)</option>
</Select>
) : (
<Text>
{user?.trading_experience === 'beginner' ? '新手 (0-1年)' :
user?.trading_experience === 'intermediate' ? '中级 (1-3年)' :
user?.trading_experience === 'advanced' ? '高级 (3-5年)' :
user?.trading_experience === 'expert' ? '专家 (5年以上)' : '未设置'}
</Text>
)}
</FormControl>
<FormControl>
<FormLabel>投资风格</FormLabel>
{isEditing ? (
<Select
name="investment_style"
value={formData.investment_style}
onChange={handleInputChange}
>
<option value="">请选择</option>
<option value="conservative">保守型</option>
<option value="moderate">稳健型</option>
<option value="aggressive">积极型</option>
<option value="speculative">投机型</option>
</Select>
) : (
<Text>
{user?.investment_style === 'conservative' ? '保守型' :
user?.investment_style === 'moderate' ? '稳健型' :
user?.investment_style === 'aggressive' ? '积极型' :
user?.investment_style === 'speculative' ? '投机型' : '未设置'}
</Text>
)}
</FormControl>
<FormControl>
<FormLabel>风险偏好</FormLabel>
{isEditing ? (
<Select
name="risk_preference"
value={formData.risk_preference}
onChange={handleInputChange}
>
<option value="">请选择</option>
<option value="low">低风险</option>
<option value="medium">中等风险</option>
<option value="high">高风险</option>
</Select>
) : (
<Text>
{user?.risk_preference === 'low' ? '低风险' :
user?.risk_preference === 'medium' ? '中等风险' :
user?.risk_preference === 'high' ? '高风险' : '未设置'}
</Text>
)}
</FormControl>
<FormControl>
<FormLabel>投资金额</FormLabel>
{isEditing ? (
<Select
name="investment_amount"
value={formData.investment_amount}
onChange={handleInputChange}
>
<option value="">请选择</option>
<option value="under_10k">1万以下</option>
<option value="10k_50k">1-5</option>
<option value="50k_100k">5-10</option>
<option value="100k_500k">10-50</option>
<option value="over_500k">50万以上</option>
</Select>
) : (
<Text>
{user?.investment_amount === 'under_10k' ? '1万以下' :
user?.investment_amount === '10k_50k' ? '1-5万' :
user?.investment_amount === '50k_100k' ? '5-10万' :
user?.investment_amount === '100k_500k' ? '10-50万' :
user?.investment_amount === 'over_500k' ? '50万以上' : '未设置'}
</Text>
)}
</FormControl>
</SimpleGrid>
{/* 偏好市场标签 */}
<FormControl mt={4}>
<FormLabel>偏好市场</FormLabel>
<Wrap>
{formData.preferred_markets.map((market, index) => (
<WrapItem key={index}>
<Tag size="md" variant="solid" colorScheme="blue">
<TagLabel>{market}</TagLabel>
{isEditing && (
<TagCloseButton onClick={() => removeMarketTag(market)} />
)}
</Tag>
</WrapItem>
))}
{isEditing && (
<WrapItem>
<HStack>
<Input
size="sm"
placeholder="添加市场"
value={newTag}
onChange={(e) => setNewTag(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addMarketTag()}
/>
<IconButton
size="sm"
icon={<AddIcon />}
onClick={addMarketTag}
/>
</HStack>
</WrapItem>
)}
</Wrap>
{formData.preferred_markets.length === 0 && !isEditing && (
<Text color="gray.500" fontSize="sm">暂未设置偏好市场</Text>
)}
</FormControl>
</Box>
</VStack>
</CardBody>
</Card>
</Box>
{/* 右侧:统计数据 */}
<VStack spacing={6}>
{/* 社区统计 */}
<Card w="full">
<CardHeader>
<Heading size="md">社区统计</Heading>
</CardHeader>
<CardBody>
<VStack spacing={4}>
<Stat textAlign="center">
<StatLabel>用户等级</StatLabel>
<StatNumber color="blue.500">Lv.{user?.user_level || 1}</StatNumber>
</Stat>
<SimpleGrid columns={2} spacing={4} w="full">
<Stat textAlign="center">
<StatLabel>声誉分数</StatLabel>
<StatNumber fontSize="lg">{user?.reputation_score || 0}</StatNumber>
</Stat>
<Stat textAlign="center">
<StatLabel>贡献点数</StatLabel>
<StatNumber fontSize="lg">{user?.contribution_point || 0}</StatNumber>
</Stat>
<Stat textAlign="center">
<StatLabel>发帖数</StatLabel>
<StatNumber fontSize="lg">{user?.post_count || 0}</StatNumber>
</Stat>
<Stat textAlign="center">
<StatLabel>评论数</StatLabel>
<StatNumber fontSize="lg">{user?.comment_count || 0}</StatNumber>
</Stat>
<Stat textAlign="center">
<StatLabel>关注者</StatLabel>
<StatNumber fontSize="lg">{user?.follower_count || 0}</StatNumber>
</Stat>
<Stat textAlign="center">
<StatLabel>关注中</StatLabel>
<StatNumber fontSize="lg">{user?.following_count || 0}</StatNumber>
</Stat>
</SimpleGrid>
<Box w="full">
<Text fontSize="sm" color="gray.600" mb={2}>声誉等级</Text>
<Progress
value={(user?.reputation_score || 0) / 10}
colorScheme={getProgressColor(user?.reputation_score || 0)}
borderRadius="md"
/>
<Text fontSize="xs" color="gray.500" mt={1}>
{user?.reputation_score || 0} / 1000
</Text>
</Box>
</VStack>
</CardBody>
</Card>
{/* 账户信息 */}
<Card w="full">
<CardHeader>
<Heading size="md">账户信息</Heading>
</CardHeader>
<CardBody>
<VStack spacing={3} align="start">
<HStack justify="space-between" w="full">
<Text fontSize="sm" color="gray.600">邮箱</Text>
<VStack align="end" spacing={0}>
<Text fontSize="sm">{user?.email}</Text>
{user?.email_confirmed && (
<Badge size="xs" colorScheme="green">已验证</Badge>
)}
</VStack>
</HStack>
{user?.phone && (
<HStack justify="space-between" w="full">
<Text fontSize="sm" color="gray.600">手机号</Text>
<VStack align="end" spacing={0}>
<Text fontSize="sm">{user.phone}</Text>
{user?.phone_confirmed && (
<Badge size="xs" colorScheme="green">已验证</Badge>
)}
</VStack>
</HStack>
)}
<HStack justify="space-between" w="full">
<Text fontSize="sm" color="gray.600">微信</Text>
{user?.has_wechat ? (
<Badge size="xs" colorScheme="green">已绑定</Badge>
) : (
<Badge size="xs" colorScheme="gray">未绑定</Badge>
)}
</HStack>
<HStack justify="space-between" w="full">
<Text fontSize="sm" color="gray.600">注册时间</Text>
<Text fontSize="sm">
{user?.created_at ? new Date(user.created_at).toLocaleDateString() : '未知'}
</Text>
</HStack>
<HStack justify="space-between" w="full">
<Text fontSize="sm" color="gray.600">最后活跃</Text>
<Text fontSize="sm">
{user?.last_seen ? new Date(user.last_seen).toLocaleDateString() : '未知'}
</Text>
</HStack>
<HStack justify="space-between" w="full">
<Text fontSize="sm" color="gray.600">账户状态</Text>
<Badge colorScheme={user?.status === 'active' ? 'green' : 'gray'}>
{user?.status === 'active' ? '正常' : '未激活'}
</Badge>
</HStack>
</VStack>
</CardBody>
</Card>
{/* 实名认证 */}
{!user?.is_verified && (
<Card w="full">
<CardHeader>
<Heading size="md">实名认证</Heading>
</CardHeader>
<CardBody>
<VStack spacing={4}>
<Text fontSize="sm" color="gray.600" textAlign="center">
完成实名认证获得更高权限和信任度
</Text>
<Button colorScheme="orange" size="sm" onClick={onOpen}>
立即认证
</Button>
</VStack>
</CardBody>
</Card>
)}
</VStack>
</SimpleGrid>
</VStack>
{/* 实名认证模态框 */}
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>实名认证</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<VStack spacing={4}>
<FormControl>
<FormLabel>真实姓名</FormLabel>
<Input placeholder="请输入真实姓名" />
</FormControl>
<FormControl>
<FormLabel>身份证号</FormLabel>
<Input placeholder="请输入身份证号" />
</FormControl>
<Text fontSize="sm" color="gray.500">
您的个人信息将严格保密仅用于身份验证
</Text>
<Button colorScheme="blue" w="full">
提交认证
</Button>
</VStack>
</ModalBody>
</ModalContent>
</Modal>
</Container>
);
}