Files
frontend/src/pages/Profile.tsx

265 lines
8.2 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 { useState, useEffect } from 'react';
import { Form, Input, Button, Card, message, Avatar, Descriptions, Space, Modal, Row, Col } from 'antd';
import { UserOutlined, LockOutlined, SafetyOutlined, KeyOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { authApi } from '@/services/api';
import type { User } from '@/types';
function ProfilePage() {
const [profileForm] = Form.useForm();
const [passwordForm] = Form.useForm();
const [loading, setLoading] = useState(false);
const [passwordModalVisible, setPasswordModalVisible] = useState(false);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
loadUserProfile();
}, []);
const loadUserProfile = () => {
const currentUser = authApi.getCurrentUser();
if (currentUser) {
setUser(currentUser);
profileForm.setFieldsValue({
username: currentUser.username,
name: currentUser.name,
email: currentUser.email,
role: currentUser.role,
createdAt: new Date(currentUser.createdAt).toLocaleString('zh-CN'),
});
}
};
const handleUpdateProfile = async (values: any) => {
setLoading(true);
try {
const updatedUser = await authApi.updateProfile({
name: values.name,
email: values.email,
});
setUser(updatedUser);
message.success('更新资料成功!');
} catch (error: any) {
message.error(error.message || '更新资料失败');
} finally {
setLoading(false);
}
};
const handleChangePassword = async (values: any) => {
setLoading(true);
try {
await authApi.changePassword(values.currentPassword, values.newPassword);
message.success('修改密码成功!');
setPasswordModalVisible(false);
passwordForm.resetFields();
} catch (error: any) {
message.error(error.message || '修改密码失败');
} finally {
setLoading(false);
}
};
return (
<div>
<Card
title={
<span>
<UserOutlined />
</span>
}
bordered={false}
>
<Row gutter={24}>
<Col span={12}>
{user && (
<Descriptions column={1} bordered>
<Descriptions.Item label="头像">
<Avatar icon={<UserOutlined />} size={64} />
</Descriptions.Item>
<Descriptions.Item label="用户名">{user.username}</Descriptions.Item>
<Descriptions.Item label="姓名">{user.name}</Descriptions.Item>
<Descriptions.Item label="邮箱">{user.email || '-'}</Descriptions.Item>
<Descriptions.Item label="角色">{user.role}</Descriptions.Item>
<Descriptions.Item label="创建时间">
{new Date(user.createdAt).toLocaleString('zh-CN')}
</Descriptions.Item>
</Descriptions>
)}
</Col>
<Col span={12}>
<div style={{ marginBottom: '16px', fontSize: '16px', fontWeight: 'bold' }}>
</div>
<Form
form={profileForm}
onFinish={handleUpdateProfile}
layout="vertical"
>
<Form.Item
label="姓名"
name="name"
rules={[{ required: true, message: '请输入姓名' }]}
>
<Input />
</Form.Item>
<Form.Item
label="邮箱"
name="email"
rules={[{ type: 'email', message: '请输入有效的邮箱地址' }]}
>
<Input />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
<Button onClick={loadUserProfile}>
</Button>
</Space>
</Form.Item>
</Form>
<div style={{ marginTop: '24px', marginBottom: '16px', fontSize: '16px', fontWeight: 'bold' }}>
</div>
<Card
size="small"
bordered={false}
style={{ backgroundColor: '#fafafa' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<LockOutlined />
<span></span>
</Space>
<Button type="primary" onClick={() => setPasswordModalVisible(true)}>
</Button>
</div>
</Card>
<Card
size="small"
bordered={false}
style={{ marginTop: '16px', backgroundColor: '#fafafa' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<SafetyOutlined />
<span>TOTP</span>
</Space>
<Button type="default">
</Button>
</div>
</Card>
<Card
size="small"
bordered={false}
style={{ marginTop: '16px', backgroundColor: '#fafafa' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<KeyOutlined />
<span>Passkey</span>
</Space>
<Button type="default">
</Button>
</div>
</Card>
<Card
size="small"
bordered={false}
style={{ marginTop: '16px', backgroundColor: '#fafafa' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space>
<ExclamationCircleOutlined />
<span></span>
</Space>
<Button danger>
</Button>
</div>
</Card>
</Col>
</Row>
</Card>
<Modal
title="修改密码"
open={passwordModalVisible}
onCancel={() => setPasswordModalVisible(false)}
footer={null}
>
<Form
form={passwordForm}
onFinish={handleChangePassword}
layout="vertical"
>
<Form.Item
label="当前密码"
name="currentPassword"
rules={[{ required: true, message: '请输入当前密码' }]}
>
<Input.Password />
</Form.Item>
<Form.Item
label="新密码"
name="newPassword"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 6, message: '密码至少6位' },
]}
>
<Input.Password />
</Form.Item>
<Form.Item
label="确认新密码"
name="confirmPassword"
dependencies={['newPassword']}
rules={[
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入的密码不一致'));
},
}),
]}
>
<Input.Password />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
<Button onClick={() => {
setPasswordModalVisible(false);
passwordForm.resetFields();
}}>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
</div>
);
}
export default ProfilePage;