feat: customize frontend with OasisRelay branding and UI enhancements

- Rebrand hero section with OasisRelay title, gradient text, and uptime counter
- Add transparent-to-frosted-glass nav bar effect on home page scroll
- Add grid square background pattern on home page
- Simplify nav items to: 首页, 控制台, 定价, 文档
- Change 分组监控 to Activity icon button opening external status page
- Switch font family to PingFang SC with cross-platform fallbacks
- Fix username visibility in light mode nav bar
- Restyle login/register forms and home page sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 23:40:56 +08:00
parent ae4cdb334d
commit e4a3b1bd29
10 changed files with 2151 additions and 1074 deletions

View File

@@ -35,21 +35,14 @@ import {
import Turnstile from 'react-turnstile';
import {
Button,
Card,
Checkbox,
Divider,
Form,
Icon,
Modal,
} from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import {
IconGithubLogo,
IconMail,
IconUser,
IconLock,
IconKey,
} from '@douyinfe/semi-icons';
import {
onGitHubOAuthClicked,
@@ -393,162 +386,118 @@ const RegisterForm = () => {
const renderOAuthOptions = () => {
return (
<div className='flex flex-col items-center'>
<div className='w-full max-w-md'>
<div className='flex items-center justify-center mb-6 gap-2'>
<img src={logo} alt='Logo' className='h-10 rounded-full' />
<Title heading={3} className='!text-gray-800'>
{systemName}
</Title>
</div>
<div className='lp-auth-card'>
<div className='lp-auth-logo-row'>
<img src={logo} alt='Logo' style={{width:'32px',height:'32px',borderRadius:'8px',objectFit:'cover'}} />
<span className='lp-auth-brand'>{systemName}</span>
</div>
<Card className='border-0 !rounded-2xl overflow-hidden'>
<div className='flex justify-center pt-6 pb-2'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'>
{t('注 册')}
</Title>
<h1 className='lp-auth-title'>{t('创建新账号')}</h1>
<p className='lp-auth-sub'> <Link to='/login' className='lp-auth-link'>{t('登录已有账号')}</Link></p>
<div className='lp-auth-oauth-list'>
{status.wechat_login && (
<Button
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={<Icon svg={<WeChatIcon />} style={{ color: '#07C160' }} />}
onClick={onWeChatLoginClicked}
loading={wechatLoading}
>
<span className='ml-3'>{t('使用 微信 继续')}</span>
</Button>
)}
{status.github_oauth && (
<Button
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={<IconGithubLogo size='large' />}
onClick={handleGitHubClick}
loading={githubLoading}
disabled={githubButtonDisabled}
>
<span className='ml-3'>{githubButtonText}</span>
</Button>
)}
{status.discord_oauth && (
<Button
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={<SiDiscord style={{ color: '#5865F2', width: '20px', height: '20px' }} />}
onClick={handleDiscordClick}
loading={discordLoading}
>
<span className='ml-3'>{t('使用 Discord 继续')}</span>
</Button>
)}
{status.oidc_enabled && (
<Button
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={<OIDCIcon style={{ color: '#1877F2' }} />}
onClick={handleOIDCClick}
loading={oidcLoading}
>
<span className='ml-3'>{t('使用 OIDC 继续')}</span>
</Button>
)}
{status.linuxdo_oauth && (
<Button
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={<LinuxDoIcon style={{ color: '#E95420', width: '20px', height: '20px' }} />}
onClick={handleLinuxDOClick}
loading={linuxdoLoading}
>
<span className='ml-3'>{t('使用 LinuxDO 继续')}</span>
</Button>
)}
{status.custom_oauth_providers &&
status.custom_oauth_providers.map((provider) => (
<Button
key={provider.slug}
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={getOAuthProviderIcon(provider.icon || '', 20)}
onClick={() => handleCustomOAuthClick(provider)}
loading={customOAuthLoading[provider.slug]}
>
<span className='ml-3'>{t('使用 {{name}} 继续', { name: provider.name })}</span>
</Button>
))}
{status.telegram_oauth && (
<div className='flex justify-center my-2'>
<TelegramLoginButton
dataOnauth={onTelegramLoginClicked}
botName={status.telegram_bot_name}
/>
</div>
<div className='px-2 py-8'>
<div className='space-y-3'>
{status.wechat_login && (
<Button
theme='outline'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type='tertiary'
icon={
<Icon svg={<WeChatIcon />} style={{ color: '#07C160' }} />
}
onClick={onWeChatLoginClicked}
loading={wechatLoading}
>
<span className='ml-3'>{t('使用 微信 继续')}</span>
</Button>
)}
)}
{status.github_oauth && (
<Button
theme='outline'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type='tertiary'
icon={<IconGithubLogo size='large' />}
onClick={handleGitHubClick}
loading={githubLoading}
disabled={githubButtonDisabled}
>
<span className='ml-3'>{githubButtonText}</span>
</Button>
)}
<div className='lp-auth-divider'><span>{t('或')}</span></div>
{status.discord_oauth && (
<Button
theme='outline'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type='tertiary'
icon={
<SiDiscord
style={{
color: '#5865F2',
width: '20px',
height: '20px',
}}
/>
}
onClick={handleDiscordClick}
loading={discordLoading}
>
<span className='ml-3'>{t('使用 Discord 继续')}</span>
</Button>
)}
{status.oidc_enabled && (
<Button
theme='outline'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type='tertiary'
icon={<OIDCIcon style={{ color: '#1877F2' }} />}
onClick={handleOIDCClick}
loading={oidcLoading}
>
<span className='ml-3'>{t('使用 OIDC 继续')}</span>
</Button>
)}
{status.linuxdo_oauth && (
<Button
theme='outline'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type='tertiary'
icon={
<LinuxDoIcon
style={{
color: '#E95420',
width: '20px',
height: '20px',
}}
/>
}
onClick={handleLinuxDOClick}
loading={linuxdoLoading}
>
<span className='ml-3'>{t('使用 LinuxDO 继续')}</span>
</Button>
)}
{status.custom_oauth_providers &&
status.custom_oauth_providers.map((provider) => (
<Button
key={provider.slug}
theme='outline'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors'
type='tertiary'
icon={getOAuthProviderIcon(provider.icon || '', 20)}
onClick={() => handleCustomOAuthClick(provider)}
loading={customOAuthLoading[provider.slug]}
>
<span className='ml-3'>
{t('使用 {{name}} 继续', { name: provider.name })}
</span>
</Button>
))}
{status.telegram_oauth && (
<div className='flex justify-center my-2'>
<TelegramLoginButton
dataOnauth={onTelegramLoginClicked}
botName={status.telegram_bot_name}
/>
</div>
)}
<Divider margin='12px' align='center'>
{t('或')}
</Divider>
<Button
theme='solid'
type='primary'
className='w-full h-12 flex items-center justify-center bg-black text-white !rounded-full hover:bg-gray-800 transition-colors'
icon={<IconMail size='large' />}
onClick={handleEmailRegisterClick}
loading={emailRegisterLoading}
>
<span className='ml-3'>{t('使用 用户名 注册')}</span>
</Button>
</div>
<div className='mt-6 text-center text-sm'>
<Text>
{t('已有账户?')}{' '}
<Link
to='/login'
className='text-blue-600 hover:text-blue-800 font-medium'
>
{t('登录')}
</Link>
</Text>
</div>
</div>
</Card>
<Button
theme='solid'
type='primary'
className='lp-auth-oauth-btn'
icon={<IconMail size='large' />}
onClick={handleEmailRegisterClick}
loading={emailRegisterLoading}
>
<span className='ml-3'>{t('使用 用户名 注册')}</span>
</Button>
</div>
</div>
);
@@ -556,175 +505,126 @@ const RegisterForm = () => {
const renderEmailRegisterForm = () => {
return (
<div className='flex flex-col items-center'>
<div className='w-full max-w-md'>
<div className='flex items-center justify-center mb-6 gap-2'>
<img src={logo} alt='Logo' className='h-10 rounded-full' />
<Title heading={3} className='!text-gray-800'>
{systemName}
</Title>
</div>
<div className='lp-auth-card'>
<div className='lp-auth-logo-row'>
<img src={logo} alt='Logo' style={{width:'32px',height:'32px',borderRadius:'8px',objectFit:'cover'}} />
<span className='lp-auth-brand'>{systemName}</span>
</div>
<Card className='border-0 !rounded-2xl overflow-hidden'>
<div className='flex justify-center pt-6 pb-2'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'>
{t('注 册')}
</Title>
<h1 className='lp-auth-title'>{t('创建新账号')}</h1>
<p className='lp-auth-sub'> <Link to='/login' className='lp-auth-link'>{t('登录已有账号')}</Link></p>
<div className='lp-form-group'>
<span className='lp-form-icon'>👤</span>
<input
type='text'
placeholder={t('请输入用户名')}
name='username'
autoComplete='nickname'
onChange={(e) => handleChange('username', e.target.value)}
className='lp-input'
/>
</div>
<div className='lp-form-group'>
<span className='lp-form-icon'>🔒</span>
<input
type='password'
placeholder={t('输入密码,最短 8 位,最长 20 位')}
name='password'
autoComplete='new-password'
onChange={(e) => handleChange('password', e.target.value)}
className='lp-input'
/>
</div>
<div className='lp-form-group'>
<span className='lp-form-icon'>🔒</span>
<input
type='password'
placeholder={t('确认密码')}
name='password2'
autoComplete='new-password'
onChange={(e) => handleChange('password2', e.target.value)}
className='lp-input'
/>
</div>
{showEmailVerification && (
<>
<div className='lp-form-group'>
<span className='lp-form-icon'></span>
<input
type='email'
placeholder={t('输入邮箱地址')}
name='email'
autoComplete='email'
onChange={(e) => handleChange('email', e.target.value)}
className='lp-input'
/>
</div>
<div className='px-2 py-8'>
<Form className='space-y-3'>
<Form.Input
field='username'
label={t('用户名')}
placeholder={t('输入用户名')}
name='username'
onChange={(value) => handleChange('username', value)}
prefix={<IconUser />}
<div className='lp-code-row'>
<div className='lp-form-group' style={{flex:1,margin:0}}>
<span className='lp-form-icon'>🔑</span>
<input
type='text'
placeholder={t('输入验证码')}
name='verification_code'
onChange={(e) => handleChange('verification_code', e.target.value)}
className='lp-input'
/>
<Form.Input
field='password'
label={t('密码')}
placeholder={t('输入密码,最短 8 位,最长 20 位')}
name='password'
mode='password'
onChange={(value) => handleChange('password', value)}
prefix={<IconLock />}
/>
<Form.Input
field='password2'
label={t('确认密码')}
placeholder={t('确认密码')}
name='password2'
mode='password'
onChange={(value) => handleChange('password2', value)}
prefix={<IconLock />}
/>
{showEmailVerification && (
<>
<Form.Input
field='email'
label={t('邮箱')}
placeholder={t('输入邮箱地址')}
name='email'
type='email'
onChange={(value) => handleChange('email', value)}
prefix={<IconMail />}
suffix={
<Button
onClick={sendVerificationCode}
loading={verificationCodeLoading}
disabled={disableButton || verificationCodeLoading}
>
{disableButton
? `${t('重新发送')} (${countdown})`
: t('获取验证码')}
</Button>
}
/>
<Form.Input
field='verification_code'
label={t('验证码')}
placeholder={t('输入验证码')}
name='verification_code'
onChange={(value) =>
handleChange('verification_code', value)
}
prefix={<IconKey />}
/>
</>
)}
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='pt-4'>
<Checkbox
checked={agreedToTerms}
onChange={(e) => setAgreedToTerms(e.target.checked)}
>
<Text size='small' className='text-gray-600'>
{t('我已阅读并同意')}
{hasUserAgreement && (
<>
<a
href='/user-agreement'
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1'
>
{t('用户协议')}
</a>
</>
)}
{hasUserAgreement && hasPrivacyPolicy && t('和')}
{hasPrivacyPolicy && (
<>
<a
href='/privacy-policy'
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1'
>
{t('隐私政策')}
</a>
</>
)}
</Text>
</Checkbox>
</div>
)}
<div className='space-y-2 pt-2'>
<Button
theme='solid'
className='w-full !rounded-full'
type='primary'
htmlType='submit'
onClick={handleSubmit}
loading={registerLoading}
disabled={
(hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms
}
>
{t('注册')}
</Button>
</div>
</Form>
{hasOAuthRegisterOptions && (
<>
<Divider margin='12px' align='center'>
{t('或')}
</Divider>
<div className='mt-4 text-center'>
<Button
theme='outline'
type='tertiary'
className='w-full !rounded-full'
onClick={handleOtherRegisterOptionsClick}
loading={otherRegisterOptionsLoading}
>
{t('其他注册选项')}
</Button>
</div>
</>
)}
<div className='mt-6 text-center text-sm'>
<Text>
{t('已有账户?')}{' '}
<Link
to='/login'
className='text-blue-600 hover:text-blue-800 font-medium'
>
{t('登录')}
</Link>
</Text>
</div>
<button
className='lp-btn-send-code'
onClick={sendVerificationCode}
disabled={disableButton || verificationCodeLoading}
>
{disableButton ? `${t('重新发送')} (${countdown})` : t('获取验证码')}
</button>
</div>
</Card>
</>
)}
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='lp-terms-row'>
<Checkbox checked={agreedToTerms} onChange={(e) => setAgreedToTerms(e.target.checked)}>
<span className='lp-auth-terms-text'>
{t('我已阅读并同意')}
{hasUserAgreement && (
<a href='/user-agreement' target='_blank' rel='noopener noreferrer' className='lp-auth-link'> {t('用户协议')}</a>
)}
{hasUserAgreement && hasPrivacyPolicy && t('和')}
{hasPrivacyPolicy && (
<a href='/privacy-policy' target='_blank' rel='noopener noreferrer' className='lp-auth-link'> {t('隐私政策')}</a>
)}
</span>
</Checkbox>
</div>
)}
<button
className='lp-btn-submit'
onClick={handleSubmit}
disabled={registerLoading || ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms)}
>
{registerLoading ? t('注册中...') : t('下一步')}
</button>
{hasOAuthRegisterOptions && (
<>
<div className='lp-auth-divider'><span>{t('或')}</span></div>
<button className='lp-btn-other-options' onClick={handleOtherRegisterOptionsClick}>
{t('其他注册选项')}
</button>
</>
)}
<div className='lp-benefits'>
<div className='lp-benefits-title'>注册后您将获得</div>
<div className='lp-benefit-item'>使用量统计面板</div>
<div className='lp-benefit-item'>灵活的订阅方案</div>
<div className='lp-benefit-item'>200+ 模型一键接入</div>
<div className='lp-benefit-item'>专属 API Token</div>
</div>
</div>
);
@@ -770,17 +670,8 @@ const RegisterForm = () => {
};
return (
<div className='relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
{/* 背景模糊晕染球 */}
<div
className='blur-ball blur-ball-indigo'
style={{ top: '-80px', right: '-80px', transform: 'none' }}
/>
<div
className='blur-ball blur-ball-teal'
style={{ top: '50%', left: '-120px' }}
/>
<div className='w-full max-w-sm mt-[60px]'>
<div className='lp-auth-page' style={{ minHeight: 'calc(100vh - 56px)' }}>
<div className='lp-auth-main'>
{showEmailRegister ||
!hasOAuthRegisterOptions
? renderEmailRegisterForm()