Browse Source

commit

main
mac 4 months ago
parent
commit
8973366599
  1. 4
      package.json
  2. BIN
      src/assets/en.jpg
  3. BIN
      src/assets/zh.jpg
  4. 10
      src/components/CountdownTimer.tsx
  5. 69
      src/components/LanguageMenu.tsx
  6. 1
      src/index.tsx
  7. 21
      src/language/index.ts
  8. 24
      src/language/zh.json
  9. 37
      src/pages/create-proxy/index.tsx
  10. 16
      src/pages/forget/index.tsx
  11. 28
      src/pages/login/index.tsx
  12. 8
      src/router/index.tsx
  13. 27
      yarn.lock

4
package.json

@ -14,12 +14,14 @@
"axios": "^1.7.2",
"echarts": "^5.5.0",
"echarts-for-react": "^3.0.2",
"i18next": "^23.11.5",
"js-md5": "^0.8.3",
"mobx": "^6.12.4",
"mobx-react": "^9.1.1",
"qrcode": "^1.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.2",
"react-icons": "^5.2.1",
"react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
@ -56,4 +58,4 @@
"devDependencies": {
"@types/qrcode": "^1.5.5"
}
}
}

BIN
src/assets/en.jpg

After

Width: 200  |  Height: 132  |  Size: 6.4 KiB

BIN
src/assets/zh.jpg

After

Width: 200  |  Height: 133  |  Size: 1.6 KiB

10
src/components/CountdownTimer.tsx

