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