fix: 旋转图片验证对接完成

This commit is contained in:
landaiqing
2024-05-10 22:45:09 +08:00
parent 7b1fbbf733
commit d3b7d8195e
11 changed files with 783 additions and 838 deletions

View File

@@ -7,7 +7,7 @@ VITE_APP_BASE_API='/api'
VITE_APP_TITLE=生产环境
# 网络请求公用地址
VITE_API_BASE_URL=''
VITE_API_BASE_URL='http://1.95.0.111:3000'
VITE_TITLE_NAME='五味子云存储'
@@ -15,4 +15,4 @@ VITE_TITLE_NAME='五味子云存储'
VITE_APP_TOKEN_KEY='token'
# the upload url
VITE_UPLOAD_URL='http://127.0.0.1:3000'
VITE_UPLOAD_URL='http://1.95.0.111:4000'

View File

@@ -10,27 +10,27 @@
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.3.6",
"@ant-design/pro-components": "^2.7.0",
"@ant-design/icons": "^5.3.7",
"@ant-design/pro-components": "^2.7.1",
"@ant-design/use-emotion-css": "^1.0.4",
"@babel/preset-env": "^7.24.5",
"@types/crypto-js": "^4.2.2",
"@vitejs/plugin-legacy": "^5.3.2",
"antd": "^5.16.1",
"@vitejs/plugin-legacy": "^5.4.0",
"antd": "^5.17.0",
"autoprefixer": "^10.4.19",
"axios": "^1.6.8",
"core-js": "3",
"core-js": "^3.37.0",
"crypto-js": "^4.2.0",
"gsap": "^3.12.5",
"jsencrypt": "^3.3.2",
"localforage": "^1.10.0",
"mobx": "^6.12.3",
"mobx-persist-store": "^1.1.4",
"mobx-persist-store": "^1.1.5",
"mobx-react": "^9.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-rotate-captcha": "^1.0.26",
"react-router-dom": "^6.22.3",
"react-router-dom": "^6.23.0",
"regenerator-runtime": "^0.14.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
@@ -39,28 +39,28 @@
"devDependencies": {
"@rollup/plugin-babel": "^6.0.4",
"@types/gsap": "^3.0.0",
"@types/node": "^20.11.30",
"@types/react": "^18.2.69",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"@types/node": "^20.12.11",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"less": "^4.2.0",
"postcss-less": "^6.0.0",
"postcss-preset-env": "^9.5.9",
"postcss-preset-env": "^9.5.11",
"prettier": "^3.2.5",
"stylelint": "^16.2.1",
"stylelint-config-recess-order": "^5.0.0",
"stylelint": "^16.5.0",
"stylelint-config-recess-order": "^5.0.1",
"stylelint-config-standard-less": "^3.0.1",
"stylelint-order": "^6.0.4",
"typescript": "^5.4.3",
"typescript": "^5.4.5",
"unplugin-imagemin": "^0.5.18",
"vite": "^5.2.3"
"vite": "^5.2.11"
}
}

481
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

16
src/api/captcha/api.ts Normal file
View File

@@ -0,0 +1,16 @@
import web from '@/utils/axios/web.ts'
/**
* 获取验证码
*/
export const getCaptcha = () => {
return web.post('/ReactRotateCaptcha/get')
}
/**
* 验证验证码
* @param data
* @constructor
*/
export const VerfiyCaptcha = (data: any) => {
return web.post('/ReactRotateCaptcha/verfiy', data)
}

View File

