From c64b7c891f8740d29652ec2034ae751c0ef5710e Mon Sep 17 00:00:00 2001 From: JustSong Date: Sun, 2 Feb 2025 16:02:38 +0800 Subject: [PATCH] chore: update default theme style --- controller/misc.go | 6 +- .../public/locales/en/translation.json | 71 ++++++++++++++++++- .../public/locales/zh/translation.json | 69 ++++++++++++++++-- web/default/src/components/LoginForm.js | 40 +++++------ .../src/components/PasswordResetConfirm.js | 37 ++++------ .../src/components/PasswordResetForm.js | 20 +++--- web/default/src/components/RegisterForm.js | 56 ++++++++++----- 7 files changed, 224 insertions(+), 75 deletions(-) diff --git a/controller/misc.go b/controller/misc.go index ae900870..c2764b57 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -3,12 +3,13 @@ package controller import ( "encoding/json" "fmt" + "net/http" + "strings" + "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/message" "github.com/songquanpeng/one-api/model" - "net/http" - "strings" "github.com/gin-gonic/gin" ) @@ -120,6 +121,7 @@ func SendEmailVerification(c *gin.Context) { "

验证码 %d 分钟内有效,如果不是本人操作,请忽略。

", config.SystemName, code, common.VerificationValidMinutes) err := message.SendEmail(subject, email, content) if err != nil { + c.JSON(http.StatusOK, gin.H{ "success": false, "message": err.Error(), diff --git a/web/default/public/locales/en/translation.json b/web/default/public/locales/en/translation.json index 4e5a4e01..11394445 100644 --- a/web/default/public/locales/en/translation.json +++ b/web/default/public/locales/en/translation.json @@ -571,7 +571,9 @@ "qrcode_placeholder": "Enter an image URL", "buttons": { "save": "Save WeChat Server Settings" - } + }, + "scan_tip": "Scan QR code to follow WeChat Official Account, enter 'code' to get verification code (valid for 3 minutes)", + "code_placeholder": "Verification code" }, "turnstile": { "title": "Turnstile Configuration", @@ -754,5 +756,72 @@ } }, "loading_failed": "Failed to load homepage content..." + }, + "auth": { + "login": { + "title": "User Login", + "username": "Username / Email", + "password": "Password", + "button": "Login", + "forgot_password": "Forgot password?", + "reset_password": "Reset", + "no_account": "No account?", + "register": "Register", + "other_methods": "Other login methods", + "wechat": { + "scan_tip": "Scan QR code to follow WeChat Official Account, enter 'code' to get verification code (valid for 3 minutes)", + "code_placeholder": "Verification code" + } + }, + "register": { + "title": "New User Registration", + "username": "Username (max 12 characters)", + "password": "Password (8-20 characters)", + "confirm_password": "Confirm password", + "email": "Email address", + "verification_code": "Verification code", + "get_code": "Get code", + "get_code_retry": "Retry ({{countdown}})", + "button": "Register", + "has_account": "Have an account?", + "login": "Login" + }, + "reset": { + "title": "Password Reset", + "email": "Email address", + "button": "Submit", + "notice": "The system will send an email containing a reset link to your mailbox. Please check your email.", + "confirm": { + "title": "Password Reset Confirmation", + "new_password": "New password", + "button": "Submit", + "button_disabled": "Password reset completed", + "notice": "New password has been generated, please click the password field or button above to copy. Please login and change your password as soon as possible!" + } + } + }, + "about": { + "title": "About", + "description": "One API is an open-source API management and proxy platform.", + "repository": "Repository: ", + "loading_failed": "Loading failed" + }, + "messages": { + "success": { + "login": "Login successful!", + "register": "Registration successful!", + "verification_code": "Verification code sent, please check your email!", + "password_reset": "Reset email sent, please check your inbox!" + }, + "error": { + "login_expired": "Not logged in or session expired, please login again!", + "password_length": "Password must be at least 8 characters!", + "password_mismatch": "Passwords do not match", + "turnstile_wait": "Please wait a few seconds, Turnstile is checking the environment!", + "root_password": "Please change the default password immediately!" + }, + "notice": { + "password_copied": "New password copied to clipboard: {{password}}" + } } } diff --git a/web/default/public/locales/zh/translation.json b/web/default/public/locales/zh/translation.json index 64c096e1..14f14776 100644 --- a/web/default/public/locales/zh/translation.json +++ b/web/default/public/locales/zh/translation.json @@ -720,10 +720,10 @@ } }, "about": { - "title": "关于系统", - "description": "可在设置页面设置关于内容,支持 HTML & Markdown", - "repository": "项目仓库地址:", - "loading_failed": "加载关于内容失败..." + "title": "关于", + "description": "One API 是一个开源的接口管理和代理平台。", + "repository": "项目地址:", + "loading_failed": "加载失败" }, "footer": { "built_by": "由", @@ -758,5 +758,66 @@ } }, "loading_failed": "加载首页内容失败..." + }, + "auth": { + "login": { + "title": "用户登录", + "username": "用户名 / 邮箱地址", + "password": "密码", + "button": "登录", + "forgot_password": "忘记密码?", + "reset_password": "点击重置", + "no_account": "没有账户?", + "register": "点击注册", + "other_methods": "使用其他方式登录", + "wechat": { + "scan_tip": "微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)", + "code_placeholder": "验证码" + } + }, + "register": { + "title": "新用户注册", + "username": "输入用户名,最长 12 位", + "password": "输入密码,最短 8 位,最长 20 位", + "confirm_password": "再次输入密码", + "email": "输入邮箱地址", + "verification_code": "输入验证码", + "get_code": "获取验证码", + "get_code_retry": "重试 ({{countdown}})", + "button": "注册", + "has_account": "已有账户?", + "login": "点击登录" + }, + "reset": { + "title": "密码重置", + "email": "邮箱地址", + "button": "提交", + "notice": "系统将向您的邮箱发送一封包含重置链接的邮件,请注意查收。", + "confirm": { + "title": "密码重置确认", + "new_password": "新密码", + "button": "提交", + "button_disabled": "密码重置完成", + "notice": "新密码已生成,请点击密码框或上方按钮复制。请及时登录并修改密码!" + } + } + }, + "messages": { + "success": { + "login": "登录成功!", + "register": "注册成功!", + "verification_code": "验证码发送成功,请检查你的邮箱!", + "password_reset": "重置邮件发送成功,请检查邮箱!" + }, + "error": { + "login_expired": "未登录或登录已过期,请重新登录!", + "password_length": "密码长度不得小于 8 位!", + "password_mismatch": "两次输入的密码不一致", + "turnstile_wait": "请稍后几秒重试,Turnstile 正在检查用户环境!", + "root_password": "请立刻修改默认密码!" + }, + "notice": { + "password_copied": "新密码已复制到剪贴板:{{password}}" + } } } diff --git a/web/default/src/components/LoginForm.js b/web/default/src/components/LoginForm.js index 437bc8ad..569aa7e5 100644 --- a/web/default/src/components/LoginForm.js +++ b/web/default/src/components/LoginForm.js @@ -12,12 +12,14 @@ import { Card, } from 'semantic-ui-react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { UserContext } from '../context/User'; import { API, getLogo, showError, showSuccess, showWarning } from '../helpers'; import { onGitHubOAuthClicked, onLarkOAuthClicked } from './utils'; import larkIcon from '../images/lark.svg'; const LoginForm = () => { + const { t } = useTranslation(); const [inputs, setInputs] = useState({ username: '', password: '', @@ -33,7 +35,7 @@ const LoginForm = () => { useEffect(() => { if (searchParams.get('expired')) { - showError('未登录或登录已过期,请重新登录!'); + showError(t('messages.error.login_expired')); } let status = localStorage.getItem('status'); if (status) { @@ -57,7 +59,7 @@ const LoginForm = () => { userDispatch({ type: 'login', payload: data }); localStorage.setItem('user', JSON.stringify(data)); navigate('/'); - showSuccess('登录成功!'); + showSuccess(t('messages.success.login')); setShowWeChatLoginModal(false); } else { showError(message); @@ -82,11 +84,11 @@ const LoginForm = () => { localStorage.setItem('user', JSON.stringify(data)); if (username === 'root' && password === '123456') { navigate('/user/edit'); - showSuccess('登录成功!'); - showWarning('请立刻修改默认密码!'); + showSuccess(t('messages.success.login')); + showWarning(t('messages.error.root_password')); } else { navigate('/token'); - showSuccess('登录成功!'); + showSuccess(t('messages.success.login')); } } else { showError(message); @@ -110,7 +112,7 @@ const LoginForm = () => { style={{ marginBottom: '1.5em' }} > - 用户登录 + {t('auth.login.title')}
@@ -118,7 +120,7 @@ const LoginForm = () => { fluid icon='user' iconPosition='left' - placeholder='用户名 / 邮箱地址' + placeholder={t('auth.login.username')} name='username' value={username} onChange={handleChange} @@ -128,7 +130,7 @@ const LoginForm = () => { fluid icon='lock' iconPosition='left' - placeholder='密码' + placeholder={t('auth.login.password')} name='password' type='password' value={password} @@ -145,7 +147,7 @@ const LoginForm = () => { }} onClick={handleSubmit} > - 登录 + {t('auth.login.button')}
@@ -160,15 +162,15 @@ const LoginForm = () => { }} >
- 忘记密码? + {t('auth.login.forgot_password')} - 点击重置 + {t('auth.login.reset_password')}
- 没有账户? + {t('auth.login.no_account')} - 点击注册 + {t('auth.login.register')}
@@ -182,7 +184,7 @@ const LoginForm = () => { horizontal style={{ color: '#666', fontSize: '0.9em' }} > - 使用其他方式登录 + {t('auth.login.other_methods')}
{
-

- 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) -

+

{t('auth.login.wechat.scan_tip')}

{ fluid size='large' style={{ - background: '#2F73FF', // 使用更现代的蓝色 + background: '#2F73FF', color: 'white', marginBottom: '1.5em', }} onClick={onSubmitWeChatVerificationCode} > - 登录 + {t('auth.login.button')}
diff --git a/web/default/src/components/PasswordResetConfirm.js b/web/default/src/components/PasswordResetConfirm.js index b4d238b4..3b366ee7 100644 --- a/web/default/src/components/PasswordResetConfirm.js +++ b/web/default/src/components/PasswordResetConfirm.js @@ -8,29 +8,23 @@ import { Card, Message, } from 'semantic-ui-react'; -import { - API, - copy, - showError, - showInfo, - showNotice, - showSuccess, -} from '../helpers'; +import { useTranslation } from 'react-i18next'; +import { API, copy, getLogo, showError, showNotice } from '../helpers'; import { useSearchParams } from 'react-router-dom'; const PasswordResetConfirm = () => { + const { t } = useTranslation(); const [inputs, setInputs] = useState({ email: '', token: '', }); const { email, token } = inputs; - const [loading, setLoading] = useState(false); - const [disableButton, setDisableButton] = useState(false); - const [countdown, setCountdown] = useState(30); - const [newPassword, setNewPassword] = useState(''); + const logo = getLogo(); + + const [countdown, setCountdown] = useState(30); const [searchParams, setSearchParams] = useSearchParams(); useEffect(() => { @@ -68,7 +62,7 @@ const PasswordResetConfirm = () => { let password = res.data.data; setNewPassword(password); await copy(password); - showNotice(`新密码已复制到剪贴板:${password}`); + showNotice(t('messages.notice.password_copied', { password })); } else { showError(message); } @@ -90,8 +84,8 @@ const PasswordResetConfirm = () => { textAlign='center' style={{ marginBottom: '1.5em' }} > - - 密码重置确认 + + {t('auth.reset.confirm.title')}
@@ -99,7 +93,7 @@ const PasswordResetConfirm = () => { fluid icon='mail' iconPosition='left' - placeholder='邮箱地址' + placeholder={t('auth.reset.email')} name='email' value={email} readOnly @@ -110,7 +104,7 @@ const PasswordResetConfirm = () => { fluid icon='lock' iconPosition='left' - placeholder='新密码' + placeholder={t('auth.reset.confirm.new_password')} name='newPassword' value={newPassword} readOnly @@ -122,30 +116,29 @@ const PasswordResetConfirm = () => { onClick={(e) => { e.target.select(); navigator.clipboard.writeText(newPassword); - showNotice(`密码已复制到剪贴板:${newPassword}`); + showNotice(t('auth.reset.confirm.notice')); }} /> )}
{newPassword && (

- 新密码已生成,请点击密码框或上方按钮复制。请及时登录并修改密码! + {t('auth.reset.confirm.notice')}

)} diff --git a/web/default/src/components/PasswordResetForm.js b/web/default/src/components/PasswordResetForm.js index f3380ef7..bb5f8d25 100644 --- a/web/default/src/components/PasswordResetForm.js +++ b/web/default/src/components/PasswordResetForm.js @@ -8,21 +8,23 @@ import { Card, Message, } from 'semantic-ui-react'; -import { API, showError, showInfo, showSuccess } from '../helpers'; +import { useTranslation } from 'react-i18next'; +import { API, getLogo, showError, showInfo, showSuccess } from '../helpers'; import Turnstile from 'react-turnstile'; const PasswordResetForm = () => { + const { t } = useTranslation(); const [inputs, setInputs] = useState({ email: '', }); const { email } = inputs; - const [loading, setLoading] = useState(false); const [turnstileEnabled, setTurnstileEnabled] = useState(false); const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); const [turnstileToken, setTurnstileToken] = useState(''); const [disableButton, setDisableButton] = useState(false); const [countdown, setCountdown] = useState(30); + const logo = getLogo(); useEffect(() => { let status = localStorage.getItem('status'); @@ -66,10 +68,12 @@ const PasswordResetForm = () => { ); const { success, message } = res.data; if (success) { - showSuccess('重置邮件发送成功,请检查邮箱!'); + showSuccess(t('auth.reset.notice')); setInputs({ ...inputs, email: '' }); } else { showError(message); + setDisableButton(false); + setCountdown(30); } setLoading(false); } @@ -89,8 +93,8 @@ const PasswordResetForm = () => { textAlign='center' style={{ marginBottom: '1.5em' }} > - - 密码重置 + + {t('auth.reset.title')}
@@ -98,7 +102,7 @@ const PasswordResetForm = () => { fluid icon='mail' iconPosition='left' - placeholder='邮箱地址' + placeholder={t('auth.reset.email')} name='email' value={email} onChange={handleChange} @@ -133,12 +137,12 @@ const PasswordResetForm = () => { marginBottom: '1.5em', }} > - {disableButton ? `重试 (${countdown})` : '提交'} + {disableButton ? t('auth.register.get_code_retry', { countdown }) : t('auth.reset.button')}

- 系统将向您的邮箱发送一封包含重置链接的邮件,请注意查收。 + {t('auth.reset.notice')}

diff --git a/web/default/src/components/RegisterForm.js b/web/default/src/components/RegisterForm.js index 01dfeb83..620dfc5b 100644 --- a/web/default/src/components/RegisterForm.js +++ b/web/default/src/components/RegisterForm.js @@ -6,15 +6,16 @@ import { Header, Image, Message, - Segment, Card, Divider, } from 'semantic-ui-react'; import { Link, useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { API, getLogo, showError, showInfo, showSuccess } from '../helpers'; import Turnstile from 'react-turnstile'; const RegisterForm = () => { + const { t } = useTranslation(); const [inputs, setInputs] = useState({ username: '', password: '', @@ -28,6 +29,8 @@ const RegisterForm = () => { const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); const [turnstileToken, setTurnstileToken] = useState(''); const [loading, setLoading] = useState(false); + const [disableButton, setDisableButton] = useState(false); + const [countdown, setCountdown] = useState(30); const logo = getLogo(); let affCode = new URLSearchParams(window.location.search).get('aff'); if (affCode) { @@ -46,6 +49,19 @@ const RegisterForm = () => { } }); + useEffect(() => { + let countdownInterval = null; + if (disableButton && countdown > 0) { + countdownInterval = setInterval(() => { + setCountdown(countdown - 1); + }, 1000); + } else if (countdown === 0) { + setDisableButton(false); + setCountdown(30); + } + return () => clearInterval(countdownInterval); + }, [disableButton, countdown]); + let navigate = useNavigate(); function handleChange(e) { @@ -56,16 +72,16 @@ const RegisterForm = () => { async function handleSubmit(e) { if (password.length < 8) { - showInfo('密码长度不得小于 8 位!'); + showInfo(t('messages.error.password_length')); return; } if (password !== password2) { - showInfo('两次输入的密码不一致'); + showInfo(t('messages.error.password_mismatch')); return; } if (username && password) { if (turnstileEnabled && turnstileToken === '') { - showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); + showInfo(t('messages.error.turnstile_wait')); return; } setLoading(true); @@ -80,7 +96,7 @@ const RegisterForm = () => { const { success, message } = res.data; if (success) { navigate('/login'); - showSuccess('注册成功!'); + showSuccess(t('messages.success.register')); } else { showError(message); } @@ -91,18 +107,21 @@ const RegisterForm = () => { const sendVerificationCode = async () => { if (inputs.email === '') return; if (turnstileEnabled && turnstileToken === '') { - showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); + showInfo(t('messages.error.turnstile_wait')); return; } + setDisableButton(true); setLoading(true); const res = await API.get( `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}` ); const { success, message } = res.data; if (success) { - showSuccess('验证码发送成功,请检查你的邮箱!'); + showSuccess(t('messages.success.verification_code')); } else { showError(message); + setDisableButton(false); + setCountdown(30); } setLoading(false); }; @@ -123,7 +142,7 @@ const RegisterForm = () => { style={{ marginBottom: '1.5em' }} > - 新用户注册 + {t('auth.register.title')}
@@ -131,7 +150,7 @@ const RegisterForm = () => { fluid icon='user' iconPosition='left' - placeholder='输入用户名,最长 12 位' + placeholder={t('auth.register.username')} onChange={handleChange} name='username' style={{ marginBottom: '1em' }} @@ -140,7 +159,7 @@ const RegisterForm = () => { fluid icon='lock' iconPosition='left' - placeholder='输入密码,最短 8 位,最长 20 位' + placeholder={t('auth.register.password')} onChange={handleChange} name='password' type='password' @@ -150,7 +169,7 @@ const RegisterForm = () => { fluid icon='lock' iconPosition='left' - placeholder='再次输入密码' + placeholder={t('auth.register.confirm_password')} onChange={handleChange} name='password2' type='password' @@ -163,7 +182,7 @@ const RegisterForm = () => { fluid icon='mail' iconPosition='left' - placeholder='输入邮箱地址' + placeholder={t('auth.register.email')} onChange={handleChange} name='email' type='email' @@ -171,9 +190,10 @@ const RegisterForm = () => { } style={{ marginBottom: '1em' }} @@ -182,7 +202,7 @@ const RegisterForm = () => { fluid icon='lock' iconPosition='left' - placeholder='输入验证码' + placeholder={t('auth.register.verification_code')} onChange={handleChange} name='verification_code' style={{ marginBottom: '1em' }} @@ -218,7 +238,7 @@ const RegisterForm = () => { }} loading={loading} > - 注册 + {t('auth.register.button')}
@@ -231,9 +251,9 @@ const RegisterForm = () => { color: '#666', }} > - 已有账户? + {t('auth.register.has_account')} - 点击登录 + {t('auth.register.login')}