import Icon from '@ant-design/icons' import '@/assets/css/pages/user/index.scss' import { COLOR_BACKGROUND, COLOR_ERROR, COLOR_PRODUCTION, DATABASE_UPDATE_SUCCESS, PERMISSION_ACCESS_DENIED, PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR } from '@/constants/common.constants' import { utcToLocalTime } from '@/util/datetime' import { getUserInfo, removeToken } from '@/util/auth' import { r_sys_user_info_change_password, r_sys_user_info_update } from '@/services/system' import { r_auth_two_factor_create, r_auth_two_factor_remove, r_auth_two_factor_validate } from '@/services/auth' import { r_api_avatar_random_base64 } from '@/services/api/avatar' import FitFullscreen from '@/components/common/FitFullscreen' import Card from '@/components/common/Card' import FlexBox from '@/components/common/FlexBox' import HideScrollbar from '@/components/common/HideScrollbar' interface ChangePasswordFields extends UserUpdatePasswordParam { newPasswordConfirm: string } const User = () => { const [modal, contextHolder] = AntdModal.useModal() const [form] = AntdForm.useForm() const formValues = AntdForm.useWatch([], form) const [twoFactorForm] = AntdForm.useForm<{ twoFactorCode: string }>() const [isLoading, setIsLoading] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmittable, setIsSubmittable] = useState(false) const [isGettingAvatar, setIsGettingAvatar] = useState(false) const [avatar, setAvatar] = useState('') const [userWithPowerInfoVo, setUserWithPowerInfoVo] = useState() const [changePasswordForm] = AntdForm.useForm() const handleOnCopyToClipboard = (username?: string) => { return username ? () => { void navigator.clipboard .writeText(new URL(`/store/${username}`, import.meta.env.VITE_UI_URL).href) .then(() => { void message.success('已复制到剪切板') }) } : undefined } const handleOnReset = () => { getProfile() } const handleOnSave = () => { if (isSubmitting) { return } setIsSubmitting(true) void message.loading({ content: '保存中', key: 'LOADING', duration: 0 }) void r_sys_user_info_update({ avatar, nickname: formValues.nickname }) .then((res) => { const response = res.data switch (response.code) { case DATABASE_UPDATE_SUCCESS: void message.success('保存成功') getProfile() break default: void message.error('保存失败,请稍后重试') } }) .finally(() => { setIsSubmitting(false) void message.destroy('LOADING') }) } const handleOnChangeAvatar = () => { if (isLoading || isGettingAvatar) { return } setIsGettingAvatar(true) void r_api_avatar_random_base64() .then((res) => { const response = res.data if (response.success) { response.data?.base64 && setAvatar(response.data.base64) } }) .finally(() => { setIsGettingAvatar(false) }) } const handleOnChangePassword = () => { changePasswordForm.resetFields() void modal.confirm({ centered: true, maskClosable: true, icon: <>, title: ( <> 修改密码 ), footer: (_, { OkBtn, CancelBtn }) => ( <> ), content: ( { setTimeout(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access changePasswordForm?.getFieldInstance('originalPassword').focus() }, 50) }} > ({ validator(_, value) { if (!value || getFieldValue('newPassword') === value) { return Promise.resolve() } return Promise.reject(new Error('两次密码输入必须一致')) } }) ]} > ), onOk: () => changePasswordForm.validateFields().then( () => { return new Promise((resolve, reject) => { void r_sys_user_info_change_password({ originalPassword: changePasswordForm.getFieldValue( 'originalPassword' ) as string, newPassword: changePasswordForm.getFieldValue( 'newPassword' ) as string }).then((res) => { const response = res.data switch (response.code) { case DATABASE_UPDATE_SUCCESS: void message.success('密码修改成功,请重新登录') removeToken() notification.info({ message: '已退出登录', icon: ( ) }) setTimeout(() => { window.location.reload() }, 1500) resolve() break case PERMISSION_ACCESS_DENIED: void message.error('拒绝访问') resolve() break case PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR: void message.warning('原密码错误,请重新输入') reject(response.msg) break default: void message.error('出错了,请稍后重试') resolve() } }) }) }, () => { return new Promise((_, reject) => { reject('输入有误') }) } ) }) } const handleOnChangeTwoFactor = (enable: boolean) => { return () => { twoFactorForm.resetFields() if (enable) { void modal.confirm({ centered: true, maskClosable: true, focusTriggerAfterClose: false, title: '双因素', footer: (_, { OkBtn, CancelBtn }) => ( <> ), content: '确定解除双因素?', onOk: () => { void modal.confirm({ centered: true, maskClosable: true, title: '解除双因素', footer: (_, { OkBtn, CancelBtn }) => ( <> ), content: ( <> { setTimeout(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-call twoFactorForm ?.getFieldInstance('twoFactorCode') // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access .focus() }, 50) }} > ), onOk: () => twoFactorForm.validateFields().then( () => { return new Promise((resolve) => { void r_auth_two_factor_remove({ code: twoFactorForm.getFieldValue( 'twoFactorCode' ) as string }) .then((res) => { const response = res.data if (response.success) { void message.success('解绑成功') getProfile() } else { void message.error('解绑失败,请稍后重试') } }) .finally(() => { resolve() }) }) }, () => { return new Promise((_, reject) => { reject('输入有误') }) } ) }) } }) } else { if (isLoading) { return } setIsLoading(true) void message.loading({ content: '加载中', key: 'LOADING', duration: 0 }) void r_auth_two_factor_create() .then((res) => { message.destroy('LOADING') const response = res.data if (response.success) { void modal.confirm({ centered: true, maskClosable: true, title: '绑定双因素', footer: (_, { OkBtn, CancelBtn }) => ( <> ), content: ( <> 请使用身份验证器APP(eg. Microsoft Authenticator, Google Authenticator)扫描二维码,并在下方输入显示的动态二维码进行绑定 { setTimeout(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-call twoFactorForm ?.getFieldInstance('twoFactorCode') // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access .focus() }, 50) }} > ), onOk: () => twoFactorForm.validateFields().then( () => { return new Promise((resolve) => { void r_auth_two_factor_validate({ code: twoFactorForm.getFieldValue( 'twoFactorCode' ) as string }) .then((res) => { const response = res.data if (response.success) { void message.success('绑定成功') getProfile() } else { void message.error( '绑定失败,请稍后重试' ) } }) .finally(() => { resolve() }) }) }, () => { return new Promise((_, reject) => { reject('输入有误') }) } ) }) } else { void message.error('获取双因素绑定二维码失败,请稍后重试') } }) .catch(() => { message.destroy('LOADING') }) .finally(() => { setIsLoading(false) }) } } } const getProfile = () => { if (isLoading) { return } setIsLoading(true) void getUserInfo(true) .then((userWithPowerInfoVo) => { setAvatar(userWithPowerInfoVo.userInfo.avatar) form.setFieldValue('nickname', userWithPowerInfoVo.userInfo.nickname) setUserWithPowerInfoVo(userWithPowerInfoVo) void form.validateFields() }) .finally(() => { setIsLoading(false) }) } useEffect(() => { form.validateFields({ validateOnly: true }).then( () => { setIsSubmittable(true) }, () => { setIsSubmittable(false) } ) }, [formValues]) useEffect(() => { getProfile() }, []) return ( <>
} size={144} style={{ background: COLOR_BACKGROUND, cursor: 'pointer' }} className={'avatar'} onClick={handleOnChangeAvatar} />
{userWithPowerInfoVo?.userInfo.nickname}
{userWithPowerInfoVo?.username && new URL( `/store/${userWithPowerInfoVo.username}`, import.meta.env.VITE_UI_URL ).href}
档案管理
重置 保存
昵称
用户名
邮箱
注册时间
上次登录 IP
上次登录时间
密码
双因素
{contextHolder} ) } export default User