@@ -1,51 +1,14 @@
import type { TicketInfoType, TokenInfoType } from 'react-rotate-captcha'
import { handle } from './canvas'
import wallhaven from '@/assets/images/wallhaven.jpg'
import { getCaptcha, VerfiyCaptcha } from '@/api/captcha/api.ts'
export type ActionType = {
code: 0 | 1
msg: string
}
const tokenRaw = 'Nvuv8LdXUNRAVW022Gm7HkGc7RTDoEmU'
const info = {
angle: -1,
sid: '',
ticket: '',
}
export async function checkTicket(ticket?: TicketInfoType) {
const { sid, ticket: ticketRaw } = info
const { data } = ticket || {}
const isWait = sid !== '' && ticketRaw !== ''
const success = sid === data?.sid && ticketRaw === data.ticket
const result =
isWait && success
? {
code: 0,
msg: 'Successful',
}
: {
code: 1,
msg: 'Failed',
}
return result as ActionType
}
let image: string = ''
export async function get(): Promise<TokenInfoType> {
info.angle = -1
info.sid = ''
info.ticket = ''
return {
code: 0,
data: {
str: 'wallhaven1',
token: tokenRaw,
},
msg: 'success',
}
const res: any = await getCaptcha()
image = res.data.str
return res
}
export function isSupportWebp() {
@@ -62,10 +25,7 @@ export function isSupportWebp() {
}
export async function load() {
const [degree, src] = await handle(wallhaven)
info.angle = degree
console.log('degree', degree)
return src
return image
}
export function sleep(time: number) {
@@ -75,25 +35,10 @@ export function sleep(time: number) {
}
export async function verify(token: string, deg: number): Promise<TicketInfoType> {
console.log(deg)
console.log(info.angle)
const { angle } = info
const success = token === tokenRaw && Math.abs(deg - angle) <= 5
info.sid = Math.random().toString(36).slice(-8)
// info.ticket = crypto.randomUUID()
return angle >= 0 && success
? {
code: 0,
data: {
sid: info.sid,
ticket: info.ticket,
},
msg: 'Success',
}
: {
code: 1,
msg: 'Fail verify',
}
const data: any = {
token: token,
deg: deg,
}
const res: any = await VerfiyCaptcha(data)
return res
}

View File

@@ -1,5 +1 @@
import web from '@/utils/axios/web.ts'
export const getPublicKey = () => {
return web.get('/encrypt/getPublicKey')
}

View File

@@ -4,197 +4,197 @@ import { ScrollTrigger } from 'gsap/ScrollTrigger'
import './index.less'
import SvgIcon from '@/components/SvgIcon/SvgIcon.tsx'
const HomeIndex: React.FC = () => {
const animationFunction=()=>{
if (!CSS.supports('animation-timeline: scroll()')) {
// const SPAN = 'max(45vw, 260px)';
const CONFIG = [
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
},
y: -10,
r: -8,
h: 160,
w: (el: any) => {
return Math.max(320, el.parentNode.offsetWidth * 0.55)
},
},
const animationFunction = () => {
if (!CSS.supports('animation-timeline: scroll()')) {
// const SPAN = 'max(45vw, 260px)';
const CONFIG = [
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
},
y: -10,
r: -8,
h: 160,
w: (el: any) => {
return Math.max(320, el.parentNode.offsetWidth * 0.55)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
},
y: -50,
r: 15,
h: 360,
w: (el: any) => {
return Math.max(220, el.parentNode.offsetWidth * 0.3)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
},
y: -50,
r: 15,
h: 360,
w: (el: any) => {
return Math.max(220, el.parentNode.offsetWidth * 0.3)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
},
y: -30,
r: 6,
h: 300,
w: (el: any) => {
return Math.max(330, el.parentNode.offsetWidth * 0.55)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
},
y: -30,
r: 6,
h: 300,
w: (el: any) => {
return Math.max(330, el.parentNode.offsetWidth * 0.55)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
},
y: -30,
r: -5,
h: 400,
w: (el: any) => {
return Math.max(305, el.parentNode.offsetWidth * 0.45)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
},
y: -30,
r: -5,
h: 400,
w: (el: any) => {
return Math.max(305, el.parentNode.offsetWidth * 0.45)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
},
y: -45,
r: -20,
h: 525,
w: (el: any) => {
return Math.max(160, el.parentNode.offsetWidth * 0.3)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
},
y: -45,
r: -20,
h: 525,
w: (el: any) => {
return Math.max(160, el.parentNode.offsetWidth * 0.3)
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
},
y: 10,
r: 10,
h: 160,
w: (el: any) => {
return Math.max(320, el.parentNode.offsetWidth * 0.55)
},
},
]
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
},
y: 10,
r: 10,
h: 160,
w: (el: any) => {
return Math.max(320, el.parentNode.offsetWidth * 0.55)
},
},
]
gsap.registerPlugin(ScrollTrigger)
console.info('gsap: ScrollTrigger registered')
gsap.registerPlugin(ScrollTrigger)
console.info('gsap: ScrollTrigger registered')
gsap.set('.hero', { position: 'absolute' })
gsap.set('.hero', { position: 'absolute' })
const cards = document.querySelectorAll('.card')
const cards = document.querySelectorAll('.card')
for (const [index, card] of [...cards].entries()) {
if (CONFIG[index]) {
gsap.from(card, {
x: CONFIG[index].x,
yPercent: CONFIG[index].y,
height: `${CONFIG[index].h}%`,
rotate: CONFIG[index].r,
width: CONFIG[index].w,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 50%',
scrub: true,
},
})
}
}
for (const [index, card] of [...cards].entries()) {
if (CONFIG[index]) {
gsap.from(card, {
x: CONFIG[index].x,
yPercent: CONFIG[index].y,
height: `${CONFIG[index].h}%`,
rotate: CONFIG[index].r,
width: CONFIG[index].w,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 50%',
scrub: true,
},
})
}
}
gsap.from(
[
'.card__content',
'.card--two .card__column:last-of-type',
'.card--three .card__column:last-of-type',
'.card--five .card__column:last-of-type',
],
gsap.from(
[
'.card__content',
'.card--two .card__column:last-of-type',
'.card--three .card__column:last-of-type',
'.card--five .card__column:last-of-type',
],
{
y: '-100cqh',
scrollTrigger: {
trigger: '.scroller',
start: 'top 80%',
end: 'top top',
scrub: true,
},
},
)
{
y: '-100cqh',
scrollTrigger: {
trigger: '.scroller',
start: 'top 80%',
end: 'top top',
scrub: true,
},
},
)
gsap.from(['.card__avatar img', '.password svg'], {
opacity: 0,
scrollTrigger: {
trigger: '.scroller',
start: 'top 50%',
end: 'top top',
scrub: true,
},
})
gsap.from(['.card__avatar img', '.password svg'], {
opacity: 0,
scrollTrigger: {
trigger: '.scroller',
start: 'top 50%',
end: 'top top',
scrub: true,
},
})
gsap.from(['.card--five .card__dummy', '.card--six .card__dummy'], {
width: (el: any) => el.parentNode.offsetWidth * 0.26,
scrollTrigger: {
trigger: '.scroller',
start: 'top 80%',
end: 'top top',
scrub: true,
},
})
gsap.from(['.card--five .card__dummy', '.card--six .card__dummy'], {
width: (el: any) => el.parentNode.offsetWidth * 0.26,
scrollTrigger: {
trigger: '.scroller',
start: 'top 80%',
end: 'top top',
scrub: true,
},
})
gsap.from(['.card--one .card__avatar', '.card--four .card__avatar'], {
scale: 2,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top top',
scrub: true,
},
})
gsap.from(['.card--one .card__avatar', '.card--four .card__avatar'], {
scale: 2,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top top',
scrub: true,
},
})
gsap.from('.card--two .card__avatar', {
width: (el: any) => Math.max(330, el.parentNode.offsetWidth * 0.55) - 32,
borderRadius: '12px',
height: 'calc(300cqh - 2rem)',
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
scrub: true,
},
})
gsap.from('.card--two .card__avatar', {
width: (el: any) => Math.max(330, el.parentNode.offsetWidth * 0.55) - 32,
borderRadius: '12px',
height: 'calc(300cqh - 2rem)',
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
scrub: true,
},
})
gsap.from('.card--six .card__column:last-of-type .card__company', {
width: 120,
x: '-1rem',
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
scrub: true,
},
})
gsap.from('.card--six .card__column:last-of-type .card__company', {
width: 120,
x: '-1rem',
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
scrub: true,
},
})
gsap.from('.cta', {
scale: 1,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
scrub: true,
},
})
}
}
gsap.from('.cta', {
scale: 1,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
scrub: true,
},
})
}
}
useEffect(() => {
document.body.classList.add('body')
window.addEventListener('resize', animationFunction);
window.addEventListener('resize', animationFunction)
return () => {
document.body.classList.remove('body')
window.removeEventListener('resize', animationFunction);
window.removeEventListener('resize', animationFunction)
}
}, [])
return (

View File

@@ -1,4 +1,4 @@
// import React from 'react'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
import './assets/styles/index.less'
@@ -8,9 +8,9 @@ import { Provider as MobxProvider } from 'mobx-react'
import { RootStore } from '@/store'
const router = createBrowserRouter(routeConfig)
ReactDOM.createRoot(document.getElementById('root')!).render(
// <React.StrictMode>
<React.StrictMode>
<MobxProvider {...RootStore}>
<RouterProvider router={router} />
</MobxProvider>,
// </React.StrictMode>,
</React.StrictMode>,
)

View File

@@ -18,10 +18,10 @@ class Request {
// 全局响应拦截
this.instance.interceptors.response.use(
(res) => {
if (res.data.code && res.data.code !== 200) {
message.error(res.data.message).then()
return Promise.reject(res.data)
}
// if (res.data.code && res.data.code !== 200) {
// message.error(res.data.message).then()
// return Promise.reject(res.data)
// }
return res.data
},
(error) => {

View File

@@ -9,14 +9,14 @@ import {
} from '@ant-design/icons'
import { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-components'
import { Divider, Space, Tabs, message, Image, Alert, Form, Button } from 'antd'
import { CSSProperties } from 'react'
import { CSSProperties, useRef } from 'react'
import { useState } from 'react'
import logo from '@/assets/icons/schisandra.svg'
import qrCode from '@/assets/images/login_qrcode-landaiqing.jpg'
import styles from './index.module.less'
import { observer } from 'mobx-react'
import FooterComponent from '@/components/Footer'
import RotateCaptcha from 'react-rotate-captcha'
import RotateCaptcha, { CaptchaInstance } from 'react-rotate-captcha'
import { get, load, verify } from '@/api/captcha/index.ts'
// import useStore from '@/utils/store/useStore.tsx'
type LoginType = 'account' | 'phone'
@@ -30,8 +30,7 @@ const iconStyles: CSSProperties = {
export default observer(() => {
const [form] = Form.useForm()
const [open, setOpen] = useState(false)
const captcha = useRef<CaptchaInstance>(null)
const items = [
{
label: (
@@ -58,322 +57,320 @@ export default observer(() => {
console.log(formData)
}
return (
<div className={styles.container}>
<div className={styles.content}>
<Space className={styles.content_content}>
<Space className={styles.login_content}>
<Space align='center' className={styles.mp_code}>
<Space direction='vertical' align='center'>
<span className={styles.mp_code_title}></span>
<Image
preview={false}
height={210}
width={200}
className={styles.mp_code_img}
// src={generateMpRegCodeData.data?.qrCodeUrl}
src={qrCode}
/>
<Alert
// message={(<span>微信扫码<span>关注公众号</span></span>)}
description={
<div>
<span>
<span className={styles.mp_tips}></span>
</span>
<br />
<RotateCaptcha get={get} load={load} verify={verify} limit={2} ref={captcha}>
<div className={styles.container}>
<div className={styles.content}>
<Space className={styles.content_content}>
<Space className={styles.login_content}>
<Space align='center' className={styles.mp_code}>
<Space direction='vertical' align='center'>
<span className={styles.mp_code_title}></span>
<Image
preview={false}
height={210}
width={200}
className={styles.mp_code_img}
// src={generateMpRegCodeData.data?.qrCodeUrl}
src={qrCode}
/>
<Alert
// message={(<span>微信扫码<span>关注公众号</span></span>)}
description={
<div>
<span>
<span className={styles.mp_tips}>
</span>
</span>
<br />
</div>
}
// type="success"
showIcon={true}
className={styles.alert}
icon={<WechatOutlined />}
/>
</Space>
</Space>
<Form
form={form}
className={styles.login_form}
initialValues={{
autoLogin: true,
}}>
<Space direction='vertical' align='center'>
<Space className={styles.logo}>
<img
alt='logo'
src={logo}
style={{ width: '44px', height: '44px' }}
/>
<span></span>
</Space>
<div className={styles.subTitle}></div>
</Space>
<Tabs
centered={true}
items={items}
activeKey={loginType}
onChange={(activeKey) =>
setLoginType(activeKey as LoginType)
}></Tabs>
{loginType === 'account' && (
<>
<ProFormText
name='username'
fieldProps={{
size: 'large',
prefix: <UserOutlined className={'prefixIcon'} />,
}}
placeholder={'请输入账号/邮箱/电话号码'}
rules={[
{
required: true,
message: '请输入用户名!',
},
]}
/>
<ProFormText.Password
name='password'
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'请输入密码'}
rules={[
{
required: true,
message: '请输入密码!',
},
{
pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message:
'密码长度需在6~18位字符且必须包含字母和数字',
},
]}
/>
{/*<ProFormText*/}
{/* addonAfter={CodeImg}*/}
{/* name='code'*/}
{/* fieldProps={{*/}
{/* size: 'large',*/}
{/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/}
{/* autoComplete: 'off',*/}
{/* }}*/}
{/* placeholder='请输入图形验证码'*/}
{/* rules={[*/}
{/* {*/}
{/* required: true,*/}
{/* message: '请输入图形验证码!',*/}
{/* },*/}
{/* {*/}
{/* pattern: /^[a-zA-Z0-9]{5}$/,*/}
{/* message: '图形验证码格式不正确',*/}
{/* },*/}
{/* ]}*/}
{/*/>*/}
</>
)}
{loginType === 'phone' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined className={'prefixIcon'} />,
autoComplete: 'off',
}}
name='mobile'
placeholder={'请输入手机号'}
rules={[
{
required: true,
message: '请输入手机号!',
},
{
pattern: /^1\d{10}$/,
message: '手机号格式错误!',
},
]}
/>
{/*<ProFormText*/}
{/* addonAfter={CodeImg}*/}
{/* name='code'*/}
{/* fieldProps={{*/}
{/* size: 'large',*/}
{/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/}
{/* autoComplete: 'off',*/}
{/* }}*/}
{/* placeholder='请输入图形验证码'*/}
{/* rules={[*/}
{/* {*/}
{/* required: true,*/}
{/* message: '请输入图形验证码!',*/}
{/* },*/}
{/* {*/}
{/* pattern: /^[a-zA-Z0-9]{5}$/,*/}
{/* message: '图形验证码格式不正确',*/}
{/* },*/}
{/* ]}*/}
{/*/>*/}
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
captchaProps={{
size: 'large',
}}
placeholder={'请输入验证码'}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${'获取验证码'}`
}
return '获取验证码'
}}
name='captcha'
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
onGetCaptcha={async () => {
captcha.current!.open()
message.success('获取验证码成功验证码为1234')
}}
/>
</>
)}
<div style={{ marginBlockEnd: 14 }}>
<ProFormCheckbox noStyle name='autoLogin'>
</ProFormCheckbox>
<a style={{ float: 'right' }}> </a>
</div>
<Button
type='primary'
block
size='large'
onClick={async () => {
let validateFields
if (loginType === 'account') {
validateFields = ['username', 'password', 'code']
} else {
validateFields = ['mobile', 'captcha', 'code']
}
await form
.validateFields(validateFields)
.then(async (values) => {
if (loginType === 'account') {
captcha.current!.open()
}
await onSubmit(values as API.PhoneRegisterRequest)
})
.catch((errorInfo) => {
console.error(errorInfo)
})
}}>
</Button>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}>
<Divider plain>
<span
style={{
color: '#CCC',
fontWeight: 'normal',
fontSize: 14,
}}>
</span>
</Divider>
<Space align='center' size={24}>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<QqOutlined
style={{ ...iconStyles, color: '#1677FF' }}
/>
</div>
}
// type="success"
showIcon={true}
className={styles.alert}
icon={<WechatOutlined />}
/>
</Space>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<WechatOutlined
style={{ ...iconStyles, color: '#08a327' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<GithubOutlined
style={{ ...iconStyles, color: '#333333' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<GitlabOutlined
style={{ ...iconStyles, color: '#FF6A10' }}
/>
</div>
</Space>
</div>
</Form>
<a href='/register' className={styles.go_to_register}>
<span></span>
</a>
</Space>
<Form
form={form}
className={styles.login_form}
initialValues={{
autoLogin: true,
}}>
<Space direction='vertical' align='center'>
<Space className={styles.logo}>
<img
alt='logo'
src={logo}
style={{ width: '44px', height: '44px' }}
/>
<span></span>
</Space>
<div className={styles.subTitle}></div>
</Space>
<Tabs
centered={true}
items={items}
activeKey={loginType}
onChange={(activeKey) =>
setLoginType(activeKey as LoginType)
}></Tabs>
{loginType === 'account' && (
<>
<ProFormText
name='username'
fieldProps={{
size: 'large',
prefix: <UserOutlined className={'prefixIcon'} />,
}}
placeholder={'请输入账号/邮箱/电话号码'}
rules={[
{
required: true,
message: '请输入用户名!',
},
]}
/>
<ProFormText.Password
name='password'
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'请输入密码'}
rules={[
{
required: true,
message: '请输入密码!',
},
{
pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message:
'密码长度需在6~18位字符且必须包含字母和数字',
},
]}
/>
{/*<ProFormText*/}
{/* addonAfter={CodeImg}*/}
{/* name='code'*/}
{/* fieldProps={{*/}
{/* size: 'large',*/}
{/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/}
{/* autoComplete: 'off',*/}
{/* }}*/}
{/* placeholder='请输入图形验证码'*/}
{/* rules={[*/}
{/* {*/}
{/* required: true,*/}
{/* message: '请输入图形验证码!',*/}
{/* },*/}
{/* {*/}
{/* pattern: /^[a-zA-Z0-9]{5}$/,*/}
{/* message: '图形验证码格式不正确',*/}
{/* },*/}
{/* ]}*/}
{/*/>*/}
</>
)}
{loginType === 'phone' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined className={'prefixIcon'} />,
autoComplete: 'off',
}}
name='mobile'
placeholder={'请输入手机号'}
rules={[
{
required: true,
message: '请输入手机号!',
},
{
pattern: /^1\d{10}$/,
message: '手机号格式错误!',
},
]}
/>
{/*<ProFormText*/}
{/* addonAfter={CodeImg}*/}
{/* name='code'*/}
{/* fieldProps={{*/}
{/* size: 'large',*/}
{/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/}
{/* autoComplete: 'off',*/}
{/* }}*/}
{/* placeholder='请输入图形验证码'*/}
{/* rules={[*/}
{/* {*/}
{/* required: true,*/}
{/* message: '请输入图形验证码!',*/}
{/* },*/}
{/* {*/}
{/* pattern: /^[a-zA-Z0-9]{5}$/,*/}
{/* message: '图形验证码格式不正确',*/}
{/* },*/}
{/* ]}*/}
{/*/>*/}
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
captchaProps={{
size: 'large',
}}
placeholder={'请输入验证码'}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${'获取验证码'}`
}
return '获取验证码'
}}
name='captcha'
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
onGetCaptcha={async () => {
message.success('获取验证码成功验证码为1234')
}}
/>
</>
)}
<div style={{ marginBlockEnd: 14 }}>
<ProFormCheckbox noStyle name='autoLogin'>
</ProFormCheckbox>
<a style={{ float: 'right' }}> </a>
</div>
<Button
type='primary'
block
size='large'
onClick={async () => {
setOpen(true)
let validateFields
if (loginType === 'account') {
validateFields = ['username', 'password', 'code']
} else {
validateFields = ['mobile', 'captcha', 'code']
}
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values as API.PhoneRegisterRequest)
})
.catch((errorInfo) => {
console.error(errorInfo)
})
}}>
</Button>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}>
<Divider plain>
<span
style={{
color: '#CCC',
fontWeight: 'normal',
fontSize: 14,
}}>
</span>
</Divider>
<Space align='center' size={24}>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<QqOutlined style={{ ...iconStyles, color: '#1677FF' }} />
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<WechatOutlined
style={{ ...iconStyles, color: '#08a327' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<GithubOutlined
style={{ ...iconStyles, color: '#333333' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<GitlabOutlined
style={{ ...iconStyles, color: '#FF6A10' }}
/>
</div>
</Space>
</div>
</Form>
<a href='/register' className={styles.go_to_register}>
<span></span>
</a>
</Space>
</Space>
</div>
<FooterComponent></FooterComponent>
</div>
<FooterComponent></FooterComponent>
<RotateCaptcha
get={get}
load={load}
verify={verify}
limit={3}
result={(val) => {
console.log(val)
}}
open={open}
onClose={() => setOpen(false)}
/>
</div>
</RotateCaptcha>
)
})

View File

@@ -149,7 +149,7 @@ export default defineConfig(({ mode }) => {
},
},
esbuild: {
drop: ['console', 'debugger'],
// drop: ['console', 'debugger'],
},
build: {
outDir: 'dist', // 指定输出路径