Browse Source

feat: 重新设计个人中心

dev-zw-temp
zhaowei 7 months ago
parent
commit
03136fe4c2
7 changed files with 287 additions and 255 deletions
  1. +1
    -2
      react-ui/src/app.tsx
  2. +9
    -1
      react-ui/src/components/RightContent/AvatarDropdown.tsx
  3. +94
    -90
      react-ui/src/pages/User/Center/components/BaseInfo/index.tsx
  4. +59
    -51
      react-ui/src/pages/User/Center/components/ResetPassword/index.tsx
  5. +25
    -22
      react-ui/src/pages/User/Center/index.less
  6. +98
    -88
      react-ui/src/pages/User/Center/index.tsx
  7. +1
    -1
      react-ui/src/pages/Workspace/components/UserSpace/index.tsx

+ 1
- 2
react-ui/src/app.tsx View File

@@ -37,8 +37,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
...response.user,
avatar: response.user.avatar || require('@/assets/img/avatar-default.png'),
permissions: response.permissions,
roles: response.roles,
roleNames: response.user.roles,
roleNames: response.roles,
} as API.CurrentUser;
} catch (error) {
console.error('getInitialState', error);


+ 9
- 1
react-ui/src/components/RightContent/AvatarDropdown.tsx View File

@@ -1,4 +1,5 @@
import { clearSessionToken } from '@/access';
import DefaultAvatar from '@/assets/img/avatar-default.png';
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { setRemoteMenu } from '@/services/session';
import { logout } from '@/services/system/auth';
@@ -56,7 +57,14 @@ const AvatarLogo = () => {
},
};
});
return <Avatar size="small" className={avatarClassName} src={currentUser?.avatar} alt="avatar" />;
return (
<Avatar
size="small"
className={avatarClassName}
src={currentUser?.avatar || DefaultAvatar}
alt="avatar"
/>
);
};

