From 6883e31331af4633d1b7d74ea7deb5f972afa05d Mon Sep 17 00:00:00 2001 From: skyouc Date: 星期五, 23 五月 2025 20:02:19 +0800 Subject: [PATCH] 新增移库功能 --- rsf-admin/src/page/login/Register.jsx | 272 +++++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 199 insertions(+), 73 deletions(-) diff --git a/rsf-admin/src/page/login/Register.jsx b/rsf-admin/src/page/login/Register.jsx index 3e19a4f..c4169db 100644 --- a/rsf-admin/src/page/login/Register.jsx +++ b/rsf-admin/src/page/login/Register.jsx @@ -15,64 +15,131 @@ useTranslate, useLogin, useNotify, + email as validEmail, } from 'react-admin'; import { useForm, Controller } from 'react-hook-form'; import ProviderChoices from "./ProviderChoices"; import Visibility from '@mui/icons-material/Visibility'; import VisibilityOff from '@mui/icons-material/VisibilityOff'; +import { sendEmailCode, register } from '@/api/auth'; const Register = (props) => { const translate = useTranslate(); const notify = useNotify(); const login = useLogin(); const location = useLocation(); - const { systemInfo, tenantList } = props; + const { systemInfo } = props; - const { control, watch, handleSubmit, setValue } = useForm(); + const { control, watch, handleSubmit, setValue, setError, clearErrors } = useForm(); - const [loading, setLoading] = useState(false); - const [showPassword, setShowPassword] = useState(true); - + const email = watch('email'); const username = watch('username'); const password = watch('password'); const confirmPassword = watch('confirmPassword'); - const tenantId = watch('tenantId'); + const code = watch('code'); - useEffect(() => { - if (tenantList.length > 0 && !tenantId) { - setValue('tenantId', tenantList[0].id); + const [loading, setLoading] = useState(false); + const [codeLoading, setCodeLoading] = useState(false); + const [showPassword, setShowPassword] = useState(true); + const [isCounting, setIsCounting] = useState(false); + const [countdown, setCountdown] = useState(60); + + // send code + const handleSendCode = async () => { + if (!email) { + setError("email", { + message: translate('ra.validation.required') + }) + return; } - }, [tenantList, setValue]); + const emailError = validEmail()(email); + if (emailError) { + setError("email", { + message: translate("ra.validation.email") + }) + return; + } + clearErrors("email"); + setCodeLoading(true); + sendEmailCode({ email }).then(res => { + setCodeLoading(false); + const { code, msg, data } = res; + if (code === 200) { + notify(msg, { type: 'success', messageArgs: { _: msg } }); - const onSubmit = (data) => { - notify("Registration is not open yet"); - return; + const timestamp = Math.floor(Date.now() / 1000); + const expirationTime = timestamp + 60; + localStorage.setItem('codeExpirationTime', expirationTime); + + setIsCounting(true); + setCountdown(60); + } else if (code === 10005 || code === 10006) { + setError('email', { + message: msg + }) + } else { + notify(msg, { type: 'error', messageArgs: { _: msg } }); + } + }).catch((error) => { + setCodeLoading(false); + notify(error.message, { type: 'error', messageArgs: { _: error.message } }); + console.error(error); + }) + }; + + // countdown + useEffect(() => { + const codeExpirationTime = localStorage.getItem('codeExpirationTime'); + if (codeExpirationTime) { + const currentTimestamp = Math.floor(Date.now() / 1000); + const remainingTime = codeExpirationTime - currentTimestamp; + if (remainingTime > 0) { + setCountdown(remainingTime); + setIsCounting(true); + } + } + + const interval = setInterval(() => { + if (isCounting && countdown > 0) { + setCountdown(prev => prev - 1); + } else if (countdown <= 0) { + clearInterval(interval); + setIsCounting(false); + localStorage.removeItem('codeExpirationTime'); + } + }, 1000); + + return () => clearInterval(interval); + }, [countdown, isCounting]); + + // register + const onSubmit = (params) => { setLoading(true); - // js native confirm && root - login( - data, - location.state ? (location.state).nextPathname : '/' - ).catch((error) => { + register(params).then(res => { setLoading(false); - notify( - typeof error === 'string' - ? error - : typeof error === 'undefined' || !error.message - ? 'ra.auth.sign_in_error' - : error.message, - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : error && error.message - ? error.message - : undefined, - }, - } - ); - }); + const { code, msg, data } = res; + if (code === 200) { + notify(msg, { type: 'success', messageArgs: { _: msg } }); + // to login + login( + params, + location.state ? (location.state).nextPathname : '/' + ).catch(({ code, msg }) => { + setLoading(false); + notify(msg, { type: 'error', messageArgs: { _: msg } }); + }); + } else if (code === 10002) { + setError("username", { + message: msg + }) + } else { + notify(msg, { type: 'error', messageArgs: { _: msg } }); + } + }).catch((error) => { + setLoading(false); + notify(error.message, { type: 'error', messageArgs: { _: error.message } }); + console.error(error); + }) }; return ( @@ -85,46 +152,20 @@ > <Stack spacing={2}> <Controller - name="tenantId" - control={control} - rules={{ required: true }} - defaultValue={tenantList.length > 0 ? tenantList[0].id : ''} - render={({ field: { onChange, value, ref } }) => { - const selectedTenant = tenantList.find(tenant => tenant.id === value) || null; - return ( - <Autocomplete - options={tenantList} - getOptionLabel={(option) => option.name} - value={selectedTenant} - onChange={(_, newValue) => { - onChange(newValue ? newValue.id : ''); - }} - renderInput={(params) => ( - <TextField - {...params} - label={translate("page.login.tenant")} - variant="standard" - inputRef={ref} - /> - )} - /> - ); - }} - /> - - <Controller name="username" control={control} defaultValue="" rules={{ required: true }} - render={({ field }) => ( + render={({ field, fieldState: { error } }) => ( <TextField {...field} - label={translate('ra.auth.username')} + label={translate("page.login.username")} variant="standard" disabled={loading} autoFocus autoComplete="off" + error={!!error} + helperText={error?.message || ""} /> )} /> @@ -133,15 +174,23 @@ name="password" control={control} defaultValue="" - rules={{ required: true }} - render={({ field }) => ( + rules={{ + required: translate('ra.validation.required'), + pattern: { + value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d\.]{6,13}$/, + message: translate('page.settings.resetPwd.tip.pwdInputLimit'), + }, + }} + render={({ field, fieldState: { error } }) => ( <TextField {...field} - label={translate('ra.auth.password')} + label={translate("page.login.password")} type={showPassword ? 'text' : 'password'} variant="standard" disabled={loading} autoComplete="off" + error={!!error} + helperText={error?.message || ""} InputProps={{ endAdornment: ( <InputAdornment position="end"> @@ -164,8 +213,12 @@ name="confirmPassword" control={control} defaultValue="" - rules={{ required: true }} - render={({ field }) => ( + rules={{ + required: translate('ra.validation.required'), + validate: value => + value === password || translate('page.settings.resetPwd.tip.pwdNotMatch'), + }} + render={({ field, fieldState: { error } }) => ( <TextField {...field} label={translate('page.login.confirmPwd')} @@ -173,6 +226,8 @@ variant="standard" disabled={loading} autoComplete="off" + error={!!error} + helperText={error?.message || ""} InputProps={{ endAdornment: ( <InputAdornment position="end"> @@ -191,12 +246,83 @@ )} /> + <Controller + name="email" + control={control} + defaultValue="" + rules={{ + required: translate('ra.validation.required'), + pattern: { + value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + message: translate("ra.validation.email"), + }, + }} + render={({ field, fieldState: { error } }) => ( + <TextField + {...field} + label={translate('page.login.email')} + variant="standard" + disabled={loading} + // autoComplete="off" + error={!!error} + helperText={error ? error.message : ""} + /> + )} + /> + + <Box display="flex" alignItems="center" justifyContent='center' width="100%"> + <Controller + name="code" + control={control} + defaultValue="" + rules={{ + required: translate('ra.validation.required'), + }} + render={({ field, fieldState: { error } }) => ( + <TextField + {...field} + label={translate('page.login.code')} + variant="standard" + disabled={loading} + autoComplete="off" + error={!!error} + helperText={error ? error.message : ""} + sx={{ + width: '65%', + mr: 2, + }} + /> + )} + /> + + <Button + variant="outlined" + onClick={handleSendCode} + disabled={codeLoading || isCounting} + sx={{ + width: '35%', + mt: 1, + whiteSpace: 'nowrap', + }} + > + {codeLoading ? ( + <CircularProgress size={20} color="primary" sx={{ marginRight: 1 }} /> + ) : + isCounting ? ( + `${countdown}s` + ) : ( + translate('page.login.button.code') + ) + } + </Button> + </Box> + <Box /> <Button type="submit" variant="contained" - disabled={loading || !(tenantId && username && password && confirmPassword)} + disabled={loading || !(email && username && password && confirmPassword && code)} sx={{ backgroundColor: "#3D4BA7" }} -- Gitblit v1.9.1