Refactor: captcha (#64)

* Refactor: captcha

* Fix: 重复获取验证码
This commit is contained in:
topjohncian 2021-03-22 18:28:14 +08:00 committed by GitHub
parent 51c246ef02
commit 22b519f5ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 818 additions and 469 deletions

View File

@ -75,6 +75,7 @@
"react-dplayer": "^0.4.1",
"react-hotkeys": "^2.0.0",
"react-lazy-load-image-component": "^1.3.2",
"react-load-script": "^0.0.6",
"react-monaco-editor": "^0.36.0",
"react-pdf": "^4.1.0",
"react-photo-view": "^0.4.0",

View File

@ -31,6 +31,7 @@ import Share from "./component/Admin/Share/Share";
import Download from "./component/Admin/Task/Download";
import Task from "./component/Admin/Task/Task";
import Import from "./component/Admin/File/Import";
import Captcha from "./component/Admin/Setting/Captcha";
const useStyles = makeStyles((theme) => ({
root: {
@ -114,6 +115,10 @@ export default function Admin() {
<ImageSetting />
</Route>
<Route path={`${path}/captcha`}>
<Captcha />
</Route>
<Route path={`${path}/policy`} exact>
<Policy />
</Route>

View File

@ -10,6 +10,7 @@ import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Code from "@material-ui/icons/Code";
import { lighten, makeStyles, useTheme } from "@material-ui/core/styles";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
@ -219,6 +220,11 @@ const items = [
path: "image",
icon: <Image />,
},
{
title: "验证码",
path: "captcha",
icon: <Code />,
},
],
},
{

View File

@ -2,7 +2,6 @@ import Button from "@material-ui/core/Button";
import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormHelperText from "@material-ui/core/FormHelperText";
import Input from "@material-ui/core/Input";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select";
@ -14,7 +13,6 @@ import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../../../actions";
import API from "../../../middleware/Api";
import AlertDialog from "../Dialogs/Alert";
import Link from "@material-ui/core/Link";
const useStyles = makeStyles((theme) => ({
root: {
@ -46,9 +44,6 @@ export default function Access() {
reg_captcha: "0",
forget_captcha: "0",
authn_enabled: "0",
captcha_IsUseReCaptcha: "0",
captcha_ReCaptchaKey: "defaultKey",
captcha_ReCaptchaSecret: "defaultSecret",
});
const [siteURL, setSiteURL] = useState("");
const [groups, setGroups] = useState([]);
@ -248,86 +243,6 @@ export default function Access() {
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={
options.captcha_IsUseReCaptcha ===
"1"
}
onChange={handleChange(
"captcha_IsUseReCaptcha"
)}
/>
}
label="使用 reCaptcha V2 验证码"
/>
<FormHelperText id="component-helper-text">
是否使用 reCaptcha V2 验证码
</FormHelperText>
</FormControl>
</div>
{options.captcha_IsUseReCaptcha === "1" && (
<>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
Site KEY
</InputLabel>
<Input
required
value={options.captcha_ReCaptchaKey}
onChange={handleInputChange(
"captcha_ReCaptchaKey"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://www.google.com/recaptcha/admin/create"
}
target={"_blank"}
>
应用管理页面
</Link>{" "}
获取到的的 网站密钥
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
Secret
</InputLabel>
<Input
required
value={
options.captcha_ReCaptchaSecret
}
onChange={handleInputChange(
"captcha_ReCaptchaSecret"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://www.google.com/recaptcha/admin/create"
}
target={"_blank"}
>
应用管理页面
</Link>{" "}
获取到的的 秘钥
</FormHelperText>
</FormControl>
</div>
</>
)}
<div className={classes.form}>
<FormControl fullWidth>
<FormControlLabel

View File

@ -0,0 +1,406 @@
import React, { useCallback, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import InputLabel from "@material-ui/core/InputLabel";
import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText";
import Button from "@material-ui/core/Button";
import API from "../../../middleware/Api";
import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../../../actions";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Input from "@material-ui/core/Input";
import Link from "@material-ui/core/Link";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.up("md")]: {
marginLeft: 100,
},
marginBottom: 40,
},
form: {
maxWidth: 400,
marginTop: 20,
marginBottom: 20,
},
formContainer: {
[theme.breakpoints.up("md")]: {
padding: "0px 24px 0 24px",
},
},
}));
export default function Captcha() {
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState({
captcha_type: "normal",
captcha_height: "1",
captcha_width: "1",
captcha_mode: "3",
captcha_CaptchaLen: "",
captcha_ReCaptchaKey: "",
captcha_ReCaptchaSecret: "",
captcha_TCaptcha_CaptchaAppId: "",
captcha_TCaptcha_AppSecretKey: "",
captcha_TCaptcha_SecretId: "",
captcha_TCaptcha_SecretKey: "",
});
const handleChange = (name) => (event) => {
setOptions({
...options,
[name]: event.target.value,
});
};
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
useEffect(() => {
API.post("/admin/setting", {
keys: Object.keys(options),
})
.then((response) => {
setOptions(response.data);
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
});
// eslint-disable-next-line
}, []);
const submit = (e) => {
e.preventDefault();
setLoading(true);
const option = [];
Object.keys(options).forEach((k) => {
option.push({
key: k,
value: options[k],
});
});
API.patch("/admin/setting", {
options: option,
})
.then(() => {
ToggleSnackbar("top", "right", "设置已更改", "success");
})
.catch((error) => {
ToggleSnackbar("top", "right", error.message, "error");
})
.then(() => {
setLoading(false);
});
};
return (
<div>
<form onSubmit={submit}>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
验证码
</Typography>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
验证码类型
</InputLabel>
<Select
value={options.captcha_type}
onChange={handleChange("captcha_type")}
required
>
<MenuItem value={"normal"}>普通</MenuItem>
<MenuItem value={"recaptcha"}>
reCAPTCHA V2
</MenuItem>
<MenuItem value={"tcaptcha"}>
腾讯云验证码
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
验证码类型
</FormHelperText>
</FormControl>
</div>
</div>
{options.captcha_type === "normal" && (
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
普通验证码
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
宽度
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_width}
onChange={handleChange("captcha_width")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
高度
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_height}
onChange={handleChange(
"captcha_height"
)}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
模式
</InputLabel>
<Select
value={options.captcha_mode}
onChange={handleChange("captcha_mode")}
required
>
<MenuItem value={"0"}>数字</MenuItem>
<MenuItem value={"1"}>字母</MenuItem>
<MenuItem value={"2"}>算数</MenuItem>
<MenuItem value={"3"}>
数字+字母
</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
验证码的形式
</FormHelperText>
</FormControl>
</div>
</div>
</div>
)}
{options.captcha_type === "recaptcha" && (
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
reCAPTCHA V2
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
Site KEY
</InputLabel>
<Input
required
value={options.captcha_ReCaptchaKey}
onChange={handleChange(
"captcha_ReCaptchaKey"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://www.google.com/recaptcha/admin/create"
}
target={"_blank"}
>
应用管理页面
</Link>{" "}
获取到的的 网站密钥
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
Secret
</InputLabel>
<Input
required
value={
options.captcha_ReCaptchaSecret
}
onChange={handleChange(
"captcha_ReCaptchaSecret"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://www.google.com/recaptcha/admin/create"
}
target={"_blank"}
>
应用管理页面
</Link>{" "}
获取到的的 秘钥
</FormHelperText>
</FormControl>
</div>
</div>
</div>
</div>
)}
{options.captcha_type === "tcaptcha" && (
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
腾讯云验证码
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
SecretId
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_SecretId
}
onChange={handleChange(
"captcha_TCaptcha_SecretId"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://console.cloud.tencent.com/capi"
}
target={"_blank"}
>
访问密钥页面
</Link>{" "}
获取到的的 SecretId
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
SecretKey
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_SecretKey
}
onChange={handleChange(
"captcha_TCaptcha_SecretKey"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://console.cloud.tencent.com/capi"
}
target={"_blank"}
>
访问密钥页面
</Link>{" "}
获取到的的 SecretKey
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
APPID
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_CaptchaAppId
}
onChange={handleChange(
"captcha_TCaptcha_CaptchaAppId"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://console.cloud.tencent.com/captcha/graphical"
}
target={"_blank"}
>
图形验证页面
</Link>{" "}
获取到的的 APPID
</FormHelperText>
</FormControl>
</div>
<div className={classes.form}>
<FormControl fullWidth>
<InputLabel htmlFor="component-helper">
App Secret Key
</InputLabel>
<Input
required
value={
options.captcha_TCaptcha_AppSecretKey
}
onChange={handleChange(
"captcha_TCaptcha_AppSecretKey"
)}
/>
<FormHelperText id="component-helper-text">
<Link
href={
"https://console.cloud.tencent.com/captcha/graphical"
}
target={"_blank"}
>
图形验证页面
</Link>{" "}
获取到的的 App Secret Key
</FormHelperText>
</FormControl>
</div>
</div>
</div>
</div>
)}
<div className={classes.root}>
<Button
disabled={loading}
type={"submit"}
variant={"contained"}
color={"primary"}
>
保存
</Button>
</div>
</form>
</div>
);
}

View File

@ -3,8 +3,6 @@ import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText";
import Input from "@material-ui/core/Input";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import React, { useCallback, useEffect, useState } from "react";
@ -44,10 +42,6 @@ export default function ImageSetting() {
avatar_size_s: "",
thumb_width: "",
thumb_height: "",
captcha_height: "1",
captcha_width: "1",
captcha_mode: "3",
captcha_CaptchaLen: "",
});
const handleChange = (name) => (event) => {
@ -260,70 +254,6 @@ export default function ImageSetting() {
</div>
</div>
<div className={classes.root}>
<Typography variant="h6" gutterBottom>
验证码
</Typography>
<div className={classes.formContainer}>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
宽度
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_width}
onChange={handleChange("captcha_width")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
高度
</InputLabel>
<Input
type={"number"}
inputProps={{
min: 1,
step: 1,
}}
value={options.captcha_height}
onChange={handleChange("captcha_height")}
required
/>
</FormControl>
</div>
<div className={classes.form}>
<FormControl>
<InputLabel htmlFor="component-helper">
模式
</InputLabel>
<Select
value={options.captcha_mode}
onChange={handleChange("captcha_mode")}
required
>
<MenuItem value={"0"}>数字</MenuItem>
<MenuItem value={"1"}>字母</MenuItem>
<MenuItem value={"2"}>算数</MenuItem>
<MenuItem value={"3"}>数字+字母</MenuItem>
</Select>
<FormHelperText id="component-helper-text">
验证码的形式
</FormHelperText>
</FormControl>
</div>
</div>
</div>
<div className={classes.root}>
<Button
disabled={loading}

View File

@ -1,34 +1,34 @@
import React, { useCallback, useState, useEffect } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
import { makeStyles } from "@material-ui/core";
import {
toggleSnackbar,
Avatar,
Button,
Divider,
FormControl,
Input,
InputLabel,
Link,
makeStyles,
Paper,
Typography,
} from "@material-ui/core";
import {
applyThemes,
setSessionStatus,
toggleSnackbar,
} from "../../actions/index";
import Placeholder from "../Placeholder/Captcha";
import { useHistory } from "react-router-dom";
import API from "../../middleware/Api";
import Auth from "../../middleware/Auth";
import {
Button,
FormControl,
Divider,
Link,
Input,
InputLabel,
Paper,
Avatar,
Typography,
} from "@material-ui/core";
import { bufferDecode, bufferEncode } from "../../utils/index";
import { enableUploaderLoad } from "../../middleware/Init";
import { Fingerprint, VpnKey } from "@material-ui/icons";
import VpnIcon from "@material-ui/icons/VpnKeyOutlined";
import { useLocation } from "react-router";
import ReCaptcha from "./ReCaptcha";
import { ICPFooter } from "../Common/ICPFooter";
import { useCaptcha } from "../../hooks/useCaptcha";
const useStyles = makeStyles((theme) => ({
layout: {
width: "auto",
@ -94,22 +94,14 @@ function useQuery() {
function LoginForm() {
const [email, setEmail] = useState("");
const [pwd, setPwd] = useState("");
const [captcha, setCaptcha] = useState("");
const [loading, setLoading] = useState(false);
const [useAuthn, setUseAuthn] = useState(false);
const [captchaData, setCaptchaData] = useState(null);
const [twoFA, setTwoFA] = useState(false);
const [faCode, setFACode] = useState("");
const loginCaptcha = useSelector((state) => state.siteConfig.loginCaptcha);
const title = useSelector((state) => state.siteConfig.title);
const authn = useSelector((state) => state.siteConfig.authn);
const useReCaptcha = useSelector(
(state) => state.siteConfig.captcha_IsUseReCaptcha
);
const reCaptchaKey = useSelector(
(state) => state.siteConfig.captcha_ReCaptchaKey
);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
@ -127,31 +119,21 @@ function LoginForm() {
const history = useHistory();
const location = useLocation();
const {
captchaLoading,
isValidate,
validate,
CaptchaRender,
captchaRefreshRef,
captchaParamsRef,
} = useCaptcha();
const query = useQuery();
const classes = useStyles();
const refreshCaptcha = useCallback(() => {
API.get("/site/captcha")
.then((response) => {
setCaptchaData(response.data);
})
.catch((error) => {
ToggleSnackbar(
"top",
"right",
"无法加载验证码:" + error.message,
"error"
);
});
}, []);
useEffect(() => {
setEmail(query.get("username"));
if (loginCaptcha && !useReCaptcha) {
refreshCaptcha();
}
}, [location, loginCaptcha]);
}, [location]);
const afterLogin = (data) => {
Auth.authenticate(data);
@ -235,10 +217,14 @@ function LoginForm() {
const login = (e) => {
e.preventDefault();
setLoading(true);
if (!isValidate.current.isValidate && loginCaptcha) {
validate(() => login(e), setLoading);
return;
}
API.post("/user/session", {
userName: email,
Password: pwd,
captchaCode: captcha,
...captchaParamsRef.current,
})
.then((response) => {
setLoading(false);
@ -251,9 +237,7 @@ function LoginForm() {
.catch((error) => {
setLoading(false);
ToggleSnackbar("top", "right", error.message, "warning");
if (!useReCaptcha) {
refreshCaptcha();
}
captchaRefreshRef.current();
});
};
@ -315,78 +299,17 @@ function LoginForm() {
autoComplete
/>
</FormControl>
{loginCaptcha && !useReCaptcha && (
<div className={classes.captchaContainer}>
<FormControl
margin="normal"
required
fullWidth
>
<InputLabel htmlFor="captcha">
验证码
</InputLabel>
<Input
name="captcha"
onChange={(e) =>
setCaptcha(e.target.value)
}
type="text"
id="captcha"
value={captcha}
autoComplete
/>
</FormControl>{" "}
<div>
{captchaData === null && (
<div
className={
classes.captchaPlaceholder
}
>
<Placeholder />
</div>
)}
{captchaData !== null && (
<img
src={captchaData}
alt="captcha"
onClick={refreshCaptcha}
/>
)}
</div>
</div>
)}
{loginCaptcha && useReCaptcha && (
<div className={classes.captchaContainer}>
<FormControl
margin="normal"
required
fullWidth
>
<div>
<ReCaptcha
style={{
display: "inline-block",
}}
sitekey={reCaptchaKey}
onChange={(value) =>
setCaptcha(value)
}
id="captcha"
name="captcha"
/>
</div>
</FormControl>{" "}
</div>
)}
{loginCaptcha && <CaptchaRender />}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
disabled={loading}
disabled={
loading ||
(loginCaptcha ? captchaLoading : false)
}
className={classes.submit}
>
登录

View File

@ -1,24 +1,24 @@
import React, { useCallback, useState, useEffect } from "react";
import React, { useCallback, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import RegIcon from "@material-ui/icons/AssignmentIndOutlined";
import { makeStyles } from "@material-ui/core";
import { toggleSnackbar } from "../../actions/index";
import Placeholder from "../Placeholder/Captcha";
import { useHistory } from "react-router-dom";
import API from "../../middleware/Api";
import {
Avatar,
Button,
FormControl,
Divider,
Link,
FormControl,
Input,
InputLabel,
Link,
makeStyles,
Paper,
Avatar,
Typography,
} from "@material-ui/core";
import { toggleSnackbar } from "../../actions/index";
import { useHistory } from "react-router-dom";
import API from "../../middleware/Api";
import EmailIcon from "@material-ui/icons/EmailOutlined";
import ReCaptcha from "./ReCaptcha";
import { useCaptcha } from "../../hooks/useCaptcha";
const useStyles = makeStyles((theme) => ({
layout: {
width: "auto",
@ -86,20 +86,12 @@ function Register() {
email: "",
password: "",
password_repeat: "",
captcha: "",
});
const [loading, setLoading] = useState(false);
const [emailActive, setEmailActive] = useState(false);
const [captchaData, setCaptchaData] = useState(null);
const title = useSelector((state) => state.siteConfig.title);
const regCaptcha = useSelector((state) => state.siteConfig.regCaptcha);
const useReCaptcha = useSelector(
(state) => state.siteConfig.captcha_IsUseReCaptcha
);
const reCaptchaKey = useSelector(
(state) => state.siteConfig.captcha_ReCaptchaKey
);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
@ -116,23 +108,16 @@ function Register() {
});
};
const {
captchaLoading,
isValidate,
validate,
CaptchaRender,
captchaRefreshRef,
captchaParamsRef,
} = useCaptcha();
const classes = useStyles();
const refreshCaptcha = useCallback(() => {
API.get("/site/captcha")
.then((response) => {
setCaptchaData(response.data);
})
.catch((error) => {
ToggleSnackbar(
"top",
"right",
"无法加载验证码:" + error.message,
"error"
);
});
}, []);
const register = (e) => {
e.preventDefault();
@ -142,10 +127,14 @@ function Register() {
}
setLoading(true);
if (!isValidate.current.isValidate && regCaptcha) {
validate(() => register(e), setLoading);
return;
}
API.post("/user", {
userName: input.email,
Password: input.password,
captchaCode: input.captcha,
...captchaParamsRef.current,
})
.then((response) => {
setLoading(false);
@ -159,18 +148,10 @@ function Register() {
.catch((error) => {
setLoading(false);
ToggleSnackbar("top", "right", error.message, "warning");
if (!useReCaptcha) {
refreshCaptcha();
}
captchaRefreshRef.current();
});
};
useEffect(() => {
if (regCaptcha && !useReCaptcha) {
refreshCaptcha();
}
}, [regCaptcha]);
return (
<div className={classes.layout}>
<>
@ -224,77 +205,17 @@ function Register() {
autoComplete
/>
</FormControl>
{regCaptcha && !useReCaptcha && (
<div className={classes.captchaContainer}>
<FormControl
margin="normal"
required
fullWidth
>
<InputLabel htmlFor="captcha">
验证码
</InputLabel>
<Input
name="captcha"
onChange={handleInputChange(
"captcha"
)}
type="text"
id="captcha"
value={input.captcha}
autoComplete
/>
</FormControl>{" "}
<div>
{captchaData === null && (
<div
className={
classes.captchaPlaceholder
}
>
<Placeholder />
</div>
)}
{captchaData !== null && (
<img
src={captchaData}
alt="captcha"
onClick={refreshCaptcha}
/>
)}
</div>
</div>
)}
{regCaptcha && useReCaptcha && (
<div className={classes.captchaContainer}>
<FormControl
margin="normal"
required
fullWidth
>
<ReCaptcha
style={{ display: "inline-block" }}
sitekey={reCaptchaKey}
onChange={(value) =>
setInput({
...input,
captcha: value,
})
}
id="captcha"
name="captcha"
/>
</FormControl>{" "}
</div>
)}
{regCaptcha && <CaptchaRender />}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
disabled={loading}
disabled={
loading ||
(regCaptcha ? captchaLoading : false)
}
className={classes.submit}
>
注册账号

View File

@ -1,22 +1,22 @@
import React, { useCallback, useState, useEffect } from "react";
import React, { useCallback, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { makeStyles } from "@material-ui/core";
import { toggleSnackbar } from "../../actions/index";
import Placeholder from "../Placeholder/Captcha";
import API from "../../middleware/Api";
import {
Avatar,
Button,
FormControl,
Divider,
Link,
FormControl,
Input,
InputLabel,
Link,
makeStyles,
Paper,
Avatar,
Typography,
} from "@material-ui/core";
import { toggleSnackbar } from "../../actions/index";
import API from "../../middleware/Api";
import KeyIcon from "@material-ui/icons/VpnKeyOutlined";
import ReCaptcha from "./ReCaptcha";
import { useCaptcha } from "../../hooks/useCaptcha";
const useStyles = makeStyles((theme) => ({
layout: {
width: "auto",
@ -64,19 +64,11 @@ const useStyles = makeStyles((theme) => ({
function Reset() {
const [input, setInput] = useState({
email: "",
captcha: "",
});
const [captchaData, setCaptchaData] = useState(null);
const [loading, setLoading] = useState(false);
const forgetCaptcha = useSelector(
(state) => state.siteConfig.forgetCaptcha
);
const useReCaptcha = useSelector(
(state) => state.siteConfig.captcha_IsUseReCaptcha
);
const reCaptchaKey = useSelector(
(state) => state.siteConfig.captcha_ReCaptchaKey
);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
@ -90,34 +82,25 @@ function Reset() {
});
};
const refreshCaptcha = () => {
API.get("/site/captcha")
.then((response) => {
setCaptchaData(response.data);
})
.catch((error) => {
ToggleSnackbar(
"top",
"right",
"无法加载验证码:" + error.message,
"error"
);
});
};
useEffect(() => {
if (forgetCaptcha && !useReCaptcha) {
refreshCaptcha();
}
// eslint-disable-next-line
}, [forgetCaptcha]);
const {
captchaLoading,
isValidate,
validate,
CaptchaRender,
captchaRefreshRef,
captchaParamsRef,
} = useCaptcha();
const submit = (e) => {
e.preventDefault();
setLoading(true);
if (!isValidate.current.isValidate && forgetCaptcha) {
validate(() => submit(e), setLoading);
return;
}
API.post("/user/reset", {
userName: input.email,
captchaCode: input.captcha,
...captchaParamsRef.current,
})
.then(() => {
setLoading(false);
@ -131,9 +114,7 @@ function Reset() {
.catch((error) => {
setLoading(false);
ToggleSnackbar("top", "right", error.message, "warning");
if (!useReCaptcha) {
refreshCaptcha();
}
captchaRefreshRef.current();
});
};
@ -161,61 +142,15 @@ function Reset() {
autoFocus
/>
</FormControl>
{forgetCaptcha && !useReCaptcha && (
<div className={classes.captchaContainer}>
<FormControl margin="normal" required fullWidth>
<InputLabel htmlFor="captcha">
验证码
</InputLabel>
<Input
name="captcha"
onChange={handleInputChange("captcha")}
type="text"
id="captcha"
value={input.captcha}
autoComplete
/>
</FormControl>{" "}
<div>
{captchaData === null && (
<div className={classes.captchaPlaceholder}>
<Placeholder />
</div>
)}
{captchaData !== null && (
<img
src={captchaData}
alt="captcha"
onClick={refreshCaptcha}
/>
)}
</div>
</div>
)}
{forgetCaptcha && useReCaptcha && (
<div className={classes.captchaContainer}>
<FormControl margin="normal" required fullWidth>
<ReCaptcha
style={{ display: "inline-block" }}
sitekey={reCaptchaKey}
onChange={(value) =>
setInput({
...input,
captcha: value,
})
}
id="captcha"
name="captcha"
/>
</FormControl>{" "}
</div>
)}
{forgetCaptcha && <CaptchaRender />}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
disabled={loading}
disabled={
loading || (forgetCaptcha ? captchaLoading : false)
}
className={classes.submit}
>
发送密码重置邮件

115
src/hooks/normal.js Normal file
View File

@ -0,0 +1,115 @@
import React, {
forwardRef,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { useDispatch } from "react-redux";
import { toggleSnackbar } from "../actions";
import API from "../middleware/Api";
import { FormControl, Input, InputLabel } from "@material-ui/core";
import Placeholder from "../component/Placeholder/Captcha";
import { defaultValidate, useStyle } from "./useCaptcha";
const NormalCaptcha = forwardRef(function NormalCaptcha(
{ captchaRef, setLoading },
ref
) {
const classes = useStyle();
const [captcha, setCaptcha] = useState("");
const [captchaData, setCaptchaData] = useState(null);
const dispatch = useDispatch();
const ToggleSnackbar = useCallback(
(vertical, horizontal, msg, color) =>
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
[dispatch]
);
const refreshCaptcha = () => {
API.get("/site/captcha")
.then((response) => {
setCaptchaData(response.data);
setLoading(false);
})
.catch((error) => {
ToggleSnackbar(
"top",
"right",
"无法加载验证码:" + error.message,
"error"
);
});
};
useEffect(() => {
ref.current = refreshCaptcha;
refreshCaptcha();
}, []);
useEffect(() => {
captchaRef.current.captchaCode = captcha;
}, [captcha]);
return (
<div className={classes.captchaContainer}>
<FormControl margin="normal" required fullWidth>
<InputLabel htmlFor="captcha">验证码</InputLabel>
<Input
name="captcha"
onChange={(e) => setCaptcha(e.target.value)}
type="text"
id="captcha"
value={captcha}
autoComplete
/>
</FormControl>{" "}
<div>
{captchaData === null && (
<div className={classes.captchaPlaceholder}>
<Placeholder />
</div>
)}
{captchaData !== null && (
<img
src={captchaData}
alt="captcha"
onClick={refreshCaptcha}
/>
)}
</div>
</div>
);
});
export default function useNormalCaptcha(captchaRefreshRef, setLoading) {
const isValidate = useRef({
isValidate: true,
});
const captchaParamsRef = useRef({
captchaCode: "",
});
const CaptchaRender = useCallback(
function Normal() {
return (
<NormalCaptcha
captchaRef={captchaParamsRef}
ref={captchaRefreshRef}
setLoading={setLoading}
/>
);
},
[captchaParamsRef, captchaRefreshRef, setLoading]
);
return {
isValidate,
validate: defaultValidate,
captchaParamsRef,
CaptchaRender,
};
}

68
src/hooks/recaptcha.js Normal file
View File

@ -0,0 +1,68 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { FormControl } from "@material-ui/core";
import ReCaptcha from "../component/Login/ReCaptcha";
import { defaultValidate, useStyle } from "./useCaptcha";
const Recaptcha = ({ captchaRef, setLoading }) => {
const classes = useStyle();
const [captcha, setCaptcha] = useState("");
const reCaptchaKey = useSelector(
(state) => state.siteConfig.captcha_ReCaptchaKey
);
useEffect(() => {
captchaRef.current.captchaCode = captcha;
}, [captcha]);
useEffect(() => setLoading(false), []);
return (
<div className={classes.captchaContainer}>
<FormControl margin="normal" required fullWidth>
<div>
<ReCaptcha
style={{
display: "inline-block",
}}
sitekey={reCaptchaKey}
onChange={(value) => setCaptcha(value)}
id="captcha"
name="captcha"
/>
</div>
</FormControl>{" "}
</div>
);
};
export default function useRecaptcha(setLoading) {
const isValidate = useRef({
isValidate: true,
});
const captchaParamsRef = useRef({
captchaCode: "",
});
const CaptchaRender = useCallback(
function RecaptchaRender() {
return (
<Recaptcha
captchaRef={captchaParamsRef}
setLoading={setLoading}
/>
);
},
[captchaParamsRef, setLoading]
);
return {
isValidate,
validate: defaultValidate,
captchaParamsRef,
CaptchaRender,
};
}

81
src/hooks/tcaptcha.js Normal file
View File

@ -0,0 +1,81 @@
import React, { forwardRef, useCallback, useRef } from "react";
import Script from "react-load-script";
import { useSelector } from "react-redux";
const TCaptcha = forwardRef(function TCaptcha(
{ captchaRef, setLoading, isValidateRef, submitRef },
ref
) {
const appid = useSelector(
(state) => state.siteConfig.tcaptcha_captcha_app_id
);
const onLoad = () => {
try {
ref.current = new window.TencentCaptcha(appid, function (res) {
if (res.ret === 0) {
captchaRef.current.ticket = res.ticket;
captchaRef.current.randstr = res.randstr;
isValidateRef.current.isValidate = true;
submitRef.current.submit();
console.log(submitRef);
} else {
submitRef.current.setLoading(false);
}
});
} catch (e) {
console.error(e);
}
setLoading(false);
};
return (
<Script
url={"https://ssl.captcha.qq.com/TCaptcha.js"}
onLoad={onLoad}
/>
);
});
export default function useTCaptcha(setLoading) {
const isValidate = useRef({
isValidate: false,
});
const captchaParamsRef = useRef({
ticket: "",
randstr: "",
});
const submitRef = useRef({
// eslint-disable-next-line @typescript-eslint/no-empty-function
submit: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
setLoading: () => {},
});
const captchaRef = useRef();
const CaptchaRender = useCallback(
function TCaptchaRender() {
return (
<TCaptcha
captchaRef={captchaParamsRef}
setLoading={setLoading}
isValidateRef={isValidate}
submitRef={submitRef}
ref={captchaRef}
/>
);
},
[captchaParamsRef, setLoading, isValidate, submitRef, captchaRef]
);
return {
isValidate: isValidate,
validate: (submit, setLoading) => {
submitRef.current.submit = submit;
submitRef.current.setLoading = setLoading;
captchaRef.current.show();
},
captchaParamsRef,
CaptchaRender,
};
}

42
src/hooks/useCaptcha.js Normal file
View File

@ -0,0 +1,42 @@
import { useSelector } from "react-redux";
import { useRef, useState } from "react";
import { makeStyles } from "@material-ui/core";
import useNormalCaptcha from "./normal";
import useRecaptcha from "./recaptcha";
import useTCaptcha from "./tcaptcha";
export const useStyle = makeStyles((theme) => ({
captchaContainer: {
display: "flex",
marginTop: "10px",
[theme.breakpoints.down("sm")]: {
display: "block",
},
},
}));
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
export const defaultValidate = (submit, setLoading) => {};
export const useCaptcha = () => {
const captchaType = useSelector((state) => state.siteConfig.captcha_type);
const [captchaLoading, setCaptchaLoading] = useState(true);
// eslint-disable-next-line @typescript-eslint/no-empty-function
const captchaRefreshRef = useRef(() => {});
const normal = useNormalCaptcha(captchaRefreshRef, setCaptchaLoading);
const recaptcha = useRecaptcha(setCaptchaLoading);
const tcaptcha = useTCaptcha(setCaptchaLoading);
switch (captchaType) {
case "normal":
return { ...normal, captchaRefreshRef, captchaLoading };
case "recaptcha":
return { ...recaptcha, captchaRefreshRef, captchaLoading };
case "tcaptcha":
return { ...tcaptcha, captchaRefreshRef, captchaLoading };
default:
return { ...normal, captchaRefreshRef, captchaLoading };
}
};

View File

@ -61,8 +61,9 @@ export const initState = {
},
},
},
captcha_IsUseReCaptcha: false,
captcha_ReCaptchaKey: "defaultKey",
captcha_type: "normal",
tcaptcha_captcha_app_id: "",
},
navigator: {
path: "/",