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 |  324 ++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 252 insertions(+), 72 deletions(-)

diff --git a/rsf-admin/src/page/login/Register.jsx b/rsf-admin/src/page/login/Register.jsx
index ca2f563..c4169db 100644
--- a/rsf-admin/src/page/login/Register.jsx
+++ b/rsf-admin/src/page/login/Register.jsx
@@ -7,64 +7,139 @@
     Button,
     TextField,
     Stack,
-    Autocomplete
+    Autocomplete,
+    InputAdornment,
+    IconButton,
 } from '@mui/material';
 import {
     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 { tab, 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 email = watch('email');
     const username = watch('username');
     const password = watch('password');
-    const tenantId = watch('tenantId');
+    const confirmPassword = watch('confirmPassword');
+    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) => {
+                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 (
@@ -77,45 +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 || ""}
                             />
                         )}
                     />
@@ -124,25 +174,155 @@
                         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')}
-                                type="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">
+                                            <IconButton
+                                                aria-label="toggle password visibility"
+                                                onClick={() => setShowPassword((show) => !show)}
+                                                onMouseDown={(event) => { event.preventDefault() }}
+                                                edge="end"
+                                            >
+                                                {showPassword ? <VisibilityOff /> : <Visibility />}
+                                            </IconButton>
+                                        </InputAdornment>
+                                    ),
+                                }}
                             />
                         )}
                     />
 
-                    <Box mt={10}></Box>
+                    <Controller
+                        name="confirmPassword"
+                        control={control}
+                        defaultValue=""
+                        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')}
+                                type={showPassword ? 'text' : 'password'}
+                                variant="standard"
+                                disabled={loading}
+                                autoComplete="off"
+                                error={!!error}
+                                helperText={error?.message || ""}
+                                InputProps={{
+                                    endAdornment: (
+                                        <InputAdornment position="end">
+                                            <IconButton
+                                                aria-label="toggle password visibility"
+                                                onClick={() => setShowPassword((show) => !show)}
+                                                onMouseDown={(event) => { event.preventDefault() }}
+                                                edge="end"
+                                            >
+                                                {showPassword ? <VisibilityOff /> : <Visibility />}
+                                            </IconButton>
+                                        </InputAdornment>
+                                    ),
+                                }}
+                            />
+                        )}
+                    />
+
+                    <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)}
+                        disabled={loading || !(email && username && password && confirmPassword && code)}
                         sx={{
                             backgroundColor: "#3D4BA7"
                         }}

--
Gitblit v1.9.1