const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {


+ 94
- 90
react-ui/src/pages/User/Center/components/BaseInfo/index.tsx View File

@@ -1,91 +1,107 @@
import KFModal from '@/components/KFModal';
import { updateUserProfile } from '@/services/system/user';
import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import { Form, message, Row } from 'antd';
import { to } from '@/utils/promise';
import { Form, Input, message, Radio } from 'antd';
import React from 'react';

export type BaseInfoProps = {
values: Partial<API.CurrentUser> | undefined;
values: Partial<API.CurrentUser>;
open: boolean;
onFinished?: (isSuccess: boolean) => void;
};

const BaseInfo: React.FC<BaseInfoProps> = (props) => {
const BaseInfo: React.FC<BaseInfoProps> = ({ open, onFinished, values: initialValues }) => {
const [form] = Form.useForm();
const intl = useIntl();

const handleFinish = async (values: Record<string, any>) => {
const data = { ...props.values, ...values } as API.CurrentUser;
const resp = await updateUserProfile(data);
if (resp.code === 200) {
const handleFinish = async (formData: Record<string, any>) => {
const data = { userId: initialValues.userId, ...formData } as API.CurrentUser;
const [res] = await to(updateUserProfile(data));
if (res) {
message.success('修改成功');
} else {
message.warning(resp.msg);
onFinished?.(true);
}
};

return (
<>
<ProForm form={form} onFinish={handleFinish} initialValues={props.values}>
<Row>
<ProFormText
name="nickName"
label={intl.formatMessage({
id: 'system.user.nick_name',
defaultMessage: '用户昵称',
})}
width="xl"
placeholder="请输入用户昵称"
rules={[
{
required: true,
message: (
<FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
),
},
]}
/>
</Row>
<Row>
<ProFormText
name="phonenumber"
label={intl.formatMessage({
id: 'system.user.phonenumber',
defaultMessage: '手机号码',
})}
width="xl"
placeholder="请输入手机号码"
rules={[
{
required: false,
message: (
<FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
),
},
]}
/>
</Row>
<Row>
<ProFormText
name="email"
label={intl.formatMessage({
id: 'system.user.email',
defaultMessage: '邮箱',
})}
width="xl"
placeholder="请输入邮箱"
rules={[
{
type: 'email',
message: '无效的邮箱地址!',
},
{
required: false,
message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
},
]}
/>
</Row>
<Row>
<ProFormRadio.Group
<KFModal
width={800}
title="修改基本信息"
open={open}
okButtonProps={{
htmlType: 'submit',
form: 'basic-info-form',
}}
onCancel={() => onFinished?.(false)}
destroyOnClose
>
<Form
name="basic-info-form"
form={form}
layout="vertical"
size="large"
autoComplete="off"
scrollToFirstError
initialValues={initialValues}
onFinish={handleFinish}
>
<Form.Item
name="nickName"
label="用户昵称"
rules={[
{
required: true,
message: '请输入用户昵称',
},
]}
>
<Input placeholder="请输入用户昵称" allowClear></Input>
</Form.Item>

<Form.Item
name="phonenumber"
label="手机号码"
rules={[
{
required: true,
message: '请输入手机号码',
},
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: '请输入正确的手机号码',
},
]}
>
<Input placeholder="请输入手机号码" allowClear></Input>
</Form.Item>

<Form.Item
name="email"
label="邮箱"
rules={[
{
required: true,
message: '请输入邮箱',
},
{
type: 'email',
message: '请输入正确的邮箱地址',
},
]}
>
<Input placeholder="请输入邮箱" allowClear></Input>
</Form.Item>

<Form.Item
name="sex"
label="性别"
rules={[
{
required: false,
message: '请选择性别',
},
]}
>
<Radio.Group
options={[
{
label: '男',
@@ -96,22 +112,10 @@ const BaseInfo: React.FC<BaseInfoProps> = (props) => {
value: '1',
},
]}
name="sex"
label={intl.formatMessage({
id: 'system.user.sex',
defaultMessage: 'sex',
})}
width="xl"
rules={[
{
required: false,
message: <FormattedMessage id="请输入性别!" defaultMessage="请输入性别!" />,
},
]}
/>
</Row>
</ProForm>
</>
</Form.Item>
</Form>
</KFModal>
);
};



+ 59
- 51
react-ui/src/pages/User/Center/components/ResetPassword/index.tsx View File

@@ -1,81 +1,89 @@
import KFModal from '@/components/KFModal';
import { updateUserPwd } from '@/services/system/user';
import { ProForm, ProFormText } from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import { Form, message } from 'antd';
import React from 'react';
import { to } from '@/utils/promise';
import { Form, Input, message } from 'antd';

const ResetPassword: React.FC = () => {
export type ResetPasswordProps = {
open: boolean;
onFinished?: (isSuccess: boolean) => void;
};

const ResetPassword = ({ open, onFinished }: ResetPasswordProps) => {
const [form] = Form.useForm();
const intl = useIntl();

const handleFinish = async (values: Record<string, any>) => {
const resp = await updateUserPwd(values.oldPassword, values.newPassword);
if (resp.code === 200) {
message.success('密码重置成功。');
} else {
message.warning(resp.msg);
const [res] = await to(updateUserPwd(values.oldPassword, values.newPassword));
if (res) {
message.success('密码重置成功');
onFinished?.(true);
}
};

const checkPassword = (_rule: any, value: string) => {
const login_password = form.getFieldValue('newPassword');
if (value === login_password) {
return Promise.resolve();
if (!value) {
return Promise.reject(new Error('请输入确认密码'));
} else if (value !== login_password) {
return Promise.reject(new Error('两次密码输入不一致'));
}
return Promise.reject(new Error('两次密码输入不一致'));
return Promise.resolve();
};

return (
<>
<ProForm form={form} onFinish={handleFinish}>
<ProFormText.Password
<KFModal
width={800}
title="重置密码"
open={open}
okButtonProps={{
htmlType: 'submit',
form: 'reset-pwd-form',
}}
onCancel={() => onFinished?.(false)}
destroyOnClose
>
<Form
form={form}
name="reset-pwd-form"
layout="vertical"
size="large"
autoComplete="off"
scrollToFirstError
onFinish={handleFinish}
>
<Form.Item
name="oldPassword"
label={intl.formatMessage({
id: 'system.user.old_password',
defaultMessage: '旧密码',
})}
width="xl"
placeholder="请输入旧密码"
label="旧密码"
rules={[
{
required: true,
message: <FormattedMessage id="请输入旧密码!" defaultMessage="请输入旧密码!" />,
message: '请输入旧密码',
},
]}
/>
<ProFormText.Password
>
<Input.Password placeholder="请输入旧密码" allowClear></Input.Password>
</Form.Item>
<Form.Item
name="newPassword"
label={intl.formatMessage({
id: 'system.user.new_password',
defaultMessage: '新密码',
})}
width="xl"
placeholder="请输入新密码"
label="新密码"
rules={[
{
required: true,
message: <FormattedMessage id="请输入新密码!" defaultMessage="请输入新密码!" />,
message: '请输入新密码',
},
]}
/>
<ProFormText.Password
>
<Input.Password placeholder="请输入新密码" allowClear></Input.Password>
</Form.Item>
<Form.Item
name="confirmPassword"
label={intl.formatMessage({
id: 'system.user.confirm_password',
defaultMessage: '确认密码',
})}
width="xl"
placeholder="请输入确认密码"
rules={[
{
required: true,
message: <FormattedMessage id="请输入确认密码!" defaultMessage="请输入确认密码!" />,
},
{ validator: checkPassword },
]}
/>
</ProForm>
</>
label="确认密码"
required
rules={[{ validator: checkPassword }]}
>
<Input.Password placeholder="请输入确认密码" allowClear></Input.Password>
</Form.Item>
</Form>
</KFModal>
);
};



react-ui/src/pages/User/Center/Center.less → react-ui/src/pages/User/Center/index.less View File

@@ -2,7 +2,7 @@
position: relative;
display: inline-block;
height: 120px;
margin-bottom: 16px;
margin-bottom: 30px;
text-align: center;

& > img {
@@ -30,31 +30,34 @@
}
}

.teamTitle {
margin-bottom: 12px;
color: @heading-color;
font-weight: 500;
}
.user-center {
height: calc(100% - 50px - 120px);
padding: 30px;
background: white;
border-radius: 8px;
width: 50%;
margin: 60px auto 0;
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;

.team {
:global {
.ant-avatar {
margin-right: 12px;
.ant-list {
width: 100%;

.ant-list-item {
height: 80px;
border-block-end: 1px solid rgba(5, 5, 5, 0.06);
font-size: 16px;
}
}
}

a {
display: block;
margin-bottom: 24px;
overflow: hidden;
color: @text-color;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
transition: color 0.3s;

&:hover {
color: @primary-color;
}
&__buttons {
display: flex;
align-items: center;
margin-top: 60px;
flex-direction: row;
}
}

+ 98
- 88
react-ui/src/pages/User/Center/index.tsx View File

@@ -1,6 +1,9 @@
import { getUserInfo } from '@/services/session';
import DefaultAvatar from '@/assets/img/avatar-default.png';
import PageTitle from '@/components/PageTitle';
import { to } from '@/utils/promise';
import {
ClusterOutlined,
HeartOutlined,
MailOutlined,
ManOutlined,
MobileOutlined,
@@ -8,45 +11,52 @@ import {
UserOutlined,
} from '@ant-design/icons';
import { PageLoading } from '@ant-design/pro-components';
import { useRequest } from '@umijs/max';
import { Card, Col, Divider, List, Row } from 'antd';
import React, { useState } from 'react';
import styles from './Center.less';
import { useModel } from '@umijs/max';
import { List } from 'antd';
import { useCallback, useState } from 'react';
import { flushSync } from 'react-dom';
import AvatarCropper from './components/AvatarCropper';
import BaseInfo from './components/BaseInfo';
import ResetPassword from './components/ResetPassword';
import BaseInfoModal from './components/BaseInfo';
import ResetPasswordModal from './components/ResetPassword';
import styles from './index.less';

const operationTabList = [
{
key: 'base',
tab: <span>基本资料</span>,
},
{
key: 'password',
tab: <span>重置密码</span>,
},
];
const Center = () => {
const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
const [infoModalOpen, setInfoModalOpen] = useState<boolean>(false);
const [resetModalOpen, setRestModalOpen] = useState<boolean>(false);

export type tabKeyType = 'base' | 'password';
const { initialState, setInitialState } = useModel('@@initialState');
const { currentUser, fetchUserInfo } = initialState || {};

const Center: React.FC = () => {
const [tabKey, setTabKey] = useState<tabKeyType>('base');
const refreshUserInfo = useCallback(async () => {
if (fetchUserInfo) {
const [res] = await to(fetchUserInfo());
if (res) {
flushSync(() => {
setInitialState((s) => ({ ...s, currentUser: res }));
});
}
}
}, [setInitialState, fetchUserInfo]);

const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
const handleBaseInfoChange = (success: boolean) => {
setInfoModalOpen(false);

// 获取用户信息
const { data: userInfo, loading } = useRequest(async () => {
return { data: await getUserInfo() };
});
if (loading) {
return <div>loading...</div>;
}
if (success) {
refreshUserInfo();
}
};

const currentUser = userInfo?.user;
const handleResetPassword = (success: boolean) => {
setRestModalOpen(false);
if (success) {
}
};

// 渲染用户信息
const renderUserInfo = ({
userName,
nickName,
phonenumber,
email,
sex,
@@ -65,6 +75,17 @@ const Center: React.FC = () => {
</div>
<div>{userName}</div>
</List.Item>
<List.Item>
<div>
<HeartOutlined
style={{
marginRight: 8,
}}
/>
昵称
</div>
<div>{nickName}</div>
</List.Item>
<List.Item>
<div>
<ManOutlined
@@ -109,75 +130,53 @@ const Center: React.FC = () => {
</div>
<div>{dept?.deptName}</div>
</List.Item>
<List.Item>
<div>
<TeamOutlined
style={{
marginRight: 8,
}}
/>
角色
</div>
<div>{currentUser?.roles?.map((item: any) => item.roleName)?.join(',')}</div>
</List.Item>
</List>
);
};

// 渲染tab切换
const renderChildrenByTabKey = (tabValue: tabKeyType) => {
if (tabValue === 'base') {
return <BaseInfo values={currentUser} />;
}
if (tabValue === 'password') {
return <ResetPassword />;
}
return null;
};

if (!currentUser) {
return <PageLoading />;
}

return (
<div>
<Row gutter={[16, 24]}>
<Col lg={8} md={24}>
<Card title="个人信息" bordered={false} loading={loading}>
{!loading && (
<div style={{ textAlign: 'center' }}>
<div
className={styles.avatarHolder}
onClick={() => {
setCropperModalOpen(true);
}}
>
<img src={currentUser.avatar} draggable={false} alt="" />
</div>
{renderUserInfo(currentUser)}
<Divider dashed />
<div className={styles.team}>
<div className={styles.teamTitle}>角色</div>
<Row gutter={36}>
{currentUser.roles &&
currentUser.roles.map((item: any) => (
<Col key={item.roleId} lg={24} xl={12}>
<TeamOutlined
style={{
marginRight: 8,
}}
/>
{item.roleName}
</Col>
))}
</Row>
</div>
</div>
)}
</Card>
</Col>
<Col lg={16} md={24}>
<Card
bordered={false}
tabList={operationTabList}
activeTabKey={tabKey}
onTabChange={(_tabKey: string) => {
setTabKey(_tabKey as tabKeyType);
}}
<div style={{ height: '100%' }}>
<PageTitle title="个人中心"></PageTitle>
<div className={styles['user-center']}>
<div
className={styles.avatarHolder}
onClick={() => {
setCropperModalOpen(true);
}}
>
<img src={currentUser.avatar || DefaultAvatar} draggable={false} alt="" />
</div>
{renderUserInfo(currentUser)}
{/* <div className={styles['user-center__buttons']}>
<Button
type="primary"
size="large"
style={{ marginRight: 50 }}
onClick={() => setInfoModalOpen(true)}
>
{renderChildrenByTabKey(tabKey)}
</Card>
</Col>
</Row>
修改基本信息
</Button>
<Button type="primary" size="large" onClick={() => setRestModalOpen(true)}>
重置密码
</Button>
</div> */}
</div>

<AvatarCropper
onFinished={() => {
setCropperModalOpen(false);
@@ -185,6 +184,17 @@ const Center: React.FC = () => {
open={cropperModalOpen}
data={currentUser.avatar}
/>

<BaseInfoModal
open={infoModalOpen}
values={currentUser}
onFinished={handleBaseInfoChange}
></BaseInfoModal>

<ResetPasswordModal
open={resetModalOpen}
onFinished={handleResetPassword}
></ResetPasswordModal>
</div>
);
};


+ 1
- 1
react-ui/src/pages/Workspace/components/UserSpace/index.tsx View File

@@ -31,7 +31,7 @@ function UserSpace({ users = [] }: UserSpaceProps) {
}
></Avatar>
<div className={styles['user-space__name']}>{currentUser?.nickName}</div>
<div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div>
<div className={styles['user-space__role']}>{currentUser?.roles?.[0]?.roleName}</div>
<Divider
dashed
style={{ borderColor: 'rgba(22, 100, 255, 0.19)', margin: '20px 0' }}


Loading…
Cancel
Save