个人中心

pull/65/head
awenes 2023-10-06 07:07:45 +08:00
parent 485e0a34fa
commit 890cb5c3f8
34 changed files with 2172 additions and 38 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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";
}

View File

@ -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',
},
/*欢迎页*/
{

View File

@ -113,7 +113,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, loading }) => {
heightLayoutHeader: showBanner ? 78 : 56,
},
pageContainer: {
paddingBlockPageContainerContent: 6,
paddingBlockPageContainerContent: 12,
paddingInlinePageContainerContent: 24,
},
},

View File

@ -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',

View File

@ -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': '退出登录',

View File

@ -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': '用户绑定',

View File

@ -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;

View File

@ -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;

View File

@ -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>
</>
);
};

View File

@ -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;

View File

@ -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>,
);
};

View File

@ -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;

View File

@ -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 },
};

View File

@ -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',
}

View File

@ -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',
}

View File

@ -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;

View File

@ -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': '修改手机号',
};

View File

@ -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;
});
}

View File

@ -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)];
});
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -117,7 +117,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, loading }) => {
heightLayoutHeader: showBanner ? 78 : 56,
},
pageContainer: {
paddingBlockPageContainerContent: 6,
paddingBlockPageContainerContent: 12,
paddingInlinePageContainerContent: 24,
},
},