blob: 2b71e6513c1f810ac5918da7dcce5f6379e33f4b [file] [log] [blame] [edit]
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { message, Button, Form, Input, Radio, Upload, Avatar, Modal, Card, Statistic, Row, Col } from 'antd';
import { UserOutlined, LockOutlined, LogoutOutlined, UploadOutlined, DownloadOutlined, StarOutlined, MoneyCollectOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import AvatarWithFrame from '../components/AvatarWithFrame.jsx';
const UserCenter = () => {
const [user, setUser] = useState(null);
const [form] = Form.useForm();
const [avatarForm] = Form.useForm();
const [passwordForm] = Form.useForm();
const [loading, setLoading] = useState(false);
const [avatarLoading, setAvatarLoading] = useState(false);
const [passwordLoading, setPasswordLoading] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false);
const [fadeAnimation, setFadeAnimation] = useState(false);
const navigate = useNavigate();
// Add fade-in animation when component mounts
useEffect(() => {
setFadeAnimation(true);
}, []);
useEffect(() => {
const userData = localStorage.getItem('user');
if (userData) {
const parsedUser = JSON.parse(userData);
setUser(parsedUser);
form.setFieldsValue({ sex: parsedUser.sex || '男' });
} else {
message.error('请先登录');
navigate('/login');
}
}, [form, navigate]);
const handleGoBack = () => {
navigate(-1); // 返回上一页
};
const handleSexChange = async () => {
try {
const values = await form.validateFields();
setLoading(true);
const response = await axios.post('http://localhost:8080/user/changesex', null, {
params: {
username: user.username,
sex: values.sex
}
});
if (response.data && response.data.success) {
message.success('性别修改成功');
const updatedUser = { ...user, sex: values.sex };
localStorage.setItem('user', JSON.stringify(updatedUser));
setUser(updatedUser);
// Add a subtle success animation effect
message.config({
duration: 2,
maxCount: 1,
});
} else {
message.error(response.data.message || '性别修改失败');
}
} catch (error) {
console.error('修改性别出错:', error);
message.error(error.response?.data?.message || '修改性别过程中出错');
} finally {
setLoading(false);
}
};
const handleAvatarChange = async (info) => {
if (info.file.status === 'uploading') {
setAvatarLoading(true);
return;
}
if (info.file.status === 'done') {
try {
const uploadRes = info.file.response;
if (!uploadRes?.success) {
throw new Error(uploadRes?.message || '上传失败');
}
const updateRes = await axios.post('http://localhost:8080/user/changeimage', null, {
params: {
username: user.username,
image: uploadRes.url
}
});
if (updateRes.data?.success) {
message.success('头像更新成功');
const updatedUser = {
...user,
image: uploadRes.url,
...updateRes.data.user
};
localStorage.setItem('user', JSON.stringify(updatedUser));
setUser(updatedUser);
// Add a subtle animation when avatar updates
message.config({
duration: 2,
maxCount: 1,
});
} else {
throw new Error(updateRes.data?.message || '更新失败');
}
} catch (err) {
message.error(err.message);
} finally {
setAvatarLoading(false);
}
}
if (info.file.status === 'error') {
message.error(info.file.response?.message || '上传出错');
setAvatarLoading(false);
}
};
const handlePasswordChange = async () => {
try {
const values = await passwordForm.validateFields();
setPasswordLoading(true);
const response = await axios.post('http://localhost:8080/user/changePassword', null, {
params: {
username: user.username,
oldpassword: values.oldPassword,
newpassword: values.newPassword
}
});
if (response.data && response.data.success) {
message.success('密码修改成功');
passwordForm.resetFields();
setIsModalVisible(false);
// Add a subtle success animation effect
message.config({
duration: 2,
maxCount: 1,
});
} else {
message.error(response.data.message || '密码修改失败');
}
} catch (error) {
console.error('修改密码出错:', error);
message.error(error.response?.data?.message || '修改密码过程中出错');
} finally {
setPasswordLoading(false);
}
};
const handleLogout = () => {
localStorage.removeItem('user');
message.success('已退出登录');
navigate('/'); // 退出后跳转到登录页
};
const uploadProps = {
name: 'avatar',
action: 'http://localhost:8080/user/uploadimage',
showUploadList: false,
onChange: handleAvatarChange,
beforeUpload: (file) => {
const isImage = ['image/jpeg', 'image/png', 'image/gif'].includes(file.type);
if (!isImage) {
message.error('只能上传JPG/PNG/GIF图片!');
return false;
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('图片必须小于10MB!');
return false;
}
return true;
},
transformResponse: (data) => {
try {
return JSON.parse(data);
} catch {
return { success: false, message: '解析响应失败' };
}
}
};
if (!user) {
return <div style={{ padding: '20px', textAlign: 'center' }}>加载中...</div>;
}
const calculateRatio = () => {
if (user.user_download === 0) return '∞';
return (user.user_upload / user.user_download).toFixed(2);
};
// Dynamic styles with the primary color #ffbd19
const primaryColor = '#ffbd19';
const secondaryColor = '#ffffff'; // White for contrast
const cardBackgroundColor = '#ffffff';
const cardShadow = '0 4px 12px rgba(255, 189, 25, 0.1)';
const textColor = '#333333';
const borderColor = '#ffbd19';
return (
<div style={{
maxWidth: '1000px',
margin: '0 auto',
padding: '20px',
animation: fadeAnimation ? 'fadeIn 0.5s ease-in' : 'none'
}}>
{/* CSS animations */}
<style>{`
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.stat-card:hover {
transform: translateY(-5px);
transition: transform 0.3s ease;
box-shadow: 0 6px 16px rgba(255, 189, 25, 0.2);
}
.section-title {
position: relative;
padding-bottom: 10px;
margin-bottom: 20px;
}
.section-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 50px;
height: 3px;
background-color: ${primaryColor};
border-radius: 3px;
}
.avatar-upload-hint {
transition: all 0.3s ease;
}
.avatar-upload-hint:hover {
background-color: rgba(255, 189, 25, 0.2);
}
`}</style>
{/* 添加返回按钮 */}
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={handleGoBack}
style={{
marginBottom: '20px',
color: textColor,
transition: 'all 0.3s'
}}
onMouseOver={(e) => e.currentTarget.style.color = primaryColor}
onMouseOut={(e) => e.currentTarget.style.color = textColor}
>
返回
</Button>
<h1 style={{
textAlign: 'center',
marginBottom: '30px',
color: textColor,
fontWeight: 'bold'
}}>
用户中心
</h1>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '30px'
}}>
{/* 基本信息展示 */}
<div style={{
backgroundColor: cardBackgroundColor,
padding: '20px',
borderRadius: '8px',
boxShadow: cardShadow
}}>
<div style={{
display: 'flex',
alignItems: 'center',
marginBottom: '20px'
}}>
<Upload {...uploadProps}>
<div style={{ position: 'relative', cursor: 'pointer' }}>
<AvatarWithFrame user={user} />
<div style={{
position: 'absolute',
bottom: -25,
right: 20,
backgroundColor: 'rgba(0,0,0,0.5)',
color: 'white',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '12px'
}}>
<UploadOutlined /> 修改
</div>
</div>
</Upload>
<div style={{
marginLeft: '20px', // Adjusted position
flex: 1
}}>
<h2 style={{ margin: '0', color: textColor }}>{user.username}</h2>
<p style={{ margin: '5px 0 0', color: '#666' }}>用户等级: Lv {user.grade_id}</p>
</div>
</div>
</div>
{/* 数据统计展示区 */}
<Card
title={
<div className="section-title">
数据统计
</div>
}
bordered={false}
style={{
borderRadius: '8px',
boxShadow: cardShadow
}}
>
<Row gutter={16}>
<Col span={6}>
<Card
className="stat-card"
hoverable
style={{
borderRadius: '8px',
textAlign: 'center',
transition: 'all 0.3s'
}}
>
<Statistic
title="积分"
value={user.credit || 0}
prefix={<MoneyCollectOutlined style={{ color: primaryColor }} />}
valueStyle={{ color: '#3f8600' }}
/>
</Card>
</Col>
<Col span={6}>
<Card
className="stat-card"
hoverable
style={{
borderRadius: '8px',
textAlign: 'center',
transition: 'all 0.3s'
}}
>
<Statistic
title="上传量 (MB)"
value={(user.user_upload / (1024 * 1024)).toFixed(2) || 0}
prefix={<UploadOutlined style={{ color: primaryColor }} />}
valueStyle={{ color: '#1890ff' }}
/>
</Card>
</Col>
<Col span={6}>
<Card
className="stat-card"
hoverable
style={{
borderRadius: '8px',
textAlign: 'center',
transition: 'all 0.3s'
}}
>
<Statistic
title="下载量 (MB)"
value={(user.user_download / (1024 * 1024)).toFixed(2) || 0}
prefix={<DownloadOutlined style={{ color: primaryColor }} />}
valueStyle={{ color: '#722ed1' }}
/>
</Card>
</Col>
<Col span={6}>
<Card
className="stat-card"
hoverable
style={{
borderRadius: '8px',
textAlign: 'center',
transition: 'all 0.3s'
}}
>
<Statistic
title="分享率"
value={calculateRatio()}
prefix={<StarOutlined style={{ color: primaryColor }} />}
valueStyle={{ color: '#faad14' }}
/>
</Card>
</Col>
</Row>
</Card>
{/* 性别设置 */}
<div style={{
backgroundColor: cardBackgroundColor,
padding: '20px',
borderRadius: '8px',
boxShadow: cardShadow
}}>
<h3 className="section-title">性别设置</h3>
<Form form={form} layout="inline">
<Form.Item name="sex">
<Radio.Group>
<Radio value="男">男</Radio>
<Radio value="女">女</Radio>
<Radio value="保密">保密</Radio>
</Radio.Group>
</Form.Item>
<Form.Item>
<Button
type="primary"
onClick={handleSexChange}
loading={loading}
style={{
backgroundColor: primaryColor,
borderColor: primaryColor,
transition: 'all 0.3s'
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = '#ffc940';
e.currentTarget.style.borderColor = '#ffc940';
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = primaryColor;
e.currentTarget.style.borderColor = primaryColor;
}}
>
确认修改
</Button>
</Form.Item>
</Form>
</div>
{/* 修改密码 */}
<div style={{
backgroundColor: cardBackgroundColor,
padding: '20px',
borderRadius: '8px',
boxShadow: cardShadow
}}>
<h3 className="section-title">修改密码</h3>
<Button
type="primary"
onClick={() => setIsModalVisible(true)}
icon={<LockOutlined />}
style={{
backgroundColor: primaryColor,
borderColor: primaryColor,
transition: 'all 0.3s'
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = '#ffc940';
e.currentTarget.style.borderColor = '#ffc940';
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = primaryColor;
e.currentTarget.style.borderColor = primaryColor;
}}
>
修改密码
</Button>
</div>
{/* 退出登录 */}
<div style={{ textAlign: 'center' }}>
<Button
danger
onClick={handleLogout}
icon={<LogoutOutlined />}
style={{
transition: 'all 0.3s'
}}
onMouseOver={(e) => e.currentTarget.style.opacity = '0.8'}
onMouseOut={(e) => e.currentTarget.style.opacity = '1'}
>
退出登录
</Button>
</div>
</div>
{/* 修改密码模态框 */}
<Modal
title={
<div style={{
color: primaryColor,
fontWeight: 'bold'
}}>
修改密码
</div>
}
open={isModalVisible}
onCancel={() => setIsModalVisible(false)}
footer={null}
centered
width={400}
className="modal-content"
style={{
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)'
}}
>
<Form
form={passwordForm}
layout="vertical"
onFinish={handlePasswordChange}
>
<Form.Item
name="oldPassword"
label="旧密码"
rules={[{ required: true, message: '请输入旧密码' }]}
>
<Input.Password
placeholder="请输入当前密码"
style={{ borderRadius: '4px' }}
/>
</Form.Item>
<Form.Item
name="newPassword"
label="新密码"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 3, message: '密码长度不能少于3位' }
]}
>
<Input.Password
placeholder="请输入新密码"
style={{ borderRadius: '4px' }}
/>
</Form.Item>
<Form.Item
name="confirmPassword"
label="确认新密码"
dependencies={['newPassword']}
rules={[
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入的密码不一致!'));
},
}),
]}
>
<Input.Password
placeholder="请再次输入新密码"
style={{ borderRadius: '4px' }}
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={passwordLoading}
style={{
width: '100%',
backgroundColor: primaryColor,
borderColor: primaryColor,
transition: 'all 0.3s'
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = '#ffc940';
e.currentTarget.style.borderColor = '#ffc940';
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = primaryColor;
e.currentTarget.style.borderColor = primaryColor;
}}
>
确认修改
</Button>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default UserCenter;