@ -1,4 +1,5 @@
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { useTranslation } from 'react-i18next';
interface CountdownTimerProps {
initialSeconds: number,
@ -15,10 +16,10 @@ export interface CountdownTimerRef {
const CountdownTimer = forwardRef<CountdownTimerRef, CountdownTimerProps>((props, ref) => {
const { initialSeconds, onComplete } = props
const { t } = useTranslation()
const [seconds, setSeconds] = useState(initialSeconds);
const [isActive, setIsActive] = useState(false);
const [title, setTitle] = useState('发送验证码')
const [title, setTitle] = useState(t('Send verification code'))
useImperativeHandle(ref, () => ({
handleStop,
@ -29,7 +30,7 @@ const CountdownTimer = forwardRef<CountdownTimerRef, CountdownTimerProps>((props
const handleStop = () => {
setIsActive(false);
setTitle('发送验证码')
setTitle(t('Send verification code'))
};
const handleStart = () => {
@ -54,11 +55,10 @@ const CountdownTimer = forwardRef<CountdownTimerRef, CountdownTimerProps>((props
}, 1000);
} else {
clearInterval(interval)
seconds <= 0 && setTitle('重新发送')
seconds <= 0 && setTitle(t('Resend'))
setIsActive(false)
// onComplete && onComplete(seconds)
}
console.log(1);
return () => clearInterval(interval);
}, [isActive, seconds, onComplete]);

69
src/components/LanguageMenu.tsx

@ -0,0 +1,69 @@
import { useState } from "react"
import { TRADER_LANGUAGE } from "../language"
import { ConfigProvider, Popover } from "antd"
import { useTranslation } from "react-i18next"
interface LanguageMenuProps {
width?: number
}
const LanguageMenu = (props: LanguageMenuProps) => {
const { width = 50 } = props
const [open, setOpen] = useState(false);
const { i18n } = useTranslation()
const [lang, setLang] = useState(window.localStorage.getItem(TRADER_LANGUAGE) || 'zh')
const [lns, setLns] = useState([
{
key: 'zh',
title: '中文简体'
},
{
key: 'en',
title: 'English'
}
])
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
const changeLanguage = (key: string) => {
setLang(key)
window.localStorage.setItem(TRADER_LANGUAGE, key)
i18n.changeLanguage(key)
handleOpenChange(false)
}
return (
<div>
<ConfigProvider
theme={{
components: {
Popover: {
titleMinWidth: 100
}
}
}}
>
<Popover
open={open}
onOpenChange={handleOpenChange}
content={<div className="tac">
{
lns.map((item, index) => (
<div key={item.key} className={`tp ${index === lns.length - 1 && 'mt-1'}`} onClick={() => changeLanguage(item.key)}>{item.title}</div>
))
}
</div>}
trigger="click"
>
<img className="tp" src={require(`../assets/${lang}.jpg`)} alt="" style={{ width, height: 'auto' }} />
</Popover>
</ConfigProvider>
</div>
)
}
export default LanguageMenu

1
src/index.tsx

@ -4,6 +4,7 @@ import App from './App';
import reportWebVitals from './reportWebVitals';
import './styles/global.scss'
import { ConfigProvider } from 'antd';
import './language'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement

21
src/language/index.ts

@ -0,0 +1,21 @@
import zh from "./zh.json";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
export const TRADER_LANGUAGE = "TRADER_LANGUAGE";
const resources = {
zh: {
translation: { ...zh },
},
};
i18n.use(initReactI18next).init({
resources,
lng: window.sessionStorage.getItem(TRADER_LANGUAGE) || "zh",
interpolation: {
escapeValue: false,
},
});
export default i18n;

24
src/language/zh.json

@ -0,0 +1,24 @@
{
"account": "账号",
"password": "密码",
"Verification Code": "验证码",
"Sign in": "登录",
"Forget password?": "忘记密码?",
"Create a proxy account": "创建代理账户",
"Invalid account": "无效的账号",
"login successful": "登录成功",
"Password sent to email": "密码发送至邮箱",
"Retrieve immediately": "立即找回",
"Already have an account? Sign in": "已有账号,登录",
"name": "姓名",
"invitation code": "邀请码",
"email": "邮箱",
"invalid email": "无效的邮箱",
"Login password, (letters + numbers) length 8-32": "登录密码,(字母+数字)长度8-32",
"Register now": "立即注册",
"Send verification code": "发送验证码",
"Resend": "重新发送",
"Register Successful": "注册成功",
"Verification code sent successfully": "验证码发送成功",
"Failed to send verification code": "验证码发送失败"
}

37
src/pages/create-proxy/index.tsx

@ -5,6 +5,7 @@ import { useRouter } from '../../hooks/useRouter'
import { http_register, http_send_email } from '../../http/api';
import CountdownTimer, { CountdownTimerRef } from '../../components/CountdownTimer';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
interface FieldType {
name: string;
@ -19,14 +20,14 @@ const CreateProxy = () => {
const { push, location } = useRouter()
const [form] = Form.useForm();
const countdownRef = useRef<CountdownTimerRef>(null)
console.log(location.search);
const { t } = useTranslation()
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
try {
const res: any = await http_register(values)
if (res.code === 0) {
notification.success({
message: '注册成功'
message: t('Register Successful')
})
push('/login', null, true)
}
@ -37,11 +38,11 @@ const CreateProxy = () => {
const validatePassword = (_: any, value: string) => {
if (!value) {
return Promise.reject(new Error("请输入密码"));
return Promise.reject(new Error(t('password')));
}
const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,32}$/;
if (!regex.test(value)) {
return Promise.reject(new Error("密码必须包含字母和数字,长度为8-32位"));
return Promise.reject(new Error(t('Login password, (letters + numbers) length 8-32')));
}
return Promise.resolve();
};
@ -57,11 +58,11 @@ const CreateProxy = () => {
})
if (res.code === 0) {
notification.success({
message: '验证码发送成功'
message: t('Verification code sent successfully')
})
} else {
notification.error({
message: '验证码发送失败'
message: t('Failed to send verification code')
})
countdownRef.current?.handleStop()
}
@ -86,30 +87,30 @@ const CreateProxy = () => {
<div style={{ maxWidth: 500, width: '100%', padding: '0 20px' }}>
<div className='row-center'>
<div className='tac mb-5 fz-24 tbs'></div>
<div className='tac mb-5 fz-24 tbs'>{t('Create a proxy account')}</div>
</div>
<Form onFinish={onFinish} form={form}>
<Form.Item name="name" rules={[{ required: true, message: "请输入姓名" }]}>
<Input className='input' placeholder='姓名' ></Input>
<Form.Item name="name" rules={[{ required: true, message: t('name') }]}>
<Input className='input' placeholder={t('name')} ></Input>
</Form.Item>
<Form.Item name="inviti_code" rules={[{ required: true, message: "请输入邀请码" }]}>
<Input className='input' placeholder='邀请码' ></Input>
<Form.Item name="inviti_code" rules={[{ required: true, message: t('invitation code') }]}>
<Input className='input' placeholder={t('invitation code')} ></Input>
</Form.Item>
<Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }, { type: 'email', message: "请输入有效的邮箱!" }]}>
<Input className='input' placeholder='电子邮件' ></Input>
<Form.Item name="email" rules={[{ required: true, message: t('email') }, { type: 'email', message: t('invalid email') }]}>
<Input className='input' placeholder={t('email')} ></Input>
</Form.Item>
<Form.Item name="password" rules={[{ validator: validatePassword }]}>
<Input.Password className='input' style={{
backgroundColor: 'hsla(0, 0%, 100%, .2)'
}} placeholder='登录密码,(字母+数字)长度8-32' />
}} placeholder={t('Login password, (letters + numbers) length 8-32')} />
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: "请输入验证码" }]}>
<Form.Item name="code" rules={[{ required: true, message: t('Verification Code') }]}>
<div className='row-between'>
<Input className='input' placeholder='验证码' ></Input>
<Input className='input' placeholder={t('Verification Code')} ></Input>
<Button className='verify-button' onClick={getVerifyCode}>
<CountdownTimer ref={countdownRef} initialSeconds={60} />
</Button>
@ -117,11 +118,11 @@ const CreateProxy = () => {
</Form.Item>
<Form.Item>
<Button htmlType='submit' style={{ height: 40, fontWeight: 'normal' }}></Button>
<Button htmlType='submit' style={{ height: 40, fontWeight: 'normal' }}>{t('Register now')}</Button>
</Form.Item>
<Form.Item>
<Button style={{ height: 40, backgroundColor: '#383d49', color: '#f7b93f', fontWeight: 'normal' }} onClick={() => push(-1)}></Button>
<Button style={{ height: 40, backgroundColor: '#383d49', color: '#f7b93f', fontWeight: 'normal' }} onClick={() => push(-1)}>{t('Sign in')}</Button>
</Form.Item>
</Form>
</div>

16
src/pages/forget/index.tsx

@ -4,6 +4,7 @@ import Button from '../../components/Button'
import { useRouter } from '../../hooks/useRouter'
import { useEffect, useState } from 'react';
import { http_code, http_forget } from '../../http/api';
import { useTranslation } from 'react-i18next';
interface FieldType {
email: string;
@ -16,6 +17,7 @@ const ForGet = () => {
const [form] = Form.useForm()
const [codeId, setCodeId] = useState('')
const [codeUrl, setCodeUrl] = useState('')
const { t } = useTranslation()
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
const res: any = await http_forget({
@ -26,7 +28,7 @@ const ForGet = () => {
if (res.code === 0) {
form.resetFields()
notification.success({
message: '密码已发送至您的邮箱!'
message: t('Password sent to email')
})
}
};
@ -52,20 +54,20 @@ const ForGet = () => {
<div className='row-center'>
<div style={{ maxWidth: 500, width: '100%', padding: '0 20px' }}>
<Form onFinish={onFinish} form={form}>
<Form.Item name="email" rules={[{ required: true, message: '请输入邮箱' }, { type: 'email', message: '请输入有效的邮箱!' }]}>
<Input className='input' placeholder='邮箱' ></Input>
<Form.Item name="email" rules={[{ required: true, message: t('account') }, { type: 'email', message: '请输入有效的邮箱!' }]}>
<Input className='input' placeholder={t('account')} ></Input>
</Form.Item>
<Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}>
<Form.Item name="code" rules={[{ required: true, message: t('Verification Code') }]}>
<div style={{ position: 'relative' }}>
<Input className='input' placeholder='验证码'></Input>
<Input className='input' placeholder={t('Verification Code')}></Input>
<img src={codeUrl} className='verify-img' alt="" onClick={getCode} />
</div>
</Form.Item>
<Form.Item>
<Button htmlType='submit'></Button>
<Button htmlType='submit'>{t('Retrieve immediately')}</Button>
</Form.Item>
</Form>
<div onClick={() => push(-1)} className='tac tp'></div>
<div onClick={() => push(-1)} className='tac tp'>{t('Already have an account? Sign in')}</div>
</div>
</div>
</div>

28
src/pages/login/index.tsx

@ -5,6 +5,7 @@ import { useRouter } from '../../hooks/useRouter'
import store from '../../store'
import { useEffect, useState } from 'react'
import { http_code, http_login } from '../../http/api'
import { useTranslation } from 'react-i18next'
type FieldType = {
email: string;
password: string;
@ -16,6 +17,7 @@ const Login = () => {
const { push } = useRouter()
const [codeId, setCodeId] = useState('')
const [codeUrl, setCodeUrl] = useState('')
const { t } = useTranslation()
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
const res: any = await http_login({
@ -24,7 +26,7 @@ const Login = () => {
})
if (res.code === 0) {
notification.success({
message: '登录成功'
message: t('login successful')
})
store.setToken(res.data.token)
push('/', null, true)
@ -42,10 +44,6 @@ const Login = () => {
}
}
const login = async () => {
}
useEffect(() => {
getCode()
}, [])
@ -66,49 +64,49 @@ const Login = () => {
rules={[
{
type: 'email',
message: '请输入有效的邮箱!',
message: t('Invalid account'),
},
{
required: true,
message: '请输入邮箱!',
message: t('account'),
},
]}
>
<Input className='input' placeholder='邮箱' ></Input>
<Input className='input' placeholder={t('account')} ></Input>
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: '请输入密码!',
message: t('password'),
},
]}
>
<Input.Password className='input' placeholder='请输入密码' />
<Input.Password className='input' placeholder={t('password')} />
</Form.Item>
<Form.Item
name="code"
rules={[
{
required: true,
message: '请输入验证码!',
message: t('Verification Code'),
},
]}
>
<div style={{ position: 'relative' }}>
<Input className='input' placeholder='验证码'></Input>
<Input className='input' placeholder={t('Verification Code')}></Input>
<img src={codeUrl} className='verify-img' alt="" onClick={getCode} />
</div>
</Form.Item>
<Form.Item>
<Button htmlType="submit"></Button>
<Button htmlType="submit">{t("Sign in")}</Button>
</Form.Item>
</Form>
<div className='tac tp' style={{ cursor: 'pointer' }} onClick={() => push('/forget')}>?</div>
<div className='tac tp' style={{ cursor: 'pointer' }} onClick={() => push('/forget')}>{t('Forget password?')}</div>
<div>
<Button className='create-button' onClick={() => push('/createProxy')}></Button>
<Button className='create-button' onClick={() => push('/createProxy')}>{t('Create a proxy account')}</Button>
</div>
</div>
</div>

8
src/router/index.tsx

@ -10,6 +10,7 @@ import { unLoginPath } from "./routes";
import { Divider, Modal } from "antd";
import { NotifyStatus_Type } from "../types";
import { http_notify } from "../http/api";
import LanguageMenu from "../components/LanguageMenu";
const LayoutRouter = () => {
@ -75,7 +76,12 @@ const LayoutRouter = () => {
<>
{
!token ? (
<RenderRouter />
<>
{/* <div style={{ position: 'absolute', top: 20, right: 20 }}>
<LanguageMenu />
</div> */}
<RenderRouter />
</>
) : (
<div className="layout">
<div className="container-header"></div>

27
yarn.lock

@ -5445,6 +5445,13 @@ html-minifier-terser@^6.0.2:
relateurl "^0.2.7"
terser "^5.10.0"
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
html-webpack-plugin@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0"
@ -5539,6 +5546,13 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
i18next@^23.11.5:
version "23.11.5"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.11.5.tgz#d71eb717a7e65498d87d0594f2664237f9e361ef"
integrity sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==
dependencies:
"@babel/runtime" "^7.23.2"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -8633,6 +8647,14 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
react-i18next@^14.1.2:
version "14.1.2"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.2.tgz#cd57a755f25a32a5fcc3dbe546cf3cc62b4f3ebd"
integrity sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==
dependencies:
"@babel/runtime" "^7.23.9"
html-parse-stringify "^3.0.1"
react-icons@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.2.1.tgz#28c2040917b2a2eda639b0f797bff1888e018e4a"
@ -10159,6 +10181,11 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"

Loading…
Cancel
Save