feat(个人信息): 新增个人信息页面
- 实现用户头像展示和上传功能 - 添加个人统计数据展示卡片 - 支持基本信息编辑功能 - 集成账号安全设置模块 - 优化页面布局和响应式设计
This commit is contained in:
parent
02c00222d9
commit
c781657003
415
src/pages/user-info/index.tsx
Normal file
415
src/pages/user-info/index.tsx
Normal file
@ -0,0 +1,415 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
Button,
|
||||
Avatar,
|
||||
Upload,
|
||||
Space,
|
||||
Typography,
|
||||
Divider,
|
||||
Grid,
|
||||
Message,
|
||||
Select,
|
||||
DatePicker,
|
||||
Radio,
|
||||
Descriptions,
|
||||
Tag,
|
||||
Statistic,
|
||||
} from '@arco-design/web-react';
|
||||
import {
|
||||
IconUser,
|
||||
IconEmail,
|
||||
IconPhone,
|
||||
IconLocation,
|
||||
IconEdit,
|
||||
IconCamera,
|
||||
IconCheck,
|
||||
IconClose,
|
||||
} from '@arco-design/web-react/icon';
|
||||
import styles from './style/index.module.less';
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
const { Row, Col } = Grid;
|
||||
const FormItem = Form.Item;
|
||||
const Option = Select.Option;
|
||||
const RadioGroup = Radio.Group;
|
||||
|
||||
/**
|
||||
* 个人信息页面
|
||||
*/
|
||||
function UserInfo() {
|
||||
const [form] = Form.useForm();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 模拟用户数据
|
||||
const [userInfo, setUserInfo] = useState({
|
||||
avatar:
|
||||
'https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
name: '张三',
|
||||
username: 'zhangsan',
|
||||
email: 'zhangsan@example.com',
|
||||
phone: '138****8888',
|
||||
gender: 'male',
|
||||
birthday: '1990-01-01',
|
||||
department: '技术部',
|
||||
position: '高级工程师',
|
||||
location: '北京市朝阳区',
|
||||
introduction:
|
||||
'热爱技术,专注于前端开发领域,有丰富的 React 和 TypeScript 开发经验。',
|
||||
joinDate: '2020-01-15',
|
||||
employeeId: 'EMP001',
|
||||
});
|
||||
|
||||
// 统计数据
|
||||
const statistics = {
|
||||
projectCount: 28,
|
||||
taskCompleted: 156,
|
||||
contribution: 892,
|
||||
teamSize: 12,
|
||||
};
|
||||
|
||||
// 初始化表单值
|
||||
React.useEffect(() => {
|
||||
form.setFieldsValue(userInfo);
|
||||
}, [userInfo, form]);
|
||||
|
||||
// 保存用户信息
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const values = await form.validate();
|
||||
setLoading(true);
|
||||
|
||||
// 模拟保存
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
setUserInfo({ ...userInfo, ...values });
|
||||
setIsEditing(false);
|
||||
Message.success('保存成功');
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消编辑
|
||||
const handleCancel = () => {
|
||||
form.setFieldsValue(userInfo);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
// 上传头像
|
||||
const handleAvatarChange = (fileList) => {
|
||||
if (fileList && fileList.length > 0) {
|
||||
const file = fileList[0];
|
||||
if (file.originFile) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
setUserInfo({ ...userInfo, avatar: e.target?.result as string });
|
||||
Message.success('头像上传成功');
|
||||
};
|
||||
reader.readAsDataURL(file.originFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{/* 顶部信息卡片 */}
|
||||
<Card className={styles['profile-card']}>
|
||||
<Row gutter={24}>
|
||||
{/* 左侧头像区域 */}
|
||||
<Col span={6}>
|
||||
<div className={styles['avatar-section']}>
|
||||
<div className={styles['avatar-wrapper']}>
|
||||
<Avatar size={120} className={styles.avatar}>
|
||||
<img src={userInfo.avatar} alt="avatar" />
|
||||
</Avatar>
|
||||
{isEditing && (
|
||||
<Upload
|
||||
accept="image/*"
|
||||
showUploadList={false}
|
||||
onChange={handleAvatarChange}
|
||||
action="/"
|
||||
>
|
||||
<div className={styles['avatar-upload']}>
|
||||
<IconCamera style={{ fontSize: 20 }} />
|
||||
<div>更换头像</div>
|
||||
</div>
|
||||
</Upload>
|
||||
)}
|
||||
</div>
|
||||
<Title heading={5} style={{ marginTop: 16, textAlign: 'center' }}>
|
||||
{userInfo.name}
|
||||
</Title>
|
||||
<Paragraph
|
||||
style={{ textAlign: 'center', color: 'var(--color-text-3)' }}
|
||||
>
|
||||
{userInfo.position}
|
||||
</Paragraph>
|
||||
<div style={{ textAlign: 'center', marginTop: 8 }}>
|
||||
<Tag color="blue">{userInfo.department}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
{/* 右侧统计信息 */}
|
||||
<Col span={18}>
|
||||
<div className={styles['stats-section']}>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title="参与项目"
|
||||
value={statistics.projectCount}
|
||||
suffix="个"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title="完成任务"
|
||||
value={statistics.taskCompleted}
|
||||
suffix="个"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title="代码贡献"
|
||||
value={statistics.contribution}
|
||||
suffix="次"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Statistic
|
||||
title="团队人数"
|
||||
value={statistics.teamSize}
|
||||
suffix="人"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Descriptions
|
||||
column={2}
|
||||
data={[
|
||||
{
|
||||
label: '工号',
|
||||
value: userInfo.employeeId,
|
||||
},
|
||||
{
|
||||
label: '入职时间',
|
||||
value: userInfo.joinDate,
|
||||
},
|
||||
{
|
||||
label: '邮箱',
|
||||
value: userInfo.email,
|
||||
},
|
||||
{
|
||||
label: '联系电话',
|
||||
value: userInfo.phone,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 详细信息卡片 */}
|
||||
<Card
|
||||
style={{ marginTop: 20 }}
|
||||
title="个人信息"
|
||||
extra={
|
||||
!isEditing ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<IconEdit />}
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
编辑资料
|
||||
</Button>
|
||||
) : (
|
||||
<Space>
|
||||
<Button icon={<IconClose />} onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<IconCheck />}
|
||||
loading={loading}
|
||||
onClick={handleSave}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
disabled={!isEditing}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
label="姓名"
|
||||
field="name"
|
||||
rules={[{ required: true, message: '请输入姓名' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入姓名"
|
||||
prefix={<IconUser />}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
label="用户名"
|
||||
field="username"
|
||||
rules={[{ required: true, message: '请输入用户名' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入用户名"
|
||||
prefix={<IconUser />}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
label="邮箱"
|
||||
field="email"
|
||||
rules={[
|
||||
{ required: true, message: '请输入邮箱' },
|
||||
{ type: 'email', message: '请输入正确的邮箱格式' },
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入邮箱"
|
||||
prefix={<IconEmail />}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
label="手机号"
|
||||
field="phone"
|
||||
rules={[{ required: true, message: '请输入手机号' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入手机号"
|
||||
prefix={<IconPhone />}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem label="性别" field="gender">
|
||||
<RadioGroup disabled={!isEditing}>
|
||||
<Radio value="male">男</Radio>
|
||||
<Radio value="female">女</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem label="生日" field="birthday">
|
||||
<DatePicker
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请选择生日"
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem label="部门" field="department">
|
||||
<Select placeholder="请选择部门" disabled={!isEditing}>
|
||||
<Option value="技术部">技术部</Option>
|
||||
<Option value="产品部">产品部</Option>
|
||||
<Option value="设计部">设计部</Option>
|
||||
<Option value="运营部">运营部</Option>
|
||||
<Option value="市场部">市场部</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem label="职位" field="position">
|
||||
<Input placeholder="请输入职位" disabled={!isEditing} />
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<FormItem label="所在地" field="location">
|
||||
<Input
|
||||
placeholder="请输入所在地"
|
||||
prefix={<IconLocation />}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<FormItem label="个人简介" field="introduction">
|
||||
<Input.TextArea
|
||||
placeholder="请输入个人简介"
|
||||
rows={4}
|
||||
disabled={!isEditing}
|
||||
maxLength={200}
|
||||
showWordLimit
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
{/* 账号安全卡片 */}
|
||||
<Card style={{ marginTop: 20 }} title="账号安全">
|
||||
<Descriptions
|
||||
column={1}
|
||||
data={[
|
||||
{
|
||||
label: '登录密码',
|
||||
value: (
|
||||
<Space>
|
||||
<Text>当前密码强度:强</Text>
|
||||
<Button type="text" size="small">
|
||||
修改密码
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '密保手机',
|
||||
value: (
|
||||
<Space>
|
||||
<Text>{userInfo.phone}</Text>
|
||||
<Button type="text" size="small">
|
||||
修改手机
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '密保邮箱',
|
||||
value: (
|
||||
<Space>
|
||||
<Text>{userInfo.email}</Text>
|
||||
<Button type="text" size="small">
|
||||
修改邮箱
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserInfo;
|
||||
81
src/pages/user-info/style/index.module.less
Normal file
81
src/pages/user-info/style/index.module.less
Normal file
@ -0,0 +1,81 @@
|
||||
.container {
|
||||
padding: 20px;
|
||||
background: var(--color-bg-2);
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
:global(.arco-card-body) {
|
||||
padding: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.avatar {
|
||||
border: 4px solid var(--color-border-2);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 10%);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-upload {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 50%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
:global(.arco-statistic-title) {
|
||||
color: var(--color-text-3);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:global(.arco-statistic-value) {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式布局
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
:global(.arco-card-body) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user