用户中心 商城 登陆注册前端
Change-Id: I4165f66073210b296608a5cfa0e8329e3d9bc8e3
diff --git a/src/pages/UserCenter.jsx b/src/pages/UserCenter.jsx
new file mode 100644
index 0000000..2b71e65
--- /dev/null
+++ b/src/pages/UserCenter.jsx
@@ -0,0 +1,599 @@
+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;
\ No newline at end of file