Browse Source

登录 修改 注册 本地化全部加上

main
lk 6 days ago
parent
commit
4db01f13ae
  1. 307
      src/components/captcha/SlideCapt.tsx
  2. 454
      src/pages/use/login/index.tsx

307
src/components/captcha/SlideCapt.tsx

@ -1,163 +1,160 @@
import React, { forwardRef, memo, useCallback, useEffect, useState, useImperativeHandle } from 'react'
import { Popover, message } from 'antd'
import GoCaptcha from 'go-captcha-react'
import { SlideCaptchaCheckData } from './types.ts'
import { SlideData, SlidePoint } from 'go-captcha-react/dist/components/Slide/meta/data'
import { SlideConfig } from 'go-captcha-react/dist/components/Slide/meta/config'
import { Props } from 'go-captcha-react/dist/components/Button'
import { useStyle } from './style.ts'
import React, { forwardRef, memo, useCallback, useEffect, useState, useImperativeHandle } from "react";
import { Popover, message } from "antd";
import GoCaptcha from "go-captcha-react";
import { SlideCaptchaCheckData } from "./types.ts";
import { SlideData, SlidePoint } from "go-captcha-react/dist/components/Slide/meta/data";
import { SlideConfig } from "go-captcha-react/dist/components/Slide/meta/config";
import { Props } from "go-captcha-react/dist/components/Button";
import { useStyle } from "./style.ts";
export interface SlideCaptProps {
api: {
getCaptcha: () => Promise<any>,
checkCaptcha: (params: SlideCaptchaCheckData) => Promise<any>,
},
config?: SlideConfig
value?: string
onChange?: (value?: string) => void
getCaptcha: () => Promise<any>;
checkCaptcha: (params: SlideCaptchaCheckData) => Promise<any>;
};
config?: SlideConfig;
value?: string;
onChange?: (value?: string) => void;
}
export type SlideCaptRef = {
reset: () => void
}
const SlideCapt = forwardRef<SlideCaptRef | undefined, SlideCaptProps>((
{
config,
api,
value,
onChange,
}: SlideCaptProps, ref) => {
const { styles } = useStyle()
const [ open, setOpen ] = useState(false)
const [ state, setState ] = useState<Props>({})
const [ data, setData ] = useState<SlideData>()
const [ innerKey, setKey ] = useState(value)
const fetchCaptcha = useCallback(() => {
return api.getCaptcha().then(res => {
if (res.code === 0) {
const data = res.data
setKey(data['captcha_key'] || '')
setData({
image: data['image_base64'] || '',
thumb: data['tile_base64'] || '',
thumbX: data['tile_x'] || 0,
thumbY: data['tile_y'] || 0,
thumbWidth: data['tile_width'] || 0,
thumbHeight: data['tile_height'] || 0,
})
reset: () => void;
};
const SlideCapt = forwardRef<SlideCaptRef | undefined, SlideCaptProps>(
({ config, api, value, onChange }: SlideCaptProps, ref) => {
const { styles } = useStyle();
const [open, setOpen] = useState(false);
const [state, setState] = useState<Props>({});
const [data, setData] = useState<SlideData>();
const [innerKey, setKey] = useState(value);
const fetchCaptcha = useCallback(() => {
return api.getCaptcha().then((res) => {
if (res.code === 0) {
const data = res.data;
setKey(data["captcha_key"] || "");
setData({
image: data["image_base64"] || "",
thumb: data["tile_base64"] || "",
thumbX: data["tile_x"] || 0,
thumbY: data["tile_y"] || 0,
thumbWidth: data["tile_width"] || 0,
thumbHeight: data["tile_height"] || 0,
});
}
});
}, [api.getCaptcha, setData]);
const refresh = useCallback(() => {
fetchCaptcha();
}, [fetchCaptcha]);
const confirm = useCallback(
(point: SlidePoint, clear: (fn?: any) => void) => {
api
.checkCaptcha({
point: [point.x, point.y].join(","),
key: innerKey!,
})
.then((res) => {
// console.log(res)
if (res.code === 0 && res.data.is_ok) {
message.success(`验证成功`);
//验证成功,onChange通知出去,外部需要key做为校验
onChange?.(innerKey);
setState((prevState) => ({
...prevState,
type: "success",
title: "校验成功",
}));
setOpen(false);
} else {
message.error("验证失败");
setState((prevState) => ({
...prevState,
type: "error",
title: "点击进行校验",
}));
}
setTimeout(() => {
clear();
fetchCaptcha();
}, 1000);
})
.catch(() => {
setTimeout(() => {
clear();
fetchCaptcha();
}, 1000);
});
},
[api.checkCaptcha, setData, state, setState, setOpen, innerKey, onChange, fetchCaptcha],
);
useImperativeHandle(ref, () => {
return {
reset() {
setState((prevState) => ({
...prevState,
type: "default",
title: "点击进行校验",
}));
setKey(undefined);
onChange?.(undefined);
refresh();
},
};
}, [fetchCaptcha, setState, setKey]);
useEffect(() => {
if (open) {
fetchCaptcha();
}
}, [open]);
return (
<div className={styles.container}>
<Popover
content={
<GoCaptcha.Slide
config={{
width: 300,
height: 217,
showTheme: false,
verticalPadding: 5,
horizontalPadding: 5,
...config,
}}
style={{ color: "red" }}
data={data!}
events={{
refresh,
confirm,
close: () => {
setOpen(false);
},
}}
/>
}
open={open}
onOpenChange={setOpen}
forceRender={true}
trigger="click"
>
<GoCaptcha.Button
title={"点击进行校验"}
{...state}
clickEvent={() => {
setOpen(true);
}}
/>
</Popover>
</div>
);
},
);
})
}, [ api.getCaptcha, setData ])
const refresh = useCallback(() => {
fetchCaptcha()
}, [ fetchCaptcha ])
const confirm = useCallback((point: SlidePoint, clear: (fn?: any) => void) => {
api.checkCaptcha({
point: [ point.x, point.y ].join(','),
key: innerKey!
}).then(res => {
// console.log(res)
if (res.code === 0 && res.data.is_ok) {
message.success(`验证成功`)
//验证成功,onChange通知出去,外部需要key做为校验
onChange?.(innerKey)
setState(prevState => ({
...prevState, type: 'success', title: '校验成功',
}))
setOpen(false)
} else {
message.error('验证失败')
setState(prevState => ({
...prevState, type: 'error', title: '点击进行校验',
}))
}
setTimeout(() => {
clear()
fetchCaptcha()
}, 1000)
}).catch(() => {
setTimeout(() => {
clear()
fetchCaptcha()
}, 1000)
})
}, [ api.checkCaptcha, setData, state, setState, setOpen, innerKey, onChange, fetchCaptcha ])
useImperativeHandle(ref, () => {
return {
reset() {
setState(prevState => ({
...prevState, type: 'default', title: '点击进行校验',
}))
setKey(undefined)
onChange?.(undefined)
refresh()
}
}
}, [ fetchCaptcha, setState, setKey ])
useEffect(() => {
if (open) {
fetchCaptcha()
}
}, [ open ])
return (
<div className={styles.container}>
<Popover
content={
<GoCaptcha.Slide
config={{
width: 300,
height: 217,
showTheme: false,
verticalPadding: 5,
horizontalPadding: 5,
...config,
}}
style={{ color: 'red' }}
data={data!}
events={{
refresh,
confirm,
close: () => {
setOpen(false)
}
}}
/>
}
open={open}
onOpenChange={setOpen}
forceRender={true}
trigger="click">
<GoCaptcha.Button
title={'点击进行校验'}
{...state}
clickEvent={
() => {
setOpen(true)
}
}/>
</Popover>
</div>
)
})
export default memo(SlideCapt)
export default memo(SlideCapt);

