From 50e4be3ef67dd2558470c630830f1518ea2f12bc Mon Sep 17 00:00:00 2001 From: xmdhs Date: Sat, 25 Nov 2023 00:57:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=BE=E5=9B=9E=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Route.tsx | 8 +- frontend/src/apis/apis.ts | 24 ++++ frontend/src/views/Forgot.tsx | 120 ++++++++++++++++++ frontend/src/views/Login.tsx | 19 ++- .../views/{SignUpEmail.tsx => SendEmail.tsx} | 16 +-- handle/handelerror/error.go | 1 + handle/user.go | 82 ++++++++++-- model/model.go | 8 +- server/route/route.go | 8 +- server/wire_gen.go | 2 +- service/email/email.go | 13 +- service/user.go | 53 +++++++- 12 files changed, 314 insertions(+), 40 deletions(-) create mode 100644 frontend/src/views/Forgot.tsx rename frontend/src/views/{SignUpEmail.tsx => SendEmail.tsx} (93%) diff --git a/frontend/src/Route.tsx b/frontend/src/Route.tsx index 0d5597a..6ff6962 100644 --- a/frontend/src/Route.tsx +++ b/frontend/src/Route.tsx @@ -9,7 +9,9 @@ import Layout from '@/views/Layout' import UserAdmin from "@/views/admin/UserAdmin"; import NeedLogin from "@/components/NeedLogin"; import Index from "@/views/Index"; -import SignUpEmail from "@/views/SignUpEmail"; +import SendEmail from "@/views/SendEmail"; +import { sendForgotEmail, sendRegEmail } from "@/apis/apis"; +import Forgot from "@/views/Forgot"; const router = createBrowserRouter([ { path: "*", Component: Root }, @@ -24,7 +26,9 @@ function Root() { 404

} /> } /> } /> - } /> + } /> + } /> + } /> }> } /> diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts index fa53ba4..b996595 100644 --- a/frontend/src/apis/apis.ts +++ b/frontend/src/apis/apis.ts @@ -129,4 +129,28 @@ export async function sendRegEmail(email: string, captchaToken: string) { }) }) return await apiGet(r) +} + +export async function sendForgotEmail(email: string, captchaToken: string) { + const r = await fetch(root() + "/api/v1/user/forgot_email", { + method: "POST", + body: JSON.stringify({ + "email": email, + "captchaToken": captchaToken + }) + }) + return await apiGet(r) +} + + +export async function forgotPassWord(email: string, emailJwt: string, password: string) { + const r = await fetch(root() + "/api/v1/user/forgot", { + method: "POST", + body: JSON.stringify({ + "email": email, + "emailJwt": emailJwt, + "passWord": password, + }) + }) + return await apiGet(r) } \ No newline at end of file diff --git a/frontend/src/views/Forgot.tsx b/frontend/src/views/Forgot.tsx new file mode 100644 index 0000000..f78d79e --- /dev/null +++ b/frontend/src/views/Forgot.tsx @@ -0,0 +1,120 @@ +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import TextField from '@mui/material/TextField'; +import { useTitle } from 'ahooks'; +import { useEffect, useState } from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import Loading from '@/components/Loading'; +import { produce } from 'immer'; +import { forgotPassWord } from '@/apis/apis'; +import { useNavigate } from 'react-router-dom'; + +export default function Forgot() { + const [err, setErr] = useState("") + useTitle("找回密码") + const [passerr, setPasserr] = useState("") + const [pass, setPass] = useState({ + pass1: "", + pass2: "", + }) + const [load, setLoad] = useState(false) + const [email, setEmail] = useState("") + const [code, setCode] = useState("") + const navigate = useNavigate(); + + useEffect(() => { + if (pass.pass1 != pass.pass2 && pass.pass2 != "") { + setPasserr("密码不相等") + return + } + setPasserr("") + }, [pass.pass1, pass.pass2]) + + const u = new URL(location.href) + + useEffect(() => { + setEmail(u.searchParams.get("email") ?? "") + setCode(u.searchParams.get("code") ?? "") + }, [u.searchParams]) + + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + setLoad(true) + forgotPassWord(email, code, pass.pass1).then(() => { + navigate("/") + }).catch(e => { + setErr(String(e)) + }).finally(() => { setLoad(false) }) + } + + + return ( + + + + + + + + 找回密码 + + + + + setPass(produce(v => { v.pass1 = p.target.value }))} + autoComplete="new-password" + /> + + + setPass(produce(v => { v.pass2 = p.target.value }))} + autoComplete="new-password" + /> + + + + + + + setErr("")} severity="error">{err} + + + {load && } + + ) +} \ No newline at end of file diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx index a4fe5f0..8d1127d 100644 --- a/frontend/src/views/Login.tsx +++ b/frontend/src/views/Login.tsx @@ -13,7 +13,7 @@ import Snackbar from '@mui/material/Snackbar'; import Alert from '@mui/material/Alert'; import { useSetAtom } from 'jotai'; import { token, user } from '@/store/store' -import { login } from '@/apis/apis' +import { getConfig, login } from '@/apis/apis' import { Link as RouterLink, useNavigate } from "react-router-dom"; import Loading from '@/components/Loading' import CheckInput, { refType } from '@/components/CheckInput' @@ -21,6 +21,7 @@ import useTitle from '@/hooks/useTitle'; import CaptchaWidget from '@/components/CaptchaWidget'; import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' import { ApiErr } from '@/apis/error'; +import { useRequest } from 'ahooks'; @@ -35,6 +36,16 @@ export default function SignIn() { const captchaRef = React.useRef(null) const [captchaToken, setCaptchaToken] = useState(""); + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -66,7 +77,7 @@ export default function SignIn() { switch (v.code) { case 10: setErr("验证码错误") - return + return case 6: setErr("密码或用户名错误") return @@ -137,9 +148,9 @@ export default function SignIn() { - {/* + {server.data?.NeedEmail && 忘记密码? - */} + } diff --git a/frontend/src/views/SignUpEmail.tsx b/frontend/src/views/SendEmail.tsx similarity index 93% rename from frontend/src/views/SignUpEmail.tsx rename to frontend/src/views/SendEmail.tsx index 203ff79..7201c54 100644 --- a/frontend/src/views/SignUpEmail.tsx +++ b/frontend/src/views/SendEmail.tsx @@ -12,7 +12,7 @@ import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; import { useRequest, useTitle } from 'ahooks'; -import { getConfig, sendRegEmail } from '@/apis/apis'; +import { getConfig } from '@/apis/apis'; import { useEffect, useRef, useState } from 'react'; import Snackbar from '@mui/material/Snackbar'; import Alert from '@mui/material/Alert'; @@ -26,14 +26,14 @@ import { useNavigate } from "react-router-dom"; import { ApiErr } from '@/apis/error'; import Loading from '@/components/Loading'; -export default function SignUpEmail() { +export default function SendEmail({ title, anyEmail = false, sendService }: { title: string, anyEmail?: boolean, sendService: (email: string, captchaToken: string) => Promise }) { const [err, setErr] = useState(""); const [domain, setDomain] = useState(""); const [email, setEmail] = useState("") const captchaRef = useRef(null) const [captchaToken, setCaptchaToken] = useState(""); const [open, setOpen] = useState(false); - useTitle("注册") + useTitle(title) const navigate = useNavigate(); const [helperText, setHelperText] = useState("") const [loading, setLoading] = useState(false); @@ -73,7 +73,7 @@ export default function SignUpEmail() { return email })() - if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)){ + if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)) { setHelperText("邮箱格式错误") return } @@ -82,7 +82,7 @@ export default function SignUpEmail() { return } setLoading(true) - sendRegEmail(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => { + sendService(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => { captchaRef.current?.reload() console.warn(e) if (e instanceof ApiErr) { @@ -120,7 +120,7 @@ export default function SignUpEmail() { - 输入邮箱 + {title} @@ -135,7 +135,7 @@ export default function SignUpEmail() { onChange={emailonChange} /> { - server.data?.AllowDomain.length != 0 && + server.data?.AllowDomain.length != 0 && !anyEmail && 域名