diff --git a/package.json b/package.json index a5222fb..fdab83f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dayjs": "^1.11.13", "fast-copy": "^3.0.2", "fast-deep-equal": "^3.1.3", + "go-captcha-react": "^1.0.0", "i18next": "^23.14.0", "i18next-browser-languagedetector": "^7.2.1", "jotai": "^2.9.3", diff --git a/src/components/captcha/SlideCapt.tsx b/src/components/captcha/SlideCapt.tsx new file mode 100644 index 0000000..30ad2da --- /dev/null +++ b/src/components/captcha/SlideCapt.tsx @@ -0,0 +1,139 @@ +import React, { memo, useCallback, useEffect, useState } 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, + checkCaptcha: (params: SlideCaptchaCheckData) => Promise, + }, + config?: SlideConfig + value?: string + onChange?: (value?: string) => void +} + +const SlideCapt = ({ config, api, value, onChange }: SlideCaptProps) => { + + const { styles } = useStyle() + const [ open, setOpen ] = useState(false) + const [ state, setState ] = useState({}) + const [ data, setData ] = useState() + 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, fetchCaptcha ]) + + useEffect(() => { + + if (open) { + fetchCaptcha() + } + + }, [ open ]) + + return ( +
+ { + setOpen(false) + } + }} + /> + } + open={open} + onOpenChange={setOpen} + forceRender={true} + trigger="click"> + { + setOpen(true) + } + }/> + +
+ ) +} + +export default memo(SlideCapt) \ No newline at end of file diff --git a/src/components/captcha/index.ts b/src/components/captcha/index.ts new file mode 100644 index 0000000..76e80be --- /dev/null +++ b/src/components/captcha/index.ts @@ -0,0 +1,2 @@ + +export * from './SlideCapt.tsx' \ No newline at end of file diff --git a/src/components/captcha/style.ts b/src/components/captcha/style.ts new file mode 100644 index 0000000..f8523d7 --- /dev/null +++ b/src/components/captcha/style.ts @@ -0,0 +1,18 @@ + +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-captcha-component` + + const container = css` + + > div{ + width: 100% !important; + } + + ` + + return { + container: cx(prefix, props?.className, container), + } +}) \ No newline at end of file diff --git a/src/components/captcha/types.ts b/src/components/captcha/types.ts new file mode 100644 index 0000000..14f05e8 --- /dev/null +++ b/src/components/captcha/types.ts @@ -0,0 +1,6 @@ + + +export interface SlideCaptchaCheckData { + point: string + key: string +} \ No newline at end of file diff --git a/src/pages/use/login/index.tsx b/src/pages/use/login/index.tsx index e595ff7..a9dce92 100644 --- a/src/pages/use/login/index.tsx +++ b/src/pages/use/login/index.tsx @@ -1,241 +1,255 @@ -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, useState } from "react"; -import systemServ from "@/service/system.ts"; -const { Title, Text, Link } = Typography; -const { TabPane } = Tabs; +} from '@/store/system/user.ts' +import React, { memo, useEffect, useLayoutEffect, useState } from 'react' +import systemServ from '@/service/system.ts' +import SlideCapt from '@/components/captcha/SlideCapt.tsx' + +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 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 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(() => { - const savedCountdown = localStorage.getItem("countdown"); - return savedCountdown !== null ? Number(savedCountdown) : 0; - }); + const [ countdown, setCountdown ] = useState(() => { + const savedCountdown = localStorage.getItem('countdown') + return savedCountdown !== null ? Number(savedCountdown) : 0 + }) - const [isButtonDisabled, setIsButtonDisabled] = useState(() => { - const savedIsButtonDisabled = localStorage.getItem("isButtonDisabled"); - return savedIsButtonDisabled !== null ? JSON.parse(savedIsButtonDisabled) : false; - }); + const [ isButtonDisabled, setIsButtonDisabled ] = useState(() => { + 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 ( - - - - - - 向量检索服务免费试用 - 免费试用向量检索服务,玩转大模型生成式检索 -
- 查看详情 > - - - - -
- - } /> - - - } - autoComplete="off" - /> - - {/**/} - {/* } />*/} - {/**/} - -
-
- -
- - } - addonAfter={ - + +
+ +
+ + } + addonAfter={ + + } + /> + + + } + autoComplete="off" + /> + + - } - /> - - - } - autoComplete="off" - /> - - -
-
- -
- - } - addonAfter={ - + } + /> +
+ + } + autoComplete="off" + /> + + - } - /> - - - } - autoComplete="off" - /> - - - -
-
-
+ + + +
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' })} > 注册 -
- - navigate({ to: "/pwdRetrieve" })}>忘记密码 - - - - - - - ); -}); +
+ + navigate({ to: '/pwdRetrieve' })}>忘记密码 + + +
+ +
+
+ ) +}) -export const Route = createFileRoute("/login")({ +export const Route = createFileRoute('/login')({ component: Login, -}); +}) -export default Login; +export default Login diff --git a/src/service/system.ts b/src/service/system.ts index e2f57ca..62d3be9 100644 --- a/src/service/system.ts +++ b/src/service/system.ts @@ -1,72 +1,79 @@ -import { IPageResult } from "@/global"; -import request from "../request.ts"; -import { createCURD } from "@/service/base.ts"; -import { System } from "@/types"; +import { IPageResult } from '@/global' +import request from '../request.ts' +import { createCURD } from '@/service/base.ts' +import { System } from '@/types' const systemServ = { dept: { - ...createCURD("/sys/dept"), + ...createCURD('/sys/dept'), tree: () => { - return request.get<{ tree: System.IDepartment }>("/sys/dept/tree"); + return request.get<{ tree: System.IDepartment }>('/sys/dept/tree') }, }, menus: { - ...createCURD("/sys/menu"), + ...createCURD('/sys/menu'), }, uplogin: (data: any) => { - return request.post("/sys/login", data); + return request.post('/sys/login', data) }, emailCode: (data: any) => { - return request.post("/sys/email", data); + return request.post('/sys/email', data) }, emailLogin: (data: any) => { - return request.post("/sys/email/login", data); + return request.post('/sys/email/login', data) }, emailRegister: (data: any) => { - return request.post("/sys/email/reg", data); + return request.post('/sys/email/reg', data) }, telegramCode: (data: any) => { - return request.post("/sys/telegram", data); + return request.post('/sys/telegram', data) }, telegramLogin: (data: any) => { - return request.post("/sys/login/telegram", data); + return request.post('/sys/login/telegram', data) }, pwdRetrieve: (data: any) => { - return request.post("/sys/email/pws", data); + return request.post('/sys/email/pws', data) }, +//验证码 + captcha: () => { + return request.get('/sys/captcha') + }, + captchaCheck: (data: any) => { + return request.post('/sys/captcha/check', data) + }, logout: () => { // }, user: { - ...createCURD("/sys/user"), + ...createCURD('/sys/user'), current: () => { - return request.get("/sys/user/info"); + return request.get('/sys/user/info') }, menus: () => { - return request.get>("/sys/user/menus"); + return request.get>('/sys/user/menus') }, resetPassword: (id: number) => { - return request.post(`/sys/user/reset/password`, { id }); + return request.post(`/sys/user/reset/password`, { id }) }, }, role: { - ...createCURD("/sys/role"), + ...createCURD('/sys/role'), }, logs: { login: { - ...createCURD("/sys/log/login"), + ...createCURD('/sys/log/login'), clear: (params: { start: string; end: string }) => { - return request.post("/sys/log/login/clear", params); + return request.post('/sys/log/login/clear', params) }, }, }, -}; +} -export default systemServ; +export default systemServ