454
src/pages/use/login/index.tsx

@ -1,261 +1,281 @@
import { Layout, Tabs, Input, Button, Typography, Row, Col, Form } from 'antd'
import { QrcodeOutlined, UserOutlined, LockOutlined } from '@ant-design/icons'
import SelectLang from '@/components/select-lang'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useAtom, useAtomValue } from 'jotai'
import { useTranslation } from '@/i18n.ts'
import { Layout, Tabs, Input, Button, Typography, Row, Col, Form } from "antd";
import { QrcodeOutlined, UserOutlined, LockOutlined } from "@ant-design/icons";
import SelectLang from "@/components/select-lang";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useAtom, useAtomValue } from "jotai";
import { useTranslation } from "@/i18n.ts";
import {
emailCodeAtom,
emailLoginAtom,
telegramCodeAtom,
telegramLoginAtom,
upLoginAtom,
} from '@/store/system/user.ts'
import React, { memo, useEffect, useLayoutEffect, useRef, useState } from 'react'
import systemServ from '@/service/system.ts'
import SlideCapt, { SlideCaptRef } from '@/components/captcha/SlideCapt.tsx'
} from "@/store/system/user.ts";
import React, { memo, useEffect, useLayoutEffect, useRef, useState } from "react";
import systemServ from "@/service/system.ts";
import SlideCapt, { SlideCaptRef } from "@/components/captcha/SlideCapt.tsx";
import { t } from "i18next";
const { Title, Text, Link } = Typography
const { TabPane } = Tabs
const { Title, Text, Link } = Typography;
const { TabPane } = Tabs;
const Login = memo(() => {
const navigate = useNavigate()
const [ upform ] = Form.useForm()
const [ emailform ] = Form.useForm()
const [ telegramform ] = Form.useForm()
const { mutate: upLoginFun } = useAtomValue(upLoginAtom)
const [ emailCodeData, setEmailCodeData ] = useState({})
const { mutate: emailLoginMutate } = useAtomValue(emailLoginAtom)
const { mutate: telegramLoginMutate } = useAtomValue(telegramLoginAtom)
const languageSet = {
向量检索服务免费试用: t("login.vectorRetrievalServiceFreeTrial", "向量检索服务免费试用"),
免费试用向量检索服务玩转大模型生成式检索: t(
"login.freeTrialVectorRetrievalService",
"免费试用向量检索服务,玩转大模型生成式检索",
),
查看详情: t("login.viewDetails", "查看详情 &gt;"),
请输入邮箱: t("login.pleaseEnterEmail", "请输入邮箱"),
获得验证码: t("login.getVerificationCode", "获得验证码"),
秒后重试: t("login.retryAfterSeconds", "秒后重试"),
请输入验证码: t("login.pleaseEnterVerificationCode", "请输入验证码"),
请输入登录密码: t("login.pleaseEnterPassword", "请输入登录密码"),
点击进行校验: t("login.clickToVerify", "点击进行校验"),
登录: t("login.login", "登录"),
注册: t("login.register", "注册"),
忘记密码: t("login.forgotPassword", "忘记密码"),
邮箱密码登录: t("login.emailPasswordLogin", "邮箱密码登录"),
邮箱验证登录: t("login.emailVerificationLogin", "邮箱验证登录"),
飞机验证登录: t("login.telegramVerificationLogin", "飞机验证登录"),
};
const slideCaptRef = useRef<SlideCaptRef>()
const navigate = useNavigate();
const [upform] = Form.useForm();
const [emailform] = Form.useForm();
const [telegramform] = Form.useForm();
const { mutate: upLoginFun } = useAtomValue(upLoginAtom);
const [emailCodeData, setEmailCodeData] = useState({});
const { mutate: emailLoginMutate } = useAtomValue(emailLoginAtom);
const { mutate: telegramLoginMutate } = useAtomValue(telegramLoginAtom);
const slideCaptRef = useRef<SlideCaptRef>();
const uphandleSubmit = (values: any) => {
console.log(values)
upLoginFun(values)
}
console.log(values);
upLoginFun(values);
};
const getEmailCode = async () => {
const email = emailform.getFieldValue('email')
const result = await systemServ.emailCode({ is_register: false, email })
setEmailCodeData(result)
}
const email = emailform.getFieldValue("email");
const result = await systemServ.emailCode({ is_register: false, email });
setEmailCodeData(result);
};
const emailhandleSubmit = (values: any) => {
emailLoginMutate(values)
}
emailLoginMutate(values);
};
const getTelegramCode = async () => {
const telegram = telegramform.getFieldValue('telegram')
const result = await systemServ.telegramCode({ telegram })
setEmailCodeData(result)
}
const telegram = telegramform.getFieldValue("telegram");
const result = await systemServ.telegramCode({ telegram });
setEmailCodeData(result);
};
const telegramhandleSubmit = (values: any) => {
telegramLoginMutate(values)
}
telegramLoginMutate(values);
};
const [ countdown, setCountdown ] = useState<number>(() => {
const savedCountdown = localStorage.getItem('countdown')
return savedCountdown !== null ? Number(savedCountdown) : 0
})
const [countdown, setCountdown] = useState<number>(() => {
const savedCountdown = localStorage.getItem("countdown");
return savedCountdown !== null ? Number(savedCountdown) : 0;
});
const [ isButtonDisabled, setIsButtonDisabled ] = useState<boolean>(() => {
const savedIsButtonDisabled = localStorage.getItem('isButtonDisabled')
return savedIsButtonDisabled !== null ? JSON.parse(savedIsButtonDisabled) : false
})
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(() => {
const savedIsButtonDisabled = localStorage.getItem("isButtonDisabled");
return savedIsButtonDisabled !== null ? JSON.parse(savedIsButtonDisabled) : false;
});
useEffect(() => {
localStorage.setItem('isButtonDisabled', JSON.stringify(isButtonDisabled))
}, [ isButtonDisabled ])
localStorage.setItem("isButtonDisabled", JSON.stringify(isButtonDisabled));
}, [isButtonDisabled]);
useEffect(() => {
if ((emailCodeData as any)?.code === 0) {
setCountdown(10)
setIsButtonDisabled(true)
setCountdown(10);
setIsButtonDisabled(true);
}
}, [ emailCodeData ])
}, [emailCodeData]);
useEffect(() => {
let timer: number
let timer: number;
if (countdown > 0) {
timer = setTimeout(() => setCountdown(countdown - 1), 1000)
timer = setTimeout(() => setCountdown(countdown - 1), 1000);
} else {
setIsButtonDisabled(false)
setIsButtonDisabled(false);
}
localStorage.setItem('countdown', String(countdown))
return () => clearTimeout(timer)
}, [ countdown ])
localStorage.setItem("countdown", String(countdown));
return () => clearTimeout(timer);
}, [countdown]);
useLayoutEffect(() => {
document.body.className = 'login'
document.body.className = "login";
return () => {
document.body.className = document.body.className.replace('login', '')
}
}, [])
document.body.className = document.body.className.replace("login", "");
};
}, []);
return (
<Layout
style={{
height: '100vh',
background: 'url(\'https://placehold.co/1920x1080\') no-repeat center center',
backgroundSize: 'cover',
}}
>
<Row justify="center" align="middle" style={{ height: '100%' }}>
<Col>
<Row>
<Col span={12} style={{ padding: '20px' }}>
<Title level={3}></Title>
<Text></Text>
<br/>
<Link href="#"> &gt;</Link>
</Col>
<Col
span={12}
style={{
padding: '20px',
background: '#fff',
borderRadius: '8px',
position: 'relative',
width: '500px',
height: '400px',
}}
>
<Tabs defaultActiveKey="1"
onChange={(key) => {
if (key === '1') {
slideCaptRef.current?.reset()
}
}}
style={{ marginTop: '50px' }}>
<TabPane tab="邮箱密码登录" key="1">
<Form form={upform} onFinish={uphandleSubmit}>
<Form.Item name="username" rules={[ { required: true, message: '请输入邮箱' } ]}>
<Input size="large" placeholder="请输入邮箱" prefix={<UserOutlined/>}/>
</Form.Item>
<Form.Item name="password" rules={[ { required: true, message: '请输入登录密码' } ]}>
<Input.Password
size="large"
placeholder="请输入登录密码"
prefix={<LockOutlined/>}
autoComplete="off"
/>
</Form.Item>
<Form.Item name={'captchaKey'}
rules={[ { required: true, message: '点击进行校验' } ]}>
<SlideCapt
ref={slideCaptRef}
api={{
getCaptcha: systemServ.captcha,
checkCaptcha: systemServ.captchaCheck
}}/>
</Form.Item>
{/*<Form.Item name="code" rules={[{ required: true, message: "请输入验证码" }]}>*/}
{/* <Input.Password size="large" placeholder="验证码" prefix={<LockOutlined />} />*/}
{/*</Form.Item>*/}
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
</Button>
</Form>
</TabPane>
<TabPane tab="邮箱验证登录" key="2">
<Form form={emailform} onFinish={emailhandleSubmit}>
<Form.Item name="email" rules={[ { required: true, message: '请输入邮箱' } ]}>
<Input
size="large"
placeholder="请输入邮箱"
prefix={<UserOutlined/>}
addonAfter={
<Button onClick={getEmailCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : '获得验证码'}
</Button>
}
/>
</Form.Item>
<Form.Item name="code" rules={[ { required: true, message: '请输入验证码' } ]}>
<Input.Password
size="large"
placeholder="请输入验证码"
prefix={<LockOutlined/>}
autoComplete="off"
/>
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
<Layout
style={{
height: "100vh",
background: "url('https://placehold.co/1920x1080') no-repeat center center",
backgroundSize: "cover",
}}
>
<Row justify="center" align="middle" style={{ height: "100%" }}>
<Col>
<Row>
<Col span={12} style={{ padding: "20px" }}>
<Title level={3}>{languageSet.}</Title>
<Text>{languageSet.}</Text>
<br />
<Link href="#">{languageSet.}</Link>
</Col>
<Col
span={12}
style={{
padding: "20px",
background: "#fff",
borderRadius: "8px",
position: "relative",
width: "500px",
height: "400px",
}}
>
<Tabs
defaultActiveKey="1"
onChange={(key) => {
if (key === "1") {
slideCaptRef.current?.reset();
}
}}
style={{ marginTop: "50px" }}
>
<TabPane tab={languageSet.} key="1">
<Form form={upform} onFinish={uphandleSubmit}>
<Form.Item name="username" rules={[{ required: true, message: languageSet.请输入邮箱 }]}>
<Input size="large" placeholder={languageSet.} prefix={<UserOutlined />} />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: languageSet.请输入登录密码 }]}>
<Input.Password
size="large"
placeholder={languageSet.}
prefix={<LockOutlined />}
autoComplete="off"
/>
</Form.Item>
<Form.Item name={"captchaKey"} rules={[{ required: true, message: languageSet.点击进行校验 }]}>
<SlideCapt
ref={slideCaptRef}
api={{
getCaptcha: systemServ.captcha,
checkCaptcha: systemServ.captchaCheck,
}}
/>
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
{languageSet.}
</Button>
</Form>
</TabPane>
<TabPane tab={languageSet.} key="2">
<Form form={emailform} onFinish={emailhandleSubmit}>
<Form.Item name="email" rules={[{ required: true, message: languageSet.请输入邮箱 }]}>
<Input
size="large"
placeholder={languageSet.}
prefix={<UserOutlined />}
addonAfter={
<Button onClick={getEmailCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}${languageSet.}` : languageSet.}
</Button>
</Form>
</TabPane>
<TabPane tab="飞机验证登录" key="3">
<Form form={telegramform} onFinish={telegramhandleSubmit}>
<Form.Item name="telegram" rules={[ { required: true, message: '请输入邮箱' } ]}>
<Input
size="large"
placeholder="请输入邮箱"
prefix={<UserOutlined/>}
addonAfter={
<Button onClick={getTelegramCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}秒后重试` : '获得飞机验证码'}
</Button>
}
/>
</Form.Item>
<Form.Item name="code" rules={[ { required: true, message: '请输入飞机验证码' } ]}>
<Input.Password
size="large"
placeholder="请输入飞机验证码"
prefix={<LockOutlined/>}
autoComplete="off"
/>
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
}
/>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: languageSet.请输入验证码 }]}>
<Input.Password
size="large"
placeholder={languageSet.}
prefix={<LockOutlined />}
autoComplete="off"
/>
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
{languageSet.}
</Button>
</Form>
</TabPane>
<TabPane tab={languageSet.} key="3">
<Form form={telegramform} onFinish={telegramhandleSubmit}>
<Form.Item name="telegram" rules={[{ required: true, message: languageSet.请输入邮箱 }]}>
<Input
size="large"
placeholder={languageSet.}
prefix={<UserOutlined />}
addonAfter={
<Button onClick={getTelegramCode} disabled={isButtonDisabled}>
{isButtonDisabled ? `${countdown}${languageSet.}` : languageSet.}
</Button>
</Form>
</TabPane>
</Tabs>
<div
style={{
position: 'absolute',
top: 0,
right: 0,
width: 0,
height: 0,
borderLeft: '80px solid transparent',
borderTop: '80px solid #ff9800',
textAlign: 'center',
}}
>
}
/>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: languageSet.请输入验证码 }]}>
<Input.Password
size="large"
placeholder={languageSet.}
prefix={<LockOutlined />}
autoComplete="off"
/>
</Form.Item>
<Button type="primary" htmlType="submit" style={{ width: "100%" }}>
{languageSet.}
</Button>
</Form>
</TabPane>
</Tabs>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: 0,
height: 0,
borderLeft: "80px solid transparent",
borderTop: "80px solid #ff9800",
textAlign: "center",
}}
>
<span
style={{
position: 'absolute',
top: '-70px',
right: '0px',
color: '#fff',
fontSize: '16px',
transform: 'rotate(0deg)',
textAlign: 'center',
width: '60px',
display: 'block',
cursor: 'pointer', // 设置手型光标
}}
onClick={() => navigate({ to: '/register' })}
style={{
position: "absolute",
top: "-70px",
right: "0px",
color: "#fff",
fontSize: "16px",
transform: "rotate(0deg)",
textAlign: "center",
width: "60px",
display: "block",
cursor: "pointer", // 设置手型光标
}}
onClick={() => navigate({ to: "/register" })}
>
{languageSet.}
</span>
</div>
<Row justify="space-between" style={{ marginTop: '10px' }}>
<Link onClick={() => navigate({ to: '/pwdRetrieve' })}></Link>
</Row>
</Col>
</Row>
</Col>
</Row>
</Layout>
)
})
</div>
<Row justify="space-between" style={{ marginTop: "10px" }}>
<Link onClick={() => navigate({ to: "/pwdRetrieve" })}>{languageSet.}</Link>
</Row>
</Col>
</Row>
</Col>
</Row>
</Layout>
);
});
export const Route = createFileRoute('/login')({
export const Route = createFileRoute("/login")({
component: Login,
})
});
export default Login
export default Login;
Loading…
Cancel
Save