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

9
web/bun.lock vendored
View File

@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "react-template",
@@ -10,7 +11,7 @@
"@visactor/react-vchart": "~1.8.8",
"@visactor/vchart": "~1.8.8",
"@visactor/vchart-semi-theme": "~1.8.8",
"axios": "1.12.0",
"axios": "1.13.5",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"history": "^5.3.0",
@@ -776,7 +777,7 @@
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
"axios": ["axios@1.12.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg=="],
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
@@ -1104,13 +1105,13 @@
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"for-in": ["for-in@1.0.2", "", {}, "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],

View File

@@ -43,15 +43,11 @@ 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 TelegramLoginButton from 'react-telegram-login';
import {
@@ -502,369 +498,245 @@ const LoginForm = () => {
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>
<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>
</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>
)}
{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>
)}
{status.passkey_login && passkeySupported && (
<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={<IconKey size='large' />}
onClick={handlePasskeyLogin}
loading={passkeyLoading}
>
<span className='ml-3'>{t('使用 Passkey 登录')}</span>
</Button>
)}
<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={handleEmailLoginClick}
loading={emailLoginLoading}
>
<span className='ml-3'>{t('使用 邮箱或用户名 登录')}</span>
</Button>
</div>
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='mt-6'>
<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>
)}
{!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'>
<Text>
{t('没有账户?')}{' '}
<Link
to='/register'
className='text-blue-600 hover:text-blue-800 font-medium'
>
{t('注册')}
</Link>
</Text>
</div>
)}
</div>
</Card>
<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>
<h1 className='lp-auth-title'>{t('登 录')}</h1>
<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>
)}
{status.passkey_login && passkeySupported && (
<Button
theme='outline'
className='lp-auth-oauth-btn'
type='tertiary'
icon={<IconKey size='large' />}
onClick={handlePasskeyLogin}
loading={passkeyLoading}
>
<span className='ml-3'>{t('使用 Passkey 登录')}</span>
</Button>
)}
<div className='lp-auth-divider'><span>{t('或')}</span></div>
<Button
theme='solid'
type='primary'
className='lp-auth-oauth-btn'
icon={<IconMail size='large' />}
onClick={handleEmailLoginClick}
loading={emailLoginLoading}
>
<span className='ml-3'>{t('使用 邮箱或用户名 登录')}</span>
</Button>
</div>
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='lp-auth-terms'>
<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>
)}
{!status.self_use_mode_enabled && (
<p className='lp-auth-sub'> <Link to='/register' className='lp-auth-link'>{t('创建新账号')}</Link></p>
)}
</div>
);
};
const renderEmailLoginForm = () => {
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}>{systemName}</Title>
</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>
</div>
<div className='px-2 py-8'>
{status.passkey_login && passkeySupported && (
<Button
theme='outline'
type='tertiary'
className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors mb-4'
icon={<IconKey size='large' />}
onClick={handlePasskeyLogin}
loading={passkeyLoading}
>
<span className='ml-3'>{t('使用 Passkey 登录')}</span>
</Button>
)}
<Form className='space-y-3'>
<Form.Input
field='username'
label={t('用户名或邮箱')}
placeholder={t('请输入您的用户名或邮箱地址')}
name='username'
onChange={(value) => handleChange('username', value)}
prefix={<IconMail />}
/>
<Form.Input
field='password'
label={t('密码')}
placeholder={t('请输入您的密码')}
name='password'
mode='password'
onChange={(value) => handleChange('password', value)}
prefix={<IconLock />}
/>
{(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={loginLoading}
disabled={
(hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms
}
>
{t('继续')}
</Button>
<Button
theme='borderless'
type='tertiary'
className='w-full !rounded-full'
onClick={handleResetPasswordClick}
loading={resetPasswordLoading}
>
{t('忘记密码?')}
</Button>
</div>
</Form>
{hasOAuthLoginOptions && (
<>
<Divider margin='12px' align='center'>
{t('或')}
</Divider>
<div className='mt-4 text-center'>
<Button
theme='outline'
type='tertiary'
className='w-full !rounded-full'
onClick={handleOtherLoginOptionsClick}
loading={otherLoginOptionsLoading}
>
{t('其他登录选项')}
</Button>
</div>
</>
)}
{!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'>
<Text>
{t('没有账户?')}{' '}
<Link
to='/register'
className='text-blue-600 hover:text-blue-800 font-medium'
>
{t('注册')}
</Link>
</Text>
</div>
)}
</div>
</Card>
<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>
<h1 className='lp-auth-title'>{t('登录到您的账号')}</h1>
<p className='lp-auth-sub'> <Link to='/register' className='lp-auth-link'>{t('创建新账号')}</Link></p>
{status.passkey_login && passkeySupported && (
<Button
theme='outline'
type='tertiary'
className='lp-auth-oauth-btn'
icon={<IconKey size='large' />}
onClick={handlePasskeyLogin}
loading={passkeyLoading}
style={{marginBottom:'16px'}}
>
<span className='ml-3'>{t('使用 Passkey 登录')}</span>
</Button>
)}
<div className='lp-form-group'>
<span className='lp-form-icon'></span>
<input
type='text'
placeholder={t('请输入用户名或邮箱地址')}
name='username'
autoComplete='username'
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('请输入密码')}
name='password'
autoComplete='current-password'
onChange={(e) => handleChange('password', e.target.value)}
className='lp-input'
/>
</div>
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='lp-auth-terms'>
<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={loginLoading || ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms)}
>
{loginLoading ? t('登录中...') : t('登录')}
</button>
<button
className='lp-btn-forgot'
onClick={handleResetPasswordClick}
>
{t('忘记密码?')}
</button>
{hasOAuthLoginOptions && (
<>
<div className='lp-auth-divider'><span>{t('或')}</span></div>
<button className='lp-btn-other-options' onClick={handleOtherLoginOptionsClick}>
{t('其他登录选项')}
</button>
</>
)}
</div>
);
};
@@ -947,19 +819,9 @@ const LoginForm = () => {
};
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]'>
{showEmailLogin ||
!hasOAuthLoginOptions
<div className='lp-auth-page' style={{ minHeight: 'calc(100vh - 56px)' }}>
<div className='lp-auth-main'>
{showEmailLogin || !hasOAuthLoginOptions
? renderEmailLoginForm()
: renderOAuthOptions()}
{renderWeChatLoginModal()}

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()

View File

@@ -18,6 +18,8 @@ For commercial licensing, please contact support@quantumnous.com
*/
import React from 'react';
import { Button } from '@douyinfe/semi-ui';
import { Activity } from 'lucide-react';
import NewYearButton from './NewYearButton';
import NotificationButton from './NotificationButton';
import ThemeToggle from './ThemeToggle';
@@ -41,23 +43,37 @@ const ActionButtons = ({
t,
}) => {
return (
<div className='flex items-center gap-2 md:gap-3'>
<div className='flex items-center gap-2'>
<NewYearButton isNewYear={isNewYear} />
{/* 通知栏 */}
<NotificationButton
unreadCount={unreadCount}
onNoticeOpen={onNoticeOpen}
t={t}
/>
{/* 分组监控 */}
<Button
icon={<Activity size={18} />}
aria-label={t('分组监控')}
theme='borderless'
type='tertiary'
className='!p-1.5 !text-current focus:!bg-semi-color-fill-1 !rounded-full !bg-semi-color-fill-0 hover:!bg-semi-color-fill-1'
onClick={() => window.open('http://107.175.54.36:3099/status/claude', '_blank')}
/>
{/* 主题切换 */}
<ThemeToggle theme={theme} onThemeToggle={onThemeToggle} t={t} />
{/* 语言切换 */}
<LanguageSelector
currentLang={currentLang}
onLanguageChange={onLanguageChange}
t={t}
/>
{/* 登录/控制台 */}
<UserArea
userState={userState}
isLoading={isLoading}

View File

@@ -29,12 +29,7 @@ const Navigation = ({
pricingRequireAuth,
}) => {
const renderNavLinks = () => {
const baseClasses =
'flex-shrink-0 flex items-center gap-1 font-semibold rounded-md transition-all duration-200 ease-in-out';
const hoverClasses = 'hover:text-semi-color-primary';
const spacingClasses = isMobile ? 'p-1' : 'p-2';
const commonLinkClasses = `${baseClasses} ${spacingClasses} ${hoverClasses}`;
const commonLinkClasses = 'lp-nav-link';
return mainNavLinks.map((link) => {
const linkContent = <span>{link.text}</span>;
@@ -70,7 +65,7 @@ const Navigation = ({
};
return (
<nav className='flex flex-1 items-center gap-1 lg:gap-2 mx-2 md:mx-4 overflow-x-auto whitespace-nowrap scrollbar-hide'>
<nav className='flex flex-1 items-center justify-center gap-1 mx-2 md:mx-4 overflow-x-auto whitespace-nowrap scrollbar-hide'>
<SkeletonWrapper
loading={isLoading}
type='navigation'

View File

@@ -129,7 +129,7 @@ const UserArea = ({
{userState.user.username[0].toUpperCase()}
</Avatar>
<span className='hidden md:inline'>
<Typography.Text className='!text-xs !font-medium !text-semi-color-text-1 dark:!text-gray-300 mr-1'>
<Typography.Text className='!text-xs !font-medium mr-1' style={{ color: 'inherit' }}>
{userState.user.username}
</Typography.Text>
</span>
@@ -144,54 +144,18 @@ const UserArea = ({
} else {
const showRegisterButton = !isSelfUseMode;
const commonSizingAndLayoutClass =
'flex items-center justify-center !py-[10px] !px-1.5';
const loginButtonSpecificStyling =
'!bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 transition-colors';
let loginButtonClasses = `${commonSizingAndLayoutClass} ${loginButtonSpecificStyling}`;
let registerButtonClasses = `${commonSizingAndLayoutClass}`;
const loginButtonTextSpanClass =
'!text-xs !text-semi-color-text-1 dark:!text-gray-300 !p-1.5';
const registerButtonTextSpanClass = '!text-xs !text-white !p-1.5';
if (showRegisterButton) {
if (isMobile) {
loginButtonClasses += ' !rounded-full';
} else {
loginButtonClasses += ' !rounded-l-full !rounded-r-none';
}
registerButtonClasses += ' !rounded-r-full !rounded-l-none';
} else {
loginButtonClasses += ' !rounded-full';
}
return (
<div className='flex items-center'>
<Link to='/login' className='flex'>
<Button
theme='borderless'
type='tertiary'
className={loginButtonClasses}
>
<span className={loginButtonTextSpanClass}>{t('登录')}</span>
</Button>
</Link>
<div className='flex items-center gap-2'>
{showRegisterButton && (
<div className='hidden md:block'>
<Link to='/register' className='flex -ml-px'>
<Button
theme='solid'
type='primary'
className={registerButtonClasses}
>
<span className={registerButtonTextSpanClass}>{t('注册')}</span>
</Button>
<Link to='/register' className='lp-nav-orange-btn'>
{t('注册')}
</Link>
</div>
)}
<Link to='/login' className='lp-nav-login-btn'>
{t('登录')}
</Link>
</div>
);
}

View File

@@ -17,10 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React from 'react';
import React, { useState, useEffect } from 'react';
import { useHeaderBar } from '../../../hooks/common/useHeaderBar';
import { useNotifications } from '../../../hooks/common/useNotifications';
import { useNavigation } from '../../../hooks/common/useNavigation';
import { useActualTheme } from '../../../context/Theme';
import NoticeModal from '../NoticeModal';
import MobileMenuButton from './MobileMenuButton';
import HeaderLogo from './HeaderLogo';
@@ -51,9 +52,12 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
handleThemeToggle,
handleMobileMenuToggle,
navigate,
location,
t,
} = useHeaderBar({ onMobileMenuToggle, drawerOpen });
const actualTheme = useActualTheme();
const {
noticeVisible,
unreadCount,
@@ -64,8 +68,41 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
const { mainNavLinks } = useNavigation(t, docsLink, headerNavModules);
// 首页路由 = '/' 或 '/home'
const isHomePage = location.pathname === '/' || location.pathname === '/home';
// 滚动监听:仅首页启用透明导航
// 注意:页面滚动发生在 Semi UI 的内层 Layout 容器overflow:auto不是 window
// 须用 document capture 阶段才能拿到滚动事件
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
if (!isHomePage) return;
const onScroll = (e) => {
const top = e.target?.scrollTop ?? window.scrollY;
setScrolled(top > 10);
};
document.addEventListener('scroll', onScroll, { passive: true, capture: true });
// 初始化:检查当前滚动位置
const layouts = document.querySelectorAll('.semi-layout');
let initTop = window.scrollY;
layouts.forEach(el => { if (el.scrollTop > initTop) initTop = el.scrollTop; });
setScrolled(initTop > 10);
return () => document.removeEventListener('scroll', onScroll, { capture: true });
}, [isHomePage]);
// 深色导航栏条件:首页 或 actualTheme=dark
const useDarkNav = isHomePage || actualTheme === 'dark';
const headerClass = [
'lp-header-bar',
useDarkNav ? 'lp-header-dark' : 'lp-header-light',
// 非首页始终用 scrolled 样式(实色背景),首页根据滚动位置切换
(!isHomePage || scrolled) ? 'lp-header-scrolled' : 'lp-header-top',
'text-semi-color-text-0 sticky top-0 z-50 transition-all duration-300',
].join(' ');
return (
<header className='text-semi-color-text-0 sticky top-0 z-50 transition-colors duration-300 bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg'>
<header className={headerClass}>
<NoticeModal
visible={noticeVisible}
onClose={handleNoticeClose}
@@ -74,9 +111,9 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
unreadKeys={getUnreadKeys()}
/>
<div className='w-full px-2'>
<div className='flex items-center justify-between h-16'>
<div className='flex items-center'>
<div className='w-full px-[5%]'>
<div className='flex items-center justify-between h-[56px]'>
<div className='flex items-center flex-shrink-0'>
<MobileMenuButton
isConsoleRoute={isConsoleRoute}
isMobile={isMobile}
@@ -99,30 +136,34 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
/>
</div>
<Navigation
mainNavLinks={mainNavLinks}
isMobile={isMobile}
isLoading={isLoading}
userState={userState}
pricingRequireAuth={pricingRequireAuth}
/>
<div className='flex-1 flex justify-center'>
<Navigation
mainNavLinks={mainNavLinks}
isMobile={isMobile}
isLoading={isLoading}
userState={userState}
pricingRequireAuth={pricingRequireAuth}
/>
</div>
<ActionButtons
isNewYear={isNewYear}
unreadCount={unreadCount}
onNoticeOpen={handleNoticeOpen}
theme={theme}
onThemeToggle={handleThemeToggle}
currentLang={currentLang}
onLanguageChange={handleLanguageChange}
userState={userState}
isLoading={isLoading}
isMobile={isMobile}
isSelfUseMode={isSelfUseMode}
logout={logout}
navigate={navigate}
t={t}
/>
<div className='flex-shrink-0'>
<ActionButtons
isNewYear={isNewYear}
unreadCount={unreadCount}
onNoticeOpen={handleNoticeOpen}
theme={theme}
onThemeToggle={handleThemeToggle}
currentLang={currentLang}
onLanguageChange={handleLanguageChange}
userState={userState}
isLoading={isLoading}
isMobile={isMobile}
isSelfUseMode={isSelfUseMode}
logout={logout}
navigate={navigate}
t={t}
/>
</div>
</div>
</div>
</header>

View File

@@ -45,7 +45,7 @@ export const useNavigation = (t, docsLink, headerNavModules) => {
to: '/console',
},
{
text: t('模型广场'),
text: t('定价'),
itemKey: 'pricing',
to: '/pricing',
},
@@ -59,11 +59,6 @@ export const useNavigation = (t, docsLink, headerNavModules) => {
},
]
: []),
{
text: t('关于'),
itemKey: 'about',
to: '/about',
},
];
// 根据配置过滤导航链接
@@ -72,7 +67,6 @@ export const useNavigation = (t, docsLink, headerNavModules) => {
return docsLink && modules.docs;
}
if (link.itemKey === 'pricing') {
// 支持新的pricing配置格式
return typeof modules.pricing === 'object'
? modules.pricing.enabled
: modules.pricing;

1165
web/src/index.css vendored

File diff suppressed because it is too large Load Diff

View File

@@ -17,103 +17,111 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useContext, useEffect, useState } from 'react';
import {
Button,
Typography,
Input,
ScrollList,
ScrollItem,
} from '@douyinfe/semi-ui';
import { API, showError, copy, showSuccess } from '../../helpers';
import { useIsMobile } from '../../hooks/common/useIsMobile';
import { API_ENDPOINTS } from '../../constants/common.constant';
import React, { useContext, useEffect, useState, useRef } from 'react';
import { API } from '../../helpers';
import { StatusContext } from '../../context/Status';
import { useActualTheme } from '../../context/Theme';
import { marked } from 'marked';
import { useTranslation } from 'react-i18next';
import {
IconGithubLogo,
IconPlay,
IconFile,
IconCopy,
} from '@douyinfe/semi-icons';
import { Link } from 'react-router-dom';
import NoticeModal from '../../components/layout/NoticeModal';
import {
Moonshot,
OpenAI,
XAI,
Zhipu,
Volcengine,
Cohere,
Claude,
Gemini,
Suno,
Minimax,
Wenxin,
Spark,
Qingyan,
DeepSeek,
Qwen,
Midjourney,
Grok,
AzureAI,
Hunyuan,
Xinference,
} from '@lobehub/icons';
import { useIsMobile } from '../../hooks/common/useIsMobile';
const { Text } = Typography;
/* ──────────────────────────────────────────────
运行时间计时器起始时间2025-01-01 00:00:00
────────────────────────────────────────────── */
const START_TIME = new Date('2025-09-12T00:00:00+08:00').getTime();
const UptimeCounter = () => {
const calc = () => {
const diff = Date.now() - START_TIME;
const s = Math.floor(diff / 1000);
return {
days: Math.floor(s / 86400),
hours: Math.floor((s % 86400) / 3600),
minutes: Math.floor((s % 3600) / 60),
seconds: s % 60,
};
};
const [t, setT] = useState(calc);
useEffect(() => {
const id = setInterval(() => setT(calc()), 1000);
return () => clearInterval(id);
}, []);
const pad = n => String(n).padStart(2, '0');
return (
<div className='lp-uptime'>
<span className='lp-uptime-dot' />
<span className='lp-uptime-label'>本站已稳定运行</span>
<span className='lp-uptime-block'>{t.days}<em></em></span>
<span className='lp-uptime-block'>{pad(t.hours)}<em></em></span>
<span className='lp-uptime-block'>{pad(t.minutes)}<em></em></span>
<span className='lp-uptime-block'>{pad(t.seconds)}<em></em></span>
</div>
);
};
const FaqItem = ({ question, children, defaultOpen }) => {
const [open, setOpen] = useState(!!defaultOpen);
return (
<div className={`lp-faq-item${open ? ' lp-open' : ''}`}>
<button className='lp-faq-q' onClick={() => setOpen((v) => !v)}>
{question}
<span className='lp-faq-arrow'></span>
</button>
<div className='lp-faq-a'>
<p>{children}</p>
</div>
</div>
);
};
/* ──────────────────────────────────────────────
首页主组件
────────────────────────────────────────────── */
const Home = () => {
const { t, i18n } = useTranslation();
const { i18n } = useTranslation();
const [statusState] = useContext(StatusContext);
const actualTheme = useActualTheme();
const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
const [homePageContent, setHomePageContent] = useState('');
const [noticeVisible, setNoticeVisible] = useState(false);
const isMobile = useIsMobile();
const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
const docsLink = statusState?.status?.docs_link || '';
const serverAddress =
statusState?.status?.server_address || `${window.location.origin}`;
const endpointItems = API_ENDPOINTS.map((e) => ({ value: e }));
const [endpointIndex, setEndpointIndex] = useState(0);
const isChinese = i18n.language.startsWith('zh');
/* 服务端配置的自定义首页内容Markdown 或外链 URL */
const displayHomePageContent = async () => {
setHomePageContent(localStorage.getItem('home_page_content') || '');
const res = await API.get('/api/home_page_content');
const { success, message, data } = res.data;
if (success) {
let content = data;
if (!data.startsWith('https://')) {
content = marked.parse(data);
}
setHomePageContent(content);
localStorage.setItem('home_page_content', content);
// 如果内容是 URL则发送主题模式
if (data.startsWith('https://')) {
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.onload = () => {
iframe.contentWindow.postMessage({ themeMode: actualTheme }, '*');
iframe.contentWindow.postMessage({ lang: i18n.language }, '*');
};
const cached = localStorage.getItem('home_page_content') || '';
setHomePageContent(cached);
try {
const res = await API.get('/api/home_page_content');
const { success, data } = res.data;
if (success && data && data.trim() !== '') {
let content = data;
if (!data.startsWith('https://')) {
content = marked.parse(data);
}
}
} else {
showError(message);
setHomePageContent('加载首页内容失败...');
}
setHomePageContentLoaded(true);
};
setHomePageContent(content);
localStorage.setItem('home_page_content', content);
const handleCopyBaseURL = async () => {
const ok = await copy(serverAddress);
if (ok) {
showSuccess(t('已复制到剪切板'));
if (data.startsWith('https://')) {
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.onload = () => {
iframe.contentWindow.postMessage({ themeMode: actualTheme }, '*');
iframe.contentWindow.postMessage({ lang: i18n.language }, '*');
};
}
}
setHomePageContentLoaded(true);
} else {
// 没有自定义内容,显示默认首页
localStorage.removeItem('home_page_content');
setHomePageContent('');
setHomePageContentLoaded(true);
}
} catch (e) {
// 网络错误/无后端,直接显示默认首页,不报错
setHomePageContent('');
setHomePageContentLoaded(true);
}
};
@@ -133,7 +141,6 @@ const Home = () => {
}
}
};
checkNoticeAndShow();
}, []);
@@ -141,214 +148,361 @@ const Home = () => {
displayHomePageContent().then();
}, []);
useEffect(() => {
const timer = setInterval(() => {
setEndpointIndex((prev) => (prev + 1) % endpointItems.length);
}, 3000);
return () => clearInterval(timer);
}, [endpointItems.length]);
/* 如果管理员设置了自定义首页内容,则显示那个 */
if (homePageContentLoaded && homePageContent !== '') {
return (
<div className='overflow-x-hidden w-full'>
{homePageContent.startsWith('https://') ? (
<iframe src={homePageContent} className='w-full h-screen border-none' />
) : (
<div className='mt-[60px]' dangerouslySetInnerHTML={{ __html: homePageContent }} />
)}
</div>
);
}
/* ── 默认 Landing Page ── */
return (
<div className='w-full overflow-x-hidden'>
<div className='lp-root'>
<NoticeModal
visible={noticeVisible}
onClose={() => setNoticeVisible(false)}
isMobile={isMobile}
/>
{homePageContentLoaded && homePageContent === '' ? (
<div className='w-full overflow-x-hidden'>
{/* Banner 部分 */}
<div className='w-full border-b border-semi-color-border min-h-[500px] md:min-h-[600px] lg:min-h-[700px] relative overflow-x-hidden'>
{/* 背景模糊晕染球 */}
<div className='blur-ball blur-ball-indigo' />
<div className='blur-ball blur-ball-teal' />
<div className='flex items-center justify-center h-full px-4 py-20 md:py-24 lg:py-32 mt-10'>
{/* 居中内容区 */}
<div className='flex flex-col items-center justify-center text-center max-w-4xl mx-auto'>
<div className='flex flex-col items-center justify-center mb-6 md:mb-8'>
<h1
className={`text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-semi-color-text-0 leading-tight ${isChinese ? 'tracking-wide md:tracking-wider' : ''}`}
>
<>
{t('统一的')}
<br />
<span className='shine-text'>{t('大模型接口网关')}</span>
</>
</h1>
<p className='text-base md:text-lg lg:text-xl text-semi-color-text-1 mt-4 md:mt-6 max-w-xl'>
{t('更好的价格,更好的稳定性,只需要将模型基址替换为:')}
</p>
{/* BASE URL 与端点选择 */}
<div className='flex flex-col md:flex-row items-center justify-center gap-4 w-full mt-4 md:mt-6 max-w-md'>
<Input
readonly
value={serverAddress}
className='flex-1 !rounded-full'
size={isMobile ? 'default' : 'large'}
suffix={
<div className='flex items-center gap-2'>
<ScrollList
bodyHeight={32}
style={{ border: 'unset', boxShadow: 'unset' }}
>
<ScrollItem
mode='wheel'
cycled={true}
list={endpointItems}
selectedIndex={endpointIndex}
onSelect={({ index }) => setEndpointIndex(index)}
/>
</ScrollList>
<Button
type='primary'
onClick={handleCopyBaseURL}
icon={<IconCopy />}
className='!rounded-full'
/>
</div>
}
/>
</div>
</div>
{/* 操作按钮 */}
<div className='flex flex-row gap-4 justify-center items-center'>
<Link to='/console'>
<Button
theme='solid'
type='primary'
size={isMobile ? 'default' : 'large'}
className='!rounded-3xl px-8 py-2'
icon={<IconPlay />}
>
{t('获取密钥')}
</Button>
</Link>
{isDemoSiteMode && statusState?.status?.version ? (
<Button
size={isMobile ? 'default' : 'large'}
className='flex items-center !rounded-3xl px-6 py-2'
icon={<IconGithubLogo />}
onClick={() =>
window.open(
'https://github.com/QuantumNous/new-api',
'_blank',
)
}
>
{statusState.status.version}
</Button>
) : (
docsLink && (
<Button
size={isMobile ? 'default' : 'large'}
className='flex items-center !rounded-3xl px-6 py-2'
icon={<IconFile />}
onClick={() => window.open(docsLink, '_blank')}
>
{t('文档')}
</Button>
)
)}
</div>
{/* ═══════════════════ HERO ═══════════════════ */}
<section className='lp-hero' id='lp-hero'>
<div className='lp-tyndall'>
<div className='lp-ty lp-ty1' />
<div className='lp-ty lp-ty2' />
<div className='lp-ty lp-ty3' />
<div className='lp-ty lp-ty4' />
<div className='lp-ty lp-ty5' />
</div>
{/* 框架兼容性图标 */}
<div className='mt-12 md:mt-16 lg:mt-20 w-full'>
<div className='flex items-center mb-6 md:mb-8 justify-center'>
<Text
type='tertiary'
className='text-lg md:text-xl lg:text-2xl font-light'
>
{t('支持众多的大模型供应商')}
</Text>
</div>
<div className='flex flex-wrap items-center justify-center gap-3 sm:gap-4 md:gap-6 lg:gap-8 max-w-5xl mx-auto px-4'>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Moonshot size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<OpenAI size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<XAI size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Zhipu.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Volcengine.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Cohere.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Claude.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Gemini.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Suno size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Minimax.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Wenxin.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Spark.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Qingyan.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<DeepSeek.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Qwen.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Midjourney size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Grok size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<AzureAI.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Hunyuan.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Xinference.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Typography.Text className='!text-lg sm:!text-xl md:!text-2xl lg:!text-3xl font-bold'>
30+
</Typography.Text>
</div>
</div>
</div>
</div>
</div>
<div className='lp-hero-top'>
<UptimeCounter />
<h1 className='lp-hero-title'>
OasisRelay<br />
<span className='lp-hero-title-sub'>全球加速 · 无需代理 · 物优价宜</span>
</h1>
<p className='lp-hero-sub'>
释放开发潜能提升集成效率为开发者打造的下一代 AI API 接入体验让模型调用从未如此简单
</p>
</div>
<div className='lp-fluid-wrap'>
<div className='lp-fluid-card' />
</div>
<div className='lp-hero-pills'>
<span className='lp-hero-pill'> Claude Code 支持</span>
<span className='lp-hero-pill'>🧠 Codex / o1 支持</span>
<span className='lp-hero-pill'>💫 Gemini CLI 支持</span>
</div>
<div
className='lp-hero-scroll'
onClick={() => document.getElementById('lp-integrations')?.scrollIntoView({ behavior: 'smooth' })}
>
<span /><span /><span />
</div>
</section>
<div className='lp-divider' />
{/* ═══════════════════ INTEGRATIONS ═══════════════════ */}
<section className='lp-integrations' id='lp-integrations'>
<div className='lp-int-header'>
<span className='lp-label' style={{ textAlign: 'left' }}>INTEGRATIONS</span>
<div className='lp-int-note'>
<span style={{ color: 'var(--lp-dim2)', fontSize: '.84rem' }}>查看官方 CLI每一行工具入口</span>
<a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer'>📖 查看教程 </a>
</div>
</div>
) : (
<div className='overflow-x-hidden w-full'>
{homePageContent.startsWith('https://') ? (
<iframe
src={homePageContent}
className='w-full h-screen border-none'
/>
) : (
<div
className='mt-[60px]'
dangerouslySetInnerHTML={{ __html: homePageContent }}
/>
)}
<div className='lp-int-cards'>
<a className='lp-int-card lp-ic-claude' href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh?from=from_copylink' target='_blank' rel='noopener noreferrer'>
<div className='lp-int-avatar'>🤖</div>
<div className='lp-int-info'>
<div className='lp-int-name'>Claude Code</div>
<div className='lp-int-org'>Anthropic</div>
</div>
<div className='lp-int-arrow'></div>
</a>
<a className='lp-int-card lp-ic-codex' href='https://my.feishu.cn/wiki/Z2ZgwZPihi9bBikBA6dcUB5onkb?from=from_copylink' target='_blank' rel='noopener noreferrer'>
<div className='lp-int-avatar'>💻</div>
<div className='lp-int-info'>
<div className='lp-int-name'>CodeX</div>
<div className='lp-int-org'>OpenAI</div>
</div>
<div className='lp-int-arrow'></div>
</a>
<a className='lp-int-card lp-ic-gemini' href='https://my.feishu.cn/wiki/NNSQwbe14iQkGqkldhucmEBynsh?from=from_copylink' target='_blank' rel='noopener noreferrer'>
<div className='lp-int-avatar'>💫</div>
<div className='lp-int-info'>
<div className='lp-int-name'>Gemini CLI</div>
<div className='lp-int-org'>Google</div>
</div>
<div className='lp-int-arrow'></div>
</a>
</div>
)}
</section>
<div className='lp-divider' />
{/* ═══════════════════ FEATURES ═══════════════════ */}
<section className='lp-features lp-section' id='lp-features'>
<span className='lp-label'>FEATURES</span>
<h2 className='lp-s-title'>开发者工具专为国内链路优化</h2>
<p className='lp-s-sub'>享受无限制更好的链路代码调试计算设计一次解决</p>
<div className='lp-feat-grid'>
<div className='lp-feat-card'>
<div className='lp-feat-icon lp-fi-teal'>🌐</div>
<h3>专用转发网络</h3>
<p>企业专级转发架构国内直连延迟极低Token 每次请求均走最优节点稳定无抖动</p>
</div>
<div className='lp-feat-card'>
<div className='lp-feat-icon lp-fi-green'></div>
<h3>一键切换</h3>
<p>单一端点支持 200+ 模型更换模型只需改 model 字段无需重配环境一行 Token 走天下</p>
</div>
<div className='lp-feat-card'>
<div className='lp-feat-icon lp-fi-blue'>💰</div>
<h3>超低使用成本</h3>
<p>价格比官方低 30%~60%无月费无订阅按量付费随充随用适合个人与企业所有规模</p>
</div>
<div className='lp-feat-card'>
<div className='lp-feat-icon lp-fi-purple'>🛡</div>
<h3>省心合规运营</h3>
<p>无需境外信用卡支持支付宝/微信支付合规采购 API 资源安心用于商业项目</p>
</div>
<div className='lp-feat-card'>
<div className='lp-feat-icon lp-fi-orange'>🔌</div>
<h3>针对性能优化</h3>
<p>支持 Claude In Chrome 插件提供内置 CLI 工具接入优化针对 Claude Code 专项加速</p>
</div>
<div className='lp-feat-card'>
<div className='lp-feat-icon lp-fi-pink'>📊</div>
<h3>使用统计</h3>
<p>精细化使用记录与用量看板计算账单明细实时查看余额轻松做预算规划</p>
</div>
</div>
</section>
<div className='lp-divider' />
{/* ═══════════════════ COMPARISON ═══════════════════ */}
<section className='lp-comparison lp-section' id='lp-comparison'>
<span className='lp-label'>COMPARISON</span>
<h2 className='lp-s-title'>Claude Code · CodeX · Gemini CLI</h2>
<p className='lp-s-sub'>快速选择参考更强调的工作流与集成推荐</p>
<div className='lp-cmp-wrap'>
<table className='lp-cmp-table'>
<thead>
<tr>
<th>对比项</th>
<th className='lp-th-claude'>Claude Code</th>
<th className='lp-th-codex'>CodeX</th>
<th className='lp-th-gemini'>Gemini CLI</th>
</tr>
</thead>
<tbody>
<tr>
<td>定位</td>
<td>代码理解 / 重构 / 终端工作流</td>
<td>代码生成 / 代理式终端工作流</td>
<td>检索 · 辅助 · Google 生态工具</td>
</tr>
<tr>
<td>使用命令</td>
<td><code className='lp-code-inline' style={{ background: 'rgba(205,133,63,.1)' }}>claude</code></td>
<td><code className='lp-code-inline' style={{ background: 'rgba(52,211,153,.08)' }}>codex</code></td>
<td><code className='lp-code-inline' style={{ background: 'rgba(96,165,250,.08)' }}>gemini</code></td>
</tr>
<tr>
<td>适合场景</td>
<td>大型代码库代码理解项目级</td>
<td>任务执行代码生成与执行</td>
<td>搜索增强统筹型图文理解</td>
</tr>
<tr>
<td>本平台能力</td>
<td>国内直连 · 代码理解 · 终端调试</td>
<td>国内直连 · 统一入口 · 所有能力</td>
<td>国内直连 · 统一入口 · 所有能力</td>
</tr>
<tr>
<td>支持情况</td>
<td><span className='lp-badge lp-cb-claude'> 完整支持</span></td>
<td><span className='lp-badge lp-cb-codex'> 完整支持</span></td>
<td><span className='lp-badge lp-cb-gemini'> 完整支持</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<div className='lp-divider' />
{/* ═══════════════════ STEPS ═══════════════════ */}
<section className='lp-steps lp-section' id='lp-steps'>
<span className='lp-label'>QUICKSTART</span>
<h2 className='lp-s-title'>三步开始使用</h2>
<p className='lp-s-sub'>注册账号 创建 Token 配置客户端所有步骤不超过 5 分钟</p>
<div className='lp-steps-grid'>
<div className='lp-step-card'>
<div className='lp-step-num'>E1</div>
<div className='lp-step-icon'>🛒</div>
<h3>购买充值</h3>
<p>前往淘宝店铺选购适合的套餐支持支付宝/微信支付充值秒到账即刻获得 API Token</p>
<a href='https://detail.tmall.com/item.htm?id=901313339780' target='_blank' rel='noopener noreferrer' className='lp-step-cta'>前往淘宝购买 </a>
</div>
<div className='lp-step-card'>
<div className='lp-step-num'>E2</div>
<div className='lp-step-icon'></div>
<h3>安装客户端</h3>
<p>根据你的客户端Claude Code / CodeX / Gemini CLI按照文档配置 API 端点与密钥</p>
<a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer' className='lp-step-cta'>查看安装文档 </a>
</div>
<div className='lp-step-card'>
<div className='lp-step-num'>E3</div>
<div className='lp-step-icon'>🚀</div>
<h3>开始使用</h3>
<p>设定 CLI base_url token运行测试命令即可调用全球顶级 AI 模型开始工作</p>
<Link to='/console' className='lp-step-cta'>进入控制台 </Link>
</div>
</div>
<div className='lp-steps-cta'>
<h3>立即注册并进入一键安装页面</h3>
<p>注册账号后在控制台可找到对应客户端一键配置脚本和详细安装教程链接</p>
<a href='https://detail.tmall.com/item.htm?id=901313339780' target='_blank' rel='noopener noreferrer' className='lp-btn-teal'>🛒 前往淘宝购买</a>
<a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer' className='lp-btn-outline' style={{ marginLeft: '10px' }}>查看文档</a>
</div>
<div className='lp-code-block'>
<div className='lp-code-dots'>
<div className='lp-dot-r' />
<div className='lp-dot-y' />
<div className='lp-dot-g' />
<span style={{ marginLeft: '8px', fontSize: '.78rem', color: 'rgba(255,255,255,.28)' }}>
Python · 快速接入示例
</span>
</div>
<code>
<span className='lp-ck'>from</span>{' openai '}
<span className='lp-ck'>import</span>{' OpenAI\n\n'}
{'client = OpenAI(\n'}
{' api_key='}
<span className='lp-cs'>"your-api-key"</span>
{',\n'}
{' base_url='}
<span className='lp-cs'>"{window.location.origin}/v1"</span>
{'\n)\n\nresponse = client.chat.completions.create(\n'}
{' model='}
<span className='lp-cv'>"claude-sonnet-4-5"</span>
{',\n'}
{' messages=[{'}
<span className='lp-cs'>"role"</span>
{': '}
<span className='lp-cs'>"user"</span>
{', '}
<span className='lp-cs'>"content"</span>
{': '}
<span className='lp-cs'>"Hello!"</span>
{'}]\n)\n'}
<span className='lp-ck'>print</span>
{'(response.choices['}
<span className='lp-cv'>0</span>
{'].message.content)'}
</code>
</div>
</section>
<div className='lp-divider' />
{/* ═══════════════════ BIG CTA ═══════════════════ */}
<section className='lp-big-cta' id='lp-bigcta'>
<span className='lp-label'>GET STARTED</span>
<h2 className='lp-s-title'>立即开始使用 Claude CodeCodeXGemini CLI</h2>
<p>注册账号 · 创建 Token · 配置客户端一个平台一键搞定</p>
<div className='lp-cta-btns'>
<a href='https://detail.tmall.com/item.htm?id=901313339780' target='_blank' rel='noopener noreferrer' className='lp-btn-teal'>购买套餐</a>
<a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer' className='lp-btn-outline'>查看文档</a>
</div>
</section>
<div className='lp-divider' />
{/* ═══════════════════ FAQ ═══════════════════ */}
<section className='lp-faq' id='lp-faq'>
<span className='lp-label'>FAQ</span>
<h2 className='lp-s-title'>常见问题</h2>
<div className='lp-faq-list'>
<FaqItem question='是否支持月包付费呢?' defaultOpen>
支持我们提供按量充值和包月套餐两种方式包月套餐性价比更高适合高频使用的开发者购买后额度即时到账无需等待审核
</FaqItem>
<FaqItem question='是否兼容 OpenAI SDK'>
完全兼容本平台 100% 实现 OpenAI REST 接口规范只需将 base_url 替换为本站地址现有代码无需任何改动
</FaqItem>
<FaqItem question='如何在 Claude Code 中配置?'>
请参考文档页面文档中包含 Claude CodeCodeXGemini CLI 的详细接入步骤和配置示例大多数客户端只需设置环境变量 ANTHROPIC_BASE_URL API_KEY 即可
</FaqItem>
<FaqItem question='充值后何时到账?支持退款吗?'>
充值后秒级到账无需等待购买后的虚拟商品原则上不支持退款如遇服务故障导致的损失请联系客服处理
</FaqItem>
<FaqItem question='支持哪些 AI 模型?'>
支持 Claude 全系列SonnetOpusHaikuGPT-4o / o1 / CodexGemini 1.5 Pro / Ultra / FlashDeepSeek V3 / R1Llama 3.xMistral 200+ 模型并持续更新
</FaqItem>
</div>
</section>
<div className='lp-divider' />
{/* ═══════════════════ FOOTER ═══════════════════ */}
<footer className='lp-footer'>
<div className='lp-footer-grid'>
<div className='lp-footer-brand'>
<Link to='/' className='lp-footer-logo'>IOasis API</Link>
<p>Claude CodeCodeXGemini CLI 国内专用 AI API 中转稳定低价开发者首选</p>
<div className='lp-footer-chips'>
<span className='lp-footer-chip'>Claude Code</span>
<span className='lp-footer-chip'>CodeX</span>
<span className='lp-footer-chip'>Gemini CLI</span>
<span className='lp-footer-chip'>低价</span>
</div>
</div>
<div className='lp-footer-col'>
<h4>产品</h4>
<ul>
<li><Link to='/'>首页</Link></li>
<li><Link to='/pricing'>定价</Link></li>
<li><Link to='/console'>控制台</Link></li>
<li><a href='https://detail.tmall.com/item.htm?id=901313339780' target='_blank' rel='noopener noreferrer'>淘宝购买</a></li>
</ul>
</div>
<div className='lp-footer-col'>
<h4>支持</h4>
<ul>
<li><a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer'>使用文档</a></li>
<li><a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer'>安装教程</a></li>
<li><a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer'>API 参考</a></li>
<li><a href='https://my.feishu.cn/wiki/TVOSwdJ2HiTJTQkOLLQc2p9SnBh' target='_blank' rel='noopener noreferrer'>联系客服</a></li>
</ul>
</div>
<div className='lp-footer-col'>
<h4>关于</h4>
<ul>
<li><Link to='/about'>关于我们</Link></li>
<li><Link to='/pricing'>套餐价格</Link></li>
</ul>
</div>
</div>
<div className='lp-footer-bottom'>
<span>© 2026 Oasis API · All rights reserved</span>
<a href='https://oarel.com/' target='_blank' rel='noopener noreferrer'>oarel.com</a>
</div>
</footer>
</div>
);
};