mirror of https://gitee.com/topiam/eiam
⚡ 个人中心
parent
485e0a34fa
commit
890cb5c3f8
|
@ -20,9 +20,6 @@ package cn.topiam.employee.audit.entity;
|
|||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
|
||||
import cn.topiam.employee.support.security.userdetails.UserType;
|
||||
|
||||
import lombok.Builder;
|
||||
|
|
|
@ -20,9 +20,6 @@ package cn.topiam.employee.audit.entity;
|
|||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.GeoPointField;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
|
||||
import cn.topiam.employee.support.geo.GeoLocationProvider;
|
||||
|
|
|
@ -20,9 +20,6 @@ package cn.topiam.employee.audit.entity;
|
|||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
|
||||
import cn.topiam.employee.audit.enums.TargetType;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
|
|
@ -19,9 +19,6 @@ package cn.topiam.employee.audit.entity;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* eiam-common - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.common.constant;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author SanLi
|
||||
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/10/5 15:11
|
||||
*/
|
||||
public class UserConstants {
|
||||
|
||||
/**
|
||||
* 忘记密码预认证
|
||||
*/
|
||||
public static final String PREPARE_FORGET_PASSWORD = "/prepare_forget_password";
|
||||
|
||||
/**
|
||||
* 忘记密码
|
||||
*/
|
||||
public static final String FORGET_PASSWORD = "/forget_password";
|
||||
|
||||
/**
|
||||
* 忘记密码发送验证码
|
||||
*/
|
||||
public static final String FORGET_PASSWORD_CODE = "/forget_password_code";
|
||||
}
|
|
@ -41,10 +41,10 @@ export default [
|
|||
component: './user/SessionExpired',
|
||||
},
|
||||
{
|
||||
name: 'account.center',
|
||||
path: '/user/center',
|
||||
name: 'account.profile',
|
||||
path: '/user/profile',
|
||||
hideInMenu: true,
|
||||
component: './user/Center',
|
||||
component: './user/Profile',
|
||||
},
|
||||
/*欢迎页*/
|
||||
{
|
||||
|
|
|
@ -113,7 +113,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, loading }) => {
|
|||
heightLayoutHeader: showBanner ? 78 : 56,
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 6,
|
||||
paddingBlockPageContainerContent: 12,
|
||||
paddingInlinePageContainerContent: 24,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -120,9 +120,9 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ children }) =
|
|||
|
||||
const menuItems: ItemType[] = [
|
||||
{
|
||||
key: 'center',
|
||||
key: 'profile',
|
||||
icon: <UserOutlined />,
|
||||
label: intl.formatMessage({ id: 'components.right_content.center' }),
|
||||
label: intl.formatMessage({ id: 'components.right_content.profile' }),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
|
|
|
@ -19,7 +19,7 @@ export default {
|
|||
'component.tagSelect.expand': '展开',
|
||||
'component.tagSelect.collapse': '收起',
|
||||
'component.tagSelect.all': '全部',
|
||||
'components.right_content.center': '个人中心',
|
||||
'components.right_content.profile': '个人中心',
|
||||
'components.right_content.change-password': '修改密码',
|
||||
'components.right_content.logout': '退出登录',
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
|||
'menu.exception.not-permission': '403',
|
||||
'menu.exception.not-find': '404',
|
||||
'menu.exception.server-error': '500',
|
||||
'menu.account.center': '个人中心',
|
||||
'menu.account.profile': '个人中心',
|
||||
'menu.account.settings': '个人设置',
|
||||
'menu.account.logout': '退出登录',
|
||||
'menu.social-bind': '用户绑定',
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { history } from '@@/core/history';
|
||||
import { GridContent, PageContainer } from '@ant-design/pro-components';
|
||||
import { useAsyncEffect } from 'ahooks';
|
||||
import { Menu } from 'antd';
|
||||
import type { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
import BaseView from './components/Base';
|
||||
import SecurityView from './components/Security';
|
||||
import { AccountSettingsStateKey } from './data.d';
|
||||
import classnames from 'classnames';
|
||||
import useStyle from './style';
|
||||
import queryString from 'query-string';
|
||||
import { useIntl, useLocation } from '@umijs/max';
|
||||
|
||||
const prefixCls = 'account';
|
||||
|
||||
type AccountSettingState = {
|
||||
mode: 'inline' | 'horizontal';
|
||||
selectKey: AccountSettingsStateKey;
|
||||
};
|
||||
|
||||
const AccountSettings = () => {
|
||||
const { wrapSSR, hashId } = useStyle(prefixCls);
|
||||
const location = useLocation();
|
||||
const query = queryString.parse(location.search);
|
||||
const { type } = query as { type: AccountSettingsStateKey };
|
||||
const intl = useIntl();
|
||||
const [initConfig, setInitConfig] = useState<AccountSettingState>({
|
||||
mode: 'inline',
|
||||
selectKey: AccountSettingsStateKey.base,
|
||||
});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (!type || !AccountSettingsStateKey[type]) {
|
||||
setInitConfig({ ...initConfig, selectKey: AccountSettingsStateKey.base });
|
||||
history.replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ type: AccountSettingsStateKey.base }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setInitConfig({ ...initConfig, selectKey: type });
|
||||
}, [type]);
|
||||
|
||||
const menu: ItemType[] = [
|
||||
{
|
||||
key: AccountSettingsStateKey.base,
|
||||
label: intl.formatMessage({
|
||||
id: 'page.user.profile.menu.base',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: AccountSettingsStateKey.security,
|
||||
label: intl.formatMessage({
|
||||
id: 'page.user.profile.menu.security',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const dom = useRef<HTMLDivElement>();
|
||||
|
||||
const resize = () => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!dom.current) {
|
||||
return;
|
||||
}
|
||||
let mode: 'inline' | 'horizontal' = 'inline';
|
||||
const { offsetWidth } = dom.current;
|
||||
if (dom.current.offsetWidth < 641 && offsetWidth > 400) {
|
||||
mode = 'horizontal';
|
||||
}
|
||||
if (window.innerWidth < 768 && offsetWidth > 400) {
|
||||
mode = 'horizontal';
|
||||
}
|
||||
setInitConfig({ ...initConfig, selectKey: type, mode: mode as AccountSettingState['mode'] });
|
||||
});
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (dom.current) {
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize);
|
||||
};
|
||||
}, [type]);
|
||||
|
||||
const renderChildren = () => {
|
||||
const { selectKey } = initConfig;
|
||||
switch (selectKey) {
|
||||
case AccountSettingsStateKey.base:
|
||||
return <BaseView />;
|
||||
case AccountSettingsStateKey.security:
|
||||
return <SecurityView />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return wrapSSR(
|
||||
<PageContainer className={classnames(`${prefixCls}`, hashId)}>
|
||||
<GridContent>
|
||||
<div
|
||||
className={classnames(`${prefixCls}-main`, hashId)}
|
||||
ref={(ref) => {
|
||||
if (ref) {
|
||||
dom.current = ref;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={classnames(`${prefixCls}-left`, hashId)}>
|
||||
<Menu
|
||||
mode={initConfig.mode}
|
||||
selectedKeys={[initConfig.selectKey]}
|
||||
onClick={({ key }) => {
|
||||
setInitConfig({
|
||||
...initConfig,
|
||||
selectKey: key as AccountSettingsStateKey,
|
||||
});
|
||||
history.replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({ type: key }),
|
||||
});
|
||||
}}
|
||||
items={menu}
|
||||
/>
|
||||
</div>
|
||||
<div className={classnames(`${prefixCls}-right`, hashId)}>
|
||||
<div className={classnames(`${prefixCls}-right-title`, hashId)}>
|
||||
{menu.map((i: any) => {
|
||||
if (i?.key === initConfig.selectKey) {
|
||||
return <div key={i}>{i.label}</div>;
|
||||
}
|
||||
return undefined;
|
||||
})}
|
||||
</div>
|
||||
{renderChildren()}
|
||||
</div>
|
||||
</div>
|
||||
</GridContent>
|
||||
</PageContainer>,
|
||||
);
|
||||
};
|
||||
export default AccountSettings;
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
ProForm,
|
||||
ProFormText,
|
||||
useStyle as useAntdStyle,
|
||||
} from '@ant-design/pro-components';
|
||||
import { App, Avatar, Button, Form, Skeleton, Upload } from 'antd';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { changeBaseInfo } from '../service';
|
||||
import { aesEcbEncrypt } from '@/utils/aes';
|
||||
import { onGetEncryptSecret } from '@/utils/utils';
|
||||
import { useAsyncEffect } from 'ahooks';
|
||||
import ImgCrop from 'antd-img-crop';
|
||||
import { uploadFile } from '@/services/upload';
|
||||
import { useModel } from '@umijs/max';
|
||||
import classnames from 'classnames';
|
||||
import { useIntl } from '@@/exports';
|
||||
|
||||
const prefixCls = 'account-base';
|
||||
|
||||
function useStyle() {
|
||||
return useAntdStyle('AccountBaseComponent', (token) => {
|
||||
return [
|
||||
{
|
||||
[`.${prefixCls}`]: {
|
||||
display: 'flex',
|
||||
'padding-top': '12px',
|
||||
[`&-left`]: {
|
||||
minWidth: '224px',
|
||||
maxWidth: '448px',
|
||||
},
|
||||
[`&-right`]: {
|
||||
flex: 1,
|
||||
'padding-inline-start': '104px',
|
||||
},
|
||||
[`&-avatar`]: {
|
||||
marginBottom: '12px',
|
||||
overflow: 'hidden',
|
||||
img: {
|
||||
width: '100%',
|
||||
},
|
||||
[`&-name`]: {
|
||||
verticalAlign: 'middle',
|
||||
backgroundColor: `${token.colorPrimary} !important`,
|
||||
},
|
||||
[`${token.antCls}-avatar`]: {
|
||||
[`&-string`]: {
|
||||
fontSize: '75px',
|
||||
},
|
||||
},
|
||||
[`&-title`]: {
|
||||
height: '22px',
|
||||
marginBottom: '8px',
|
||||
color: '@heading-color',
|
||||
fontSize: '@font-size-base',
|
||||
lineHeight: '22px',
|
||||
},
|
||||
[`&-button-view`]: {
|
||||
width: '144px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
},
|
||||
[`@media screen and (max-width: ${token.screenXL}px)`]: {
|
||||
[`.${prefixCls}`]: {
|
||||
flexDirection: 'column-reverse',
|
||||
[`&-right`]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
maxWidth: '448px',
|
||||
padding: '20px',
|
||||
},
|
||||
['&-avatar']: {
|
||||
['&-title']: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
export const FORM_ITEM_LAYOUT = {
|
||||
labelCol: {
|
||||
span: 5,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 19,
|
||||
},
|
||||
};
|
||||
|
||||
const BaseView = () => {
|
||||
const intl = useIntl();
|
||||
const useApp = App.useApp();
|
||||
const { wrapSSR, hashId } = useStyle();
|
||||
const [loading, setLoading] = useState<boolean>();
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const [avatarURL, setAvatarURL] = useState<string | undefined>(initialState?.currentUser?.avatar);
|
||||
const [name, setName] = useState<string>('');
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
setLoading(true);
|
||||
if (initialState && initialState.currentUser) {
|
||||
setAvatarURL(initialState?.currentUser?.avatar);
|
||||
setName(initialState?.currentUser?.fullName || initialState?.currentUser?.username);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [initialState]);
|
||||
|
||||
const handleFinish = async (values: Record<string, string>) => {
|
||||
//加密传输
|
||||
const publicSecret = await onGetEncryptSecret();
|
||||
if (publicSecret) {
|
||||
const { success } = await changeBaseInfo(
|
||||
aesEcbEncrypt(
|
||||
JSON.stringify({
|
||||
fullName: values.fullName,
|
||||
nickName: values.nickName,
|
||||
personalProfile: values.personalProfile,
|
||||
avatar: avatarURL,
|
||||
}),
|
||||
publicSecret,
|
||||
),
|
||||
);
|
||||
if (success) {
|
||||
useApp.message.success(intl.formatMessage({ id: 'app.update_success' }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 头像组件 方便以后独立,增加裁剪之类的功能
|
||||
*
|
||||
* @param avatar
|
||||
* @param name
|
||||
* @param callBack
|
||||
* @constructor
|
||||
*/
|
||||
const AvatarView = ({
|
||||
avatar,
|
||||
name,
|
||||
callBack,
|
||||
}: {
|
||||
avatar: string | undefined;
|
||||
name: string;
|
||||
callBack: any;
|
||||
}) => (
|
||||
<>
|
||||
<div className={classnames(`${prefixCls}-avatar-title`, hashId)}>
|
||||
{intl.formatMessage({ id: 'page.user.profile.base.avatar_title' })}
|
||||
</div>
|
||||
<div className={classnames(`${prefixCls}-avatar`, hashId)}>
|
||||
{avatar ? (
|
||||
<Avatar alt="avatar" shape={'circle'} size={144} src={avatar} />
|
||||
) : (
|
||||
<Avatar
|
||||
shape={'circle'}
|
||||
className={classnames(`${prefixCls}-avatar-name`, hashId)}
|
||||
size={144}
|
||||
>
|
||||
{name.substring(0, 1)}
|
||||
</Avatar>
|
||||
)}
|
||||
</div>
|
||||
<ImgCrop
|
||||
rotationSlider
|
||||
aspectSlider
|
||||
modalOk={intl.formatMessage({ id: 'app.confirm' })}
|
||||
modalCancel={intl.formatMessage({ id: 'app.cancel' })}
|
||||
>
|
||||
<Upload
|
||||
name="file"
|
||||
showUploadList={false}
|
||||
accept="image/png, image/jpeg"
|
||||
customRequest={async (files) => {
|
||||
if (!files.file) {
|
||||
return;
|
||||
}
|
||||
const { success, result, message } = await uploadFile(files.file);
|
||||
if (success && result) {
|
||||
callBack(result);
|
||||
return;
|
||||
}
|
||||
useApp.message.error(message);
|
||||
}}
|
||||
>
|
||||
<div className={classnames(`${prefixCls}-avatar-button-view`, hashId)}>
|
||||
<Button>
|
||||
<UploadOutlined />
|
||||
{intl.formatMessage({ id: 'page.user.profile.base.avatar_change_title' })}
|
||||
</Button>
|
||||
</div>
|
||||
</Upload>
|
||||
</ImgCrop>
|
||||
</>
|
||||
);
|
||||
|
||||
return wrapSSR(
|
||||
<div className={classnames(`${prefixCls}`, hashId)}>
|
||||
{loading ? (
|
||||
<Skeleton paragraph={{ rows: 8 }} />
|
||||
) : (
|
||||
<>
|
||||
<div className={classnames(`${prefixCls}-left`, hashId)}>
|
||||
<ProForm
|
||||
layout="horizontal"
|
||||
labelAlign={'left'}
|
||||
{...FORM_ITEM_LAYOUT}
|
||||
onFinish={handleFinish}
|
||||
submitter={{
|
||||
render: (p, dom) => {
|
||||
return <Form.Item wrapperCol={{ span: 19, offset: 5 }}>{dom}</Form.Item>;
|
||||
},
|
||||
searchConfig: {
|
||||
submitText: intl.formatMessage({ id: 'app.save' }),
|
||||
},
|
||||
resetButtonProps: {
|
||||
style: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
}}
|
||||
initialValues={{
|
||||
...initialState?.currentUser,
|
||||
phone: initialState?.currentUser?.phone?.split('-'),
|
||||
}}
|
||||
requiredMark={false}
|
||||
>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="username"
|
||||
readonly
|
||||
label={intl.formatMessage({ id: 'page.user.profile.base.form.username' })}
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="email"
|
||||
readonly
|
||||
label={intl.formatMessage({ id: 'page.user.profile.base.form.email' })}
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="phone"
|
||||
readonly
|
||||
label={intl.formatMessage({ id: 'page.user.profile.base.form.phone' })}
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="fullName"
|
||||
label={intl.formatMessage({ id: 'page.user.profile.base.form.full_name' })}
|
||||
allowClear={false}
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="nickName"
|
||||
label={intl.formatMessage({ id: 'page.user.profile.base.form.nick_name' })}
|
||||
allowClear={false}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({
|
||||
id: 'page.user.profile.base.form.nick_name.rule.0',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ProForm>
|
||||
</div>
|
||||
<div className={classnames(`${prefixCls}-right`, hashId)}>
|
||||
<AvatarView avatar={avatarURL} callBack={setAvatarURL} name={name} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>,
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseView;
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { FieldNames, ServerExceptionStatus } from '../constant';
|
||||
import { changeEmail, prepareChangeEmail } from '../service';
|
||||
import { aesEcbEncrypt } from '@/utils/aes';
|
||||
import { onGetEncryptSecret } from '@/utils/utils';
|
||||
import type { CaptFieldRef, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, ProFormCaptcha, ProFormText } from '@ant-design/pro-components';
|
||||
import { App, Spin } from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FormLayout } from './constant';
|
||||
import { useIntl } from '@@/exports';
|
||||
|
||||
export default (props: {
|
||||
visible: boolean;
|
||||
prefixCls: string;
|
||||
setVisible: (visible: boolean) => void;
|
||||
setRefresh: (visible: boolean) => void;
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const useApp = App.useApp();
|
||||
const { visible, setVisible, setRefresh } = props;
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
/**已发送验证码*/
|
||||
const [hasSendCaptcha, setHasSendCaptcha] = useState<boolean>(false);
|
||||
const captchaRef = useRef<CaptFieldRef>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setLoading(false);
|
||||
}, [visible]);
|
||||
return (
|
||||
<>
|
||||
<ModalForm
|
||||
title={intl.formatMessage({ id: 'page.user.profile.bind.totp.form.update_email' })}
|
||||
width={'560px'}
|
||||
formRef={formRef}
|
||||
labelAlign={'right'}
|
||||
preserve={false}
|
||||
layout={'horizontal'}
|
||||
{...FormLayout}
|
||||
autoFocusFirstInput
|
||||
open={visible}
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
maskClosable: false,
|
||||
onCancel: async () => {
|
||||
setVisible(false);
|
||||
setHasSendCaptcha(false);
|
||||
},
|
||||
}}
|
||||
onFinish={async (formData: Record<string, any>) => {
|
||||
if (!hasSendCaptcha) {
|
||||
useApp.message.error(
|
||||
intl.formatMessage({ id: 'page.user.profile.please_send_code.message' }),
|
||||
);
|
||||
return Promise.reject();
|
||||
}
|
||||
const { success } = await changeEmail(omit(formData, FieldNames.PASSWORD));
|
||||
if (success) {
|
||||
useApp.message.success(intl.formatMessage({ id: 'app.update_success' }));
|
||||
setVisible(false);
|
||||
setRefresh(true);
|
||||
setHasSendCaptcha(false);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
}}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<ProFormText.Password
|
||||
name={FieldNames.PASSWORD}
|
||||
label={intl.formatMessage({ id: 'page.user.profile.common.form.password' })}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'page.user.profile.common.form.password.placeholder',
|
||||
})}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({ id: 'page.user.profile.common.form.password.rule.0' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormCaptcha
|
||||
name={FieldNames.EMAIL}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'page.user.profile.modify_email.form.email.placeholder',
|
||||
})}
|
||||
label={intl.formatMessage({ id: 'page.user.profile.modify_email.form.email' })}
|
||||
fieldRef={captchaRef}
|
||||
phoneName={FieldNames.EMAIL}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({ id: 'page.user.profile.modify_email.form.email.rule.0' }),
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
message: intl.formatMessage({ id: 'page.user.profile.modify_email.form.email.rule.1' }),
|
||||
},
|
||||
]}
|
||||
onGetCaptcha={async (email) => {
|
||||
if (!(await formRef.current?.validateFields([FieldNames.PASSWORD]))) {
|
||||
return Promise.reject();
|
||||
}
|
||||
const publicSecret = await onGetEncryptSecret();
|
||||
if (publicSecret !== undefined) {
|
||||
//加密传输
|
||||
const { success, message, result, status } = await prepareChangeEmail(
|
||||
aesEcbEncrypt(
|
||||
JSON.stringify({
|
||||
email: email,
|
||||
password: formRef.current?.getFieldValue(FieldNames.PASSWORD),
|
||||
}),
|
||||
publicSecret,
|
||||
),
|
||||
);
|
||||
if (!success && status === ServerExceptionStatus.PASSWORD_VALIDATED_FAIL_ERROR) {
|
||||
formRef.current?.setFields([
|
||||
{ name: FieldNames.PASSWORD, errors: [`${message}`] },
|
||||
]);
|
||||
return Promise.reject();
|
||||
}
|
||||
if (success && result) {
|
||||
setHasSendCaptcha(true);
|
||||
useApp.message.success(intl.formatMessage({ id: 'app.send_successfully' }));
|
||||
return Promise.resolve();
|
||||
}
|
||||
useApp.message.error(message);
|
||||
captchaRef.current?.endTiming();
|
||||
return Promise.reject();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
label={intl.formatMessage({ id: 'page.user.profile.common.form.code' })}
|
||||
placeholder={intl.formatMessage({ id: 'page.user.profile.common.form.code.placeholder' })}
|
||||
name={FieldNames.OTP}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({ id: 'page.user.profile.common.form.code.rule.0' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Spin>
|
||||
</ModalForm>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { FieldNames } from '../constant';
|
||||
import { changePassword, prepareChangePassword } from '../service';
|
||||
import { aesEcbEncrypt } from '@/utils/aes';
|
||||
import { onGetEncryptSecret } from '@/utils/utils';
|
||||
import {
|
||||
ModalForm,
|
||||
ProFormCaptcha,
|
||||
ProFormDependency,
|
||||
ProFormInstance,
|
||||
ProFormRadio,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { App, Spin } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FormLayout } from './constant';
|
||||
import { FormattedMessage, useIntl, useModel } from '@@/exports';
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param props
|
||||
* @constructor
|
||||
*/
|
||||
const ModifyPassword = (props: {
|
||||
visible: boolean;
|
||||
prefixCls: string;
|
||||
setRefresh: (visible: boolean) => void;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}) => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const intl = useIntl();
|
||||
const { message } = App.useApp();
|
||||
const { visible, setVisible, setRefresh } = props;
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setLoading(false);
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
title={intl.formatMessage({ id: 'page.user.profile.modify_password.form' })}
|
||||
initialValues={{ channel: 'sms' }}
|
||||
width={'560px'}
|
||||
formRef={formRef}
|
||||
labelAlign={'right'}
|
||||
preserve={false}
|
||||
layout={'horizontal'}
|
||||
{...FormLayout}
|
||||
autoFocusFirstInput
|
||||
open={visible}
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
maskClosable: false,
|
||||
onCancel: async () => {
|
||||
setVisible(false);
|
||||
},
|
||||
}}
|
||||
onFinish={async (formData: Record<string, any>) => {
|
||||
const publicSecret = await onGetEncryptSecret();
|
||||
if (publicSecret) {
|
||||
//加密传输
|
||||
const { success, result } = await changePassword(
|
||||
aesEcbEncrypt(
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
newPassword: formData[FieldNames.NEW_PASSWORD] as string,
|
||||
verifyCode: formData[FieldNames.VERIFY_CODE] as string,
|
||||
channel: formData[FieldNames.CHANNEL] as string,
|
||||
}),
|
||||
publicSecret,
|
||||
),
|
||||
);
|
||||
if (success && result) {
|
||||
setVisible(false);
|
||||
message.success(intl.formatMessage({ id: 'page.user.profile.modify_password.success' }));
|
||||
setRefresh(true);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
return Promise.reject();
|
||||
}}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<ProFormText.Password
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.new_password.placeholder',
|
||||
})}
|
||||
label={intl.formatMessage({ id: 'page.user.profile.modify_password.form.new_password' })}
|
||||
name={FieldNames.NEW_PASSWORD}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.new_password.rule.0',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
name={FieldNames.CHANNEL}
|
||||
label={intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.verify-code-type.label',
|
||||
})}
|
||||
options={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.phone.label',
|
||||
}),
|
||||
value: 'sms',
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.mail.label',
|
||||
}),
|
||||
value: 'mail',
|
||||
},
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.verify-code-type.rule.0',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormDependency name={[FieldNames.CHANNEL]}>
|
||||
{({ channel }) => {
|
||||
if (channel === 'sms') {
|
||||
return (
|
||||
<ProFormText
|
||||
key={'sms'}
|
||||
label={intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.phone',
|
||||
})}
|
||||
name={'show'}
|
||||
initialValue={initialState?.currentUser?.phone}
|
||||
readonly
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ProFormText
|
||||
key={'mail'}
|
||||
label={intl.formatMessage({
|
||||
id: 'page.user.profile.modify_password.form.mail',
|
||||
})}
|
||||
name={'show'}
|
||||
initialValue={initialState?.currentUser?.email}
|
||||
readonly
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ProFormDependency>
|
||||
<ProFormCaptcha
|
||||
label={intl.formatMessage({ id: 'page.user.profile.modify_password.form.verify-code' })}
|
||||
fieldProps={{
|
||||
maxLength: 6,
|
||||
}}
|
||||
captchaProps={{}}
|
||||
phoneName={'show'}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'pages.login.captcha.placeholder',
|
||||
})}
|
||||
captchaTextRender={(timing, count) => {
|
||||
if (timing) {
|
||||
return `${count} ${intl.formatMessage({
|
||||
id: 'pages.login.phone.captcha-second-text',
|
||||
})}`;
|
||||
}
|
||||
return intl.formatMessage({
|
||||
id: 'pages.login.phone.get-opt-code',
|
||||
});
|
||||
}}
|
||||
name={FieldNames.VERIFY_CODE}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: <FormattedMessage id="pages.login.captcha.required" />,
|
||||
},
|
||||
]}
|
||||
onGetCaptcha={async () => {
|
||||
const validate = await formRef.current?.validateFields([FieldNames.CHANNEL]);
|
||||
if (!validate) {
|
||||
return;
|
||||
}
|
||||
let channel = formRef.current?.getFieldValue(FieldNames.CHANNEL);
|
||||
const publicSecret = await onGetEncryptSecret();
|
||||
if (publicSecret) {
|
||||
const { success } = await prepareChangePassword(
|
||||
aesEcbEncrypt(
|
||||
JSON.stringify({
|
||||
channel: channel as string,
|
||||
}),
|
||||
publicSecret,
|
||||
),
|
||||
);
|
||||
if (success) {
|
||||
message.success(
|
||||
intl.formatMessage({
|
||||
id: 'pages.login.phone.get-opt-code.success',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Spin>
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
||||
export default ModifyPassword;
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {FieldNames} from '../constant';
|
||||
import {changePhone} from '../service';
|
||||
import type {CaptFieldRef, ProFormInstance} from '@ant-design/pro-components';
|
||||
import {ModalForm, ProFormText, useStyle as useAntdStyle,} from '@ant-design/pro-components';
|
||||
import {App, ConfigProvider, Spin} from 'antd';
|
||||
import {omit} from 'lodash';
|
||||
import {useContext, useEffect, useRef, useState} from 'react';
|
||||
import {FormLayout} from './constant';
|
||||
import classnames from 'classnames';
|
||||
import {ConfigContext} from 'antd/es/config-provider';
|
||||
import {useIntl} from '@@/exports';
|
||||
|
||||
function useStyle(prefixCls: string) {
|
||||
const { getPrefixCls } = useContext(ConfigContext || ConfigProvider.ConfigContext);
|
||||
const antCls = `.${getPrefixCls()}`;
|
||||
return useAntdStyle('AccountModifyPhoneComponent', () => {
|
||||
return [
|
||||
{
|
||||
[`.${prefixCls}`]: {
|
||||
['&-captcha']: {
|
||||
[`div${antCls}-form-item-control-input`]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
export default (props: {
|
||||
visible: boolean;
|
||||
prefixCls: string;
|
||||
setVisible: (visible: boolean) => void;
|
||||
setRefresh: (visible: boolean) => void;
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const useApp = App.useApp();
|
||||
const { visible, setVisible, setRefresh, prefixCls } = props;
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const captchaRef = useRef<CaptFieldRef>();
|
||||
/**已发送验证码*/
|
||||
const [hasSendCaptcha, setHasSendCaptcha] = useState<boolean>(false);
|
||||
/**手机区域*/
|
||||
const [phoneRegion, setPhoneRegion] = useState<string>('86');
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
const { wrapSSR, hashId } = useStyle(prefixCls);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setLoading(false);
|
||||
}, [visible]);
|
||||
|
||||
return wrapSSR(
|
||||
<ModalForm
|
||||
title={intl.formatMessage({ id: 'page.user.profile.modify_email.form' })}
|
||||
width={'560px'}
|
||||
className={classnames(`${prefixCls}`, hashId)}
|
||||
formRef={formRef}
|
||||
labelAlign={'right'}
|
||||
preserve={false}
|
||||
layout={'horizontal'}
|
||||
{...FormLayout}
|
||||
autoFocusFirstInput
|
||||
open={visible}
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
maskClosable: false,
|
||||
onCancel: async () => {
|
||||
setVisible(false);
|
||||
setHasSendCaptcha(false);
|
||||
},
|
||||
}}
|
||||
onFinish={async (formData: Record<string, any>) => {
|
||||
if (!hasSendCaptcha) {
|
||||
useApp.message.error(intl.formatMessage({ id: 'page.user.profile.please_send_code.message' }));
|
||||
return Promise.reject();
|
||||
}
|
||||
const { success } = await changePhone(omit(formData, FieldNames.PASSWORD));
|
||||
if (success) {
|
||||
useApp.message.success(intl.formatMessage({ id: 'app.update_success' }));
|
||||
setVisible(false);
|
||||
setRefresh(true);
|
||||
setHasSendCaptcha(false);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
}}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<ProFormText.Password
|
||||
name={FieldNames.PASSWORD}
|
||||
label={intl.formatMessage({ id: 'page.user.profile.common.form.password' })}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'page.user.profile.common.form.password.placeholder',
|
||||
})}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({ id: 'page.user.profile.common.form.password.rule.0' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<ProFormText
|
||||
placeholder={intl.formatMessage({ id: 'page.user.profile.common.form.code.placeholder' })}
|
||||
label={intl.formatMessage({ id: 'page.user.profile.common.form.code' })}
|
||||
name={FieldNames.OTP}
|
||||
fieldProps={{ autoComplete: 'off' }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage({ id: 'page.user.profile.common.form.code.rule.0' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Spin>
|
||||
</ModalForm>,
|
||||
);
|
||||
};
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useModel } from '@umijs/max';
|
||||
import { useAsyncEffect } from 'ahooks';
|
||||
import { List, Skeleton } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import ModifyEmail from './ModifyEmail';
|
||||
import ModifyPassword from './ModifyPassword';
|
||||
import ModifyPhone from './ModifyPhone';
|
||||
import classnames from 'classnames';
|
||||
import { useStyle as useAntdStyle } from '@ant-design/pro-components';
|
||||
|
||||
type Unpacked<T> = T extends (infer U)[] ? U : T;
|
||||
|
||||
function useStyle(prefixCls: string) {
|
||||
return useAntdStyle('AccountSecurityComponent', (token) => {
|
||||
return [
|
||||
{
|
||||
[`.${prefixCls}`]: {
|
||||
'&-strong': {
|
||||
color: `${token.colorSuccess}`,
|
||||
},
|
||||
'&-medium': {
|
||||
color: `${token.colorWarning}`,
|
||||
},
|
||||
'&-weak': {
|
||||
color: `${token.colorError}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
const SecurityView = () => {
|
||||
const prefixCls = 'account-security';
|
||||
const { wrapSSR, hashId } = useStyle(prefixCls);
|
||||
|
||||
/**更新密码*/
|
||||
const [modifyPasswordVisible, setModifyPasswordVisible] = useState<boolean>(false);
|
||||
/**更新手机号*/
|
||||
const [modifyPhoneVisible, setModifyPhoneVisible] = useState<boolean>(false);
|
||||
/**更新邮箱*/
|
||||
const [modifyEmailVisible, setModifyEmailVisible] = useState<boolean>(false);
|
||||
/**刷新*/
|
||||
const [refresh, setRefresh] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>();
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
useAsyncEffect(async () => {
|
||||
setLoading(true);
|
||||
if (initialState && initialState?.currentUser) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [initialState]);
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (refresh) {
|
||||
setLoading(true);
|
||||
//获取当前用户信息
|
||||
const currentUser = await initialState?.fetchUserInfo?.();
|
||||
await setInitialState((s: any) => ({ ...s, currentUser: currentUser }));
|
||||
setRefresh(false);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [refresh]);
|
||||
|
||||
const passwordStrength = {
|
||||
strong: <span className={classnames(`${prefixCls}-strong`, hashId)}>强</span>,
|
||||
medium: <span className={classnames(`${prefixCls}-medium`, hashId)}>中</span>,
|
||||
weak: <span className={classnames(`${prefixCls}-weak`, hashId)}>弱</span>,
|
||||
};
|
||||
|
||||
const getData = () => [
|
||||
{
|
||||
title: '账户密码',
|
||||
description: (
|
||||
<>
|
||||
当前密码强度:
|
||||
{passwordStrength.strong}
|
||||
</>
|
||||
),
|
||||
actions: [
|
||||
<a
|
||||
key="Modify"
|
||||
onClick={() => {
|
||||
setModifyPasswordVisible(true);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>,
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '账户手机',
|
||||
description: initialState?.currentUser?.phone
|
||||
? `已绑定手机:${initialState?.currentUser?.phone}`
|
||||
: `暂未绑定`,
|
||||
actions: [
|
||||
<a
|
||||
key="Modify"
|
||||
onClick={() => {
|
||||
setModifyPhoneVisible(true);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>,
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '账户邮箱',
|
||||
description: initialState?.currentUser?.email
|
||||
? `已绑定邮箱:${initialState?.currentUser?.email}`
|
||||
: `暂未绑定`,
|
||||
actions: [
|
||||
<a
|
||||
key="Modify"
|
||||
onClick={() => {
|
||||
setModifyEmailVisible(true);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const data = getData();
|
||||
|
||||
return wrapSSR(
|
||||
<Skeleton loading={loading} paragraph={{ rows: 8 }}>
|
||||
<List<Unpacked<typeof data>>
|
||||
itemLayout="horizontal"
|
||||
className={classnames(`${prefixCls}`, hashId)}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item actions={item.actions}>
|
||||
<List.Item.Meta title={item.title} description={item.description} />
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
{/*更新密码*/}
|
||||
<ModifyPassword
|
||||
visible={modifyPasswordVisible}
|
||||
setRefresh={setRefresh}
|
||||
setVisible={(visible) => {
|
||||
setModifyPasswordVisible(visible);
|
||||
}}
|
||||
prefixCls={prefixCls}
|
||||
/>
|
||||
{/*更新手机号*/}
|
||||
<ModifyPhone
|
||||
visible={modifyPhoneVisible}
|
||||
setRefresh={setRefresh}
|
||||
setVisible={(visible) => {
|
||||
setModifyPhoneVisible(visible);
|
||||
}}
|
||||
prefixCls={prefixCls}
|
||||
/>
|
||||
{/*更新手机号*/}
|
||||
<ModifyEmail
|
||||
visible={modifyEmailVisible}
|
||||
setRefresh={setRefresh}
|
||||
setVisible={(visible) => {
|
||||
setModifyEmailVisible(visible);
|
||||
}}
|
||||
prefixCls={prefixCls}
|
||||
/>
|
||||
</Skeleton>,
|
||||
);
|
||||
};
|
||||
|
||||
export default SecurityView;
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export const FormLayout = {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 20 },
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* 服务异常状态
|
||||
*/
|
||||
export enum ServerExceptionStatus {
|
||||
/**密码验证失败错误 */
|
||||
PASSWORD_VALIDATED_FAIL_ERROR = 'password_validated_fail_error',
|
||||
/**无效的 MFA 代码错误 */
|
||||
INVALID_MFA_CODE_ERROR = 'invalid_mfa_code_error',
|
||||
/**MFA 未发现秘密错误 */
|
||||
BIND_MFA_NOT_FOUND_SECRET_ERROR = 'bind_mfa_not_found_secret_error',
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段名称
|
||||
*/
|
||||
export enum FieldNames {
|
||||
/**密码 */
|
||||
PASSWORD = 'password',
|
||||
/**OTP */
|
||||
OTP = 'otp',
|
||||
/**手机号 */
|
||||
PHONE = 'phone',
|
||||
/**邮箱 */
|
||||
EMAIL = 'email',
|
||||
/**新密码 */
|
||||
NEW_PASSWORD = 'newPassword',
|
||||
/**验证码*/
|
||||
VERIFY_CODE = 'verifyCode',
|
||||
CHANNEL = 'channel',
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* 账户信息
|
||||
*/
|
||||
export type AccountInfo = {
|
||||
/** 用户ID */
|
||||
id: string;
|
||||
avatar: string;
|
||||
username: string;
|
||||
phone: string;
|
||||
access: string;
|
||||
};
|
||||
|
||||
export interface GetBoundIdpList {
|
||||
code: string;
|
||||
name: string;
|
||||
type: string;
|
||||
category: string;
|
||||
bound: boolean;
|
||||
idpId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户菜单类型
|
||||
*/
|
||||
export enum AccountSettingsStateKey {
|
||||
base = 'base',
|
||||
security = 'security',
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* eiam-portal - Employee Identity and Access Management
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
@ -15,18 +15,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.portal.controller;
|
||||
import Profile from './Profile';
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* message
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2021/9/12 21:42
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/notice")
|
||||
public class NoticeController {
|
||||
}
|
||||
export default Profile;
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export default {
|
||||
'page.user.profile.menu.base': '基本设置',
|
||||
'page.user.profile.menu.security': '安全设置',
|
||||
'page.user.profile.menu.bind': '账号绑定',
|
||||
'page.user.profile.base.avatar_title': '头像',
|
||||
'page.user.profile.base.avatar_change_title': '更换头像',
|
||||
'page.user.profile.base.form.username': '用户名称',
|
||||
'page.user.profile.base.form.email': '邮箱',
|
||||
'page.user.profile.base.form.phone': '手机号',
|
||||
'page.user.profile.base.form.full_name': '姓名',
|
||||
'page.user.profile.base.form.nick_name': '昵称',
|
||||
'page.user.profile.base.form.nick_name.rule.0': '请输入您的昵称',
|
||||
'page.user.profile.common.form.password': '密码',
|
||||
'page.user.profile.common.form.password.placeholder': '请输入密码',
|
||||
'page.user.profile.common.form.password.rule.0': '请输入密码',
|
||||
|
||||
'page.user.profile.common.form.phone': '手机号',
|
||||
'page.user.profile.common.form.phone.placeholder': '请输入手机号',
|
||||
'page.user.profile.common.form.phone.rule.0': '手机号未填写',
|
||||
'page.user.profile.common.form.phone.rule.1': '手机号不合法',
|
||||
'page.user.profile.common.form.phone.rule.2': '手机号已存在',
|
||||
|
||||
'page.user.profile.common.form.code': '验证码',
|
||||
'page.user.profile.common.form.code.placeholder': '请输入验证码',
|
||||
'page.user.profile.common.form.code.rule.0': '请输入验证码',
|
||||
'page.user.profile.please_send_code.message': '请发送验证码',
|
||||
'page.user.profile.unbind': '解绑',
|
||||
'pages.account.unbind.confirm': '您确定要解除该平台绑定吗?',
|
||||
'page.user.profile.bind': '绑定',
|
||||
'page.user.profile.bind.success': '绑定成功',
|
||||
'page.user.profile.bind.totp': '绑定动态口令',
|
||||
'page.user.profile.bind.totp.form.verify': '身份验证',
|
||||
'page.user.profile.bind.totp.form.verify.placeholder': '输入密码确认身份',
|
||||
'page.user.profile.bind.totp.form.bind': '绑定动态口令',
|
||||
'page.user.profile.bind.totp.form.bind.placeholder': '使用移动端认证器绑定口令',
|
||||
'page.user.profile.bind.totp.form.bind.alert': '请使用市面常见认证器 APP,扫描下方二维码,完成绑定。',
|
||||
'page.user.profile.bind.totp.form.bind.paragraph':
|
||||
'扫码绑定后,请您输入移动端 APP 中的六位动态口令,完成本次绑定。',
|
||||
'page.user.profile.bind.totp.form.update_email': '修改邮箱',
|
||||
'page.user.profile.modify_email.form.email': '邮箱',
|
||||
'page.user.profile.modify_email.form.email.placeholder': '请输入邮箱',
|
||||
'page.user.profile.modify_email.form.email.rule.0': '请输入邮箱',
|
||||
'page.user.profile.modify_email.form.email.rule.1': '邮箱格式不正确',
|
||||
'page.user.profile.modify_password.form': '修改密码',
|
||||
'page.user.profile.modify_password.success': '修改成功,请重新登录',
|
||||
'page.user.profile.modify_password.form.new_password': '新密码',
|
||||
'page.user.profile.modify_password.form.new_password.placeholder': '请输入新密码',
|
||||
'page.user.profile.modify_password.form.new_password.rule.0': '请输入请输入新密码',
|
||||
'page.user.profile.modify_password.form.verify-code': '验证码',
|
||||
'page.user.profile.modify_password.form.verify-code-type.label': '验证方式',
|
||||
'page.user.profile.modify_password.form.verify-code-type.rule.0': '请选择验证方式',
|
||||
'page.user.profile.modify_password.form.phone.label': '短信',
|
||||
'page.user.profile.modify_password.form.mail.label': '邮件',
|
||||
'page.user.profile.modify_password.form.phone': '手机号',
|
||||
'page.user.profile.modify_password.form.mail': '邮箱',
|
||||
'page.user.profile.modify_email.form': '修改手机号',
|
||||
};
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { ParamCheckType } from '@/constant';
|
||||
import { request } from '@@/plugin-request/request';
|
||||
import { GetBoundIdpList } from './data.d';
|
||||
|
||||
/**
|
||||
* 准备修改手机号
|
||||
*
|
||||
* @param encrypt
|
||||
*/
|
||||
export async function prepareChangePhone(encrypt: string): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/prepare_change_phone`, {
|
||||
data: { encrypt: encrypt },
|
||||
method: 'POST',
|
||||
skipErrorHandler: true,
|
||||
}).catch(({ response: { data } }) => {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getBoundIdpList(): Promise<API.ApiResult<GetBoundIdpList[]>> {
|
||||
return request(`/api/v1/account/bound_idp`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
export async function unbindIdp(id: string): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/unbind_idp/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换手机
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
export async function changePhone(data: Record<string, string>): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/change_phone`, {
|
||||
data: data,
|
||||
method: 'PUT',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备更换邮箱
|
||||
*
|
||||
* @param encrypt
|
||||
*/
|
||||
export async function prepareChangeEmail(encrypt: string): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/prepare_change_email`, {
|
||||
data: { encrypt: encrypt },
|
||||
method: 'POST',
|
||||
skipErrorHandler: true,
|
||||
}).catch(({ response: { data } }) => {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换邮箱
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
export async function changeEmail(data: Record<string, string>): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/change_email`, {
|
||||
data: data,
|
||||
method: 'PUT',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换密码
|
||||
*
|
||||
* @param encrypt
|
||||
*/
|
||||
export async function changePassword(encrypt: string): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/change_password`, {
|
||||
data: { encrypt: encrypt },
|
||||
method: 'PUT',
|
||||
skipErrorHandler: true,
|
||||
}).catch(({ response: { data } }) => {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改基础信息
|
||||
*
|
||||
* @param encrypt
|
||||
*/
|
||||
export async function changeBaseInfo(encrypt: string): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/account/change_info`, {
|
||||
data: { encrypt: encrypt },
|
||||
method: 'PUT',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户信息
|
||||
*
|
||||
* @param type
|
||||
* @param value
|
||||
* @param id
|
||||
*/
|
||||
export async function userParamCheck(
|
||||
type: ParamCheckType,
|
||||
value: string,
|
||||
id?: string,
|
||||
): Promise<API.ApiResult<boolean>> {
|
||||
return request(`/api/v1/user/param_check`, {
|
||||
params: { id, type, value },
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备修改账户密码
|
||||
*/
|
||||
export async function prepareChangePassword(encrypt: string): Promise<API.ApiResult<boolean>> {
|
||||
return request('/api/v1/account/prepare_change_password', {
|
||||
method: 'POST',
|
||||
data: { encrypt: encrypt },
|
||||
skipErrorHandler: true,
|
||||
}).catch(({ response: { data } }) => {
|
||||
return data;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import type { GenerateStyle, ProAliasToken } from '@ant-design/pro-components';
|
||||
import { useStyle as useAntdStyle } from '@ant-design/pro-components';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { useContext } from 'react';
|
||||
|
||||
const { ConfigContext } = ConfigProvider;
|
||||
|
||||
interface AccountToken extends ProAliasToken {
|
||||
antCls: string;
|
||||
prefixCls: string;
|
||||
}
|
||||
|
||||
const genActionsStyle: GenerateStyle<AccountToken> = (token) => {
|
||||
const { prefixCls, antCls } = token;
|
||||
|
||||
return {
|
||||
[`${prefixCls}`]: {
|
||||
['&-main']: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
paddingTop: '16px',
|
||||
paddingBottom: '16px',
|
||||
'background-color': `${token.colorBgBase}`,
|
||||
},
|
||||
['&-left']: {
|
||||
width: '224px',
|
||||
[`${antCls}-menu-light${antCls}-menu-root${antCls}-menu-inline`]: {
|
||||
'border-inline-end': '1px solid rgba(5, 5, 5, 0.06)',
|
||||
height: '100%',
|
||||
},
|
||||
[`${antCls}-menu-light:not(${antCls}-menu-horizontal) ${antCls}-menu-item-selected`]: {
|
||||
'background-color': `${token.layout?.sider?.colorBgMenuItemSelected}`,
|
||||
color: `${token.layout?.sider?.colorTextMenuSelected}`,
|
||||
},
|
||||
},
|
||||
['&-right']: {
|
||||
flex: 1,
|
||||
padding: '8px 40px',
|
||||
[`${antCls}-list ${antCls}-list-item`]: {
|
||||
'padding-inline-start': 0,
|
||||
},
|
||||
['&-title']: {
|
||||
marginBottom: '12px',
|
||||
color: `${token.colorTextHeading}`,
|
||||
fontWeight: 500,
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
},
|
||||
},
|
||||
},
|
||||
[`@media screen and (max-width: ${token.screenMD}px)`]: {
|
||||
[`${prefixCls}`]: {
|
||||
['&-main']: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
['&-left']: {
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
},
|
||||
['&-right']: {
|
||||
padding: '40px',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default function useStyle(prefixCls?: string) {
|
||||
const { getPrefixCls } = useContext(ConfigContext || ConfigProvider.ConfigContext);
|
||||
const antCls = `.${getPrefixCls()}`;
|
||||
|
||||
return useAntdStyle('AccountToken', (token) => {
|
||||
const accountToken: AccountToken = {
|
||||
...token,
|
||||
prefixCls: `.${prefixCls}`,
|
||||
antCls,
|
||||
};
|
||||
|
||||
return [genActionsStyle(accountToken)];
|
||||
});
|
||||
}
|
|
@ -21,8 +21,6 @@ import java.time.LocalDateTime;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cn.topiam.employee.audit.entity.GeoLocation;
|
||||
import cn.topiam.employee.audit.entity.UserAgent;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
|
||||
|
@ -33,7 +31,9 @@ import com.querydsl.core.types.ExpressionUtils;
|
|||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
import cn.topiam.employee.audit.entity.AuditEntity;
|
||||
import cn.topiam.employee.audit.entity.GeoLocation;
|
||||
import cn.topiam.employee.audit.entity.QAuditEntity;
|
||||
import cn.topiam.employee.audit.entity.UserAgent;
|
||||
import cn.topiam.employee.audit.event.type.PortalEventType;
|
||||
import cn.topiam.employee.common.constant.CommonConstants;
|
||||
import cn.topiam.employee.common.entity.account.UserDetailEntity;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* 更改电子邮件入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2022/8/8 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更改电子邮件入参")
|
||||
public class ChangeEmailRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* OTP
|
||||
*/
|
||||
@NotEmpty(message = "OTP验证码不能为空")
|
||||
@Parameter(description = "OTP")
|
||||
private String otp;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@NotEmpty(message = "邮箱不能为空")
|
||||
@Parameter(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.topiam.employee.common.enums.MessageNoticeChannel;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 更改密码入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2022/8/8 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更改密码入参")
|
||||
public class ChangePasswordRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* 新密码
|
||||
*/
|
||||
@NotEmpty(message = "新密码不能为空")
|
||||
@Parameter(description = "新密码")
|
||||
private String newPassword;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
@NotEmpty(message = "验证码不能为空")
|
||||
@Parameter(description = "验证码")
|
||||
private String verifyCode;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
@NotNull(message = "消息类型不能为空")
|
||||
@Parameter(description = "消息类型")
|
||||
private MessageNoticeChannel channel;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* 更改手机号入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2022/8/8 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更改手机号入参")
|
||||
public class ChangePhoneRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* OTP
|
||||
*/
|
||||
@NotEmpty(message = "OTP验证码不能为空")
|
||||
@Parameter(description = "OTP")
|
||||
private String otp;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Parameter(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* 忘记密码入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2023/02/27 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "忘记密码入参")
|
||||
public class ForgetPasswordRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* 新密码
|
||||
*/
|
||||
@NotEmpty(message = "新密码不能为空")
|
||||
@Parameter(description = "新密码")
|
||||
private String newPassword;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2022/8/8 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "准备更改电子邮件入参")
|
||||
public class PrepareChangeEmailRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@NotEmpty(message = "邮箱不能为空")
|
||||
@Parameter(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Parameter(description = "密码")
|
||||
private String password;
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.topiam.employee.common.enums.MessageNoticeChannel;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
*准备更改密码入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2022/11/13 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "准备更改密码入参")
|
||||
public class PrepareChangePasswordRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
@NotNull(message = "消息类型不能为空")
|
||||
@Parameter(description = "消息类型")
|
||||
private MessageNoticeChannel channel;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
*准备更改手机号入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2022/8/8 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "准备更改手机号入参")
|
||||
public class PrepareChangePhoneRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754485L;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@NotEmpty(message = "手机号不能为空")
|
||||
@Parameter(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 手机号区域
|
||||
*/
|
||||
@NotEmpty(message = "手机号区域不能为空")
|
||||
@Parameter(description = "手机号区域")
|
||||
private String phoneRegion;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Parameter(description = "密码")
|
||||
private String password;
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* 忘记密码预认证
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2023/02/27 21:15
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "忘记密码预认证")
|
||||
public class PrepareForgetPasswordRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5681761697876754482L;
|
||||
|
||||
/**
|
||||
* 验证码接收者
|
||||
*/
|
||||
@NotEmpty(message = "邮箱/手机号不能为空")
|
||||
@Parameter(description = "验证码接收者(邮箱/手机号)")
|
||||
private String recipient;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
@NotEmpty(message = "验证码不能为空")
|
||||
@Parameter(description = "验证码")
|
||||
private String code;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* eiam-console - Employee Identity and Access Management
|
||||
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cn.topiam.employee.console.pojo.update.user;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* 编辑用户入参
|
||||
*
|
||||
* @author TopIAM
|
||||
* Created by support@topiam.cn on 2020/8/11 23:16
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "修改用户入参")
|
||||
public class UpdateUserInfoRequest implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -6616249172773611157L;
|
||||
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
@Schema(description = "姓名")
|
||||
private String fullName;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@Schema(description = "昵称")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
}
|
|
@ -117,7 +117,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, loading }) => {
|
|||
heightLayoutHeader: showBanner ? 78 : 56,
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 6,
|
||||
paddingBlockPageContainerContent: 12,
|
||||
paddingInlinePageContainerContent: 24,
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue