首页改版:紫色主题 + 登录页重构

This commit is contained in:
wangxiaoji 2026-07-03 17:51:11 +08:00
parent 6836012ab4
commit f55309e953
13 changed files with 1919 additions and 1069 deletions

2
web/deploy.py vendored
View File

@ -5,7 +5,7 @@ import sys
host = "26.0.12.13" host = "26.0.12.13"
user = "root" user = "root"
password = "root@1234" password = "root@1234"
local_dist = r"C:\Users\wangxj\tokenFactory\web\dist" local_dist = r"D:\tuling_new\web\dist"
remote_dist = "/root/tokenFactory/web/dist" remote_dist = "/root/tokenFactory/web/dist"
print("Connecting...") print("Connecting...")

17
web/src/App.jsx vendored
View File

@ -95,6 +95,11 @@ import LuckyBag from './pages/LuckyBag';
import Drawing from './pages/Drawing'; import Drawing from './pages/Drawing';
import ProfitDashboard from './pages/ProfitDashboard'; import ProfitDashboard from './pages/ProfitDashboard';
import SettingsAntiAbuse from './pages/Setting/Operation/SettingsAntiAbuse'; import SettingsAntiAbuse from './pages/Setting/Operation/SettingsAntiAbuse';
import Contact from './pages/Contact';
import About from './pages/About';
import UserAgreement from './pages/UserAgreement';
import PrivacyPolicy from './pages/PrivacyPolicy';
import Docs from './pages/Docs';
const Home = lazy(() => import('./pages/Home')); const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard')); const Dashboard = lazy(() => import('./pages/Dashboard'));
@ -102,10 +107,6 @@ const DistributorApply = lazy(() => import('./pages/DistributorApply'));
const DistributorCenter = lazy(() => import('./pages/DistributorCenter')); const DistributorCenter = lazy(() => import('./pages/DistributorCenter'));
const DistributorAdmin = lazy(() => import('./pages/DistributorAdmin')); const DistributorAdmin = lazy(() => import('./pages/DistributorAdmin'));
const InviteRedirect = lazy(() => import('./pages/InviteRedirect')); const InviteRedirect = lazy(() => import('./pages/InviteRedirect'));
const About = lazy(() => import('./pages/About'));
const UserAgreement = lazy(() => import('./pages/UserAgreement'));
const PrivacyPolicy = lazy(() => import('./pages/PrivacyPolicy'));
const Docs = lazy(() => import('./pages/Docs'));
function DynamicOAuth2Callback() { function DynamicOAuth2Callback() {
const { provider } = useParams(); const { provider } = useParams();
@ -545,6 +546,14 @@ function App() {
</Suspense> </Suspense>
} }
/> />
<Route
path='/contact'
element={
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
<Contact />
</Suspense>
}
/>
<Route <Route
path='/docs' path='/docs'
element={ element={

View File

@ -15,16 +15,16 @@ import React, {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { Modal, Form, Typography, Space, Button } from '@douyinfe/semi-ui';
import { IconLock } from '@douyinfe/semi-icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { UserContext } from '../../context/User'; import { Lock as IconLock } from 'lucide-react';
import { Modal, Form, Typography, Space, Button } from '@douyinfe/semi-ui';
import { import {
API, API,
showError, showError,
showSuccess, showSuccess,
mergeSelfResponseIntoLocalUser, mergeSelfResponseIntoLocalUser,
} from '../../helpers'; } from '../../helpers';
import { UserContext } from '../../context/User';
import { buildAdminUserPhoneFieldRules } from '../table/users/modals/userPhoneFormRules'; import { buildAdminUserPhoneFieldRules } from '../table/users/modals/userPhoneFormRules';
const { Text, Title } = Typography; const { Text, Title } = Typography;

View File

@ -44,18 +44,7 @@ import {
redirectApplyIntentToAdminIfNeeded, redirectApplyIntentToAdminIfNeeded,
} from '../../helpers'; } from '../../helpers';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
import { import { Modal } from '@douyinfe/semi-ui';
Button,
Card,
Checkbox,
Divider,
Form,
Icon,
Input,
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 TelegramLoginButton from 'react-telegram-login';
import { import {
@ -71,6 +60,17 @@ import TwoFAVerification from './TwoFAVerification';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SiDiscord } from 'react-icons/si'; import { SiDiscord } from 'react-icons/si';
/* 与首页紫色版统一的色板 */
const ACC = '#635BFF';
const ACC_SOFT = '#EEEBFF';
const TINT = '#F6F4FF';
const INK = '#0A2540';
const SUB = '#425466';
const DIM = '#697386';
const LINE = '#E3E8EE';
const SOFT_BG = '#F6F9FC';
const BG_GRADIENT = `linear-gradient(180deg, ${TINT} 0%, #FFFFFF 70%, #FFFFFF 100%)`;
const LoginForm = () => { const LoginForm = () => {
let navigate = useNavigate(); let navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
@ -576,123 +576,112 @@ const LoginForm = () => {
}; };
const renderOAuthOptions = () => { const renderOAuthOptions = () => {
return ( const OauthButton = ({ icon, children, onClick, loading, disabled, brandColor }) => (
<div className='flex flex-col items-center'> <button
<div className='w-full max-w-md'> type='button'
<div className='flex items-center justify-center mb-6 gap-2'> onClick={onClick}
<img src={logo} alt='Logo' className='h-10 rounded-full' /> disabled={disabled || loading}
<Title heading={3} className='!text-gray-800'> className='w-full h-11 inline-flex items-center justify-center gap-2 rounded-full border bg-white text-[13.5px] font-medium transition-colors hover:bg-[#0A254006] disabled:opacity-50'
{systemName} style={{ borderColor: LINE, color: INK }}
</Title> >
</div> <span
className='inline-flex items-center justify-center w-5 h-5 shrink-0'
style={{ color: brandColor || INK }}
>
{loading ? (
<span className='block w-4 h-4 rounded-full border-2 border-t-transparent animate-spin'
style={{ borderColor: `${ACC} transparent transparent transparent` }}
/>
) : (
icon
)}
</span>
<span>{children}</span>
</button>
);
<Card className='border-0 !rounded-2xl overflow-hidden'> return (
<div className='flex justify-center pt-6 pb-2'> <div className='w-full'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'> <div
{t('登 录')} className='rounded-2xl border bg-white p-6 sm:p-7'
</Title> style={{ borderColor: LINE, boxShadow: '0 1px 2px rgba(15,23,42,0.04)' }}
</div> >
<div className='px-2 py-8'> <h2
<div className='space-y-3'> className='text-center text-[18px] font-semibold mb-1'
style={{ color: INK }}
>
{t('欢迎回来')}
</h2>
<p className='text-center text-[12.5px] mb-6' style={{ color: DIM }}>
{t('选择登录方式继续')}
</p>
<div className='space-y-2.5'>
{status.wechat_login && ( {status.wechat_login && (
<Button <OauthButton
theme='outline' icon={<WeChatIcon style={{ color: '#07C160' }} />}
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} onClick={onWeChatLoginClicked}
loading={wechatLoading} loading={wechatLoading}
> >
<span className='ml-3'>{t('使用 微信 继续')}</span> {t('使用 微信 继续')}
</Button> </OauthButton>
)} )}
{status.github_oauth && ( {status.github_oauth && (
<Button <OauthButton
theme='outline' icon={<IconGithubLogo size='large' style={{ color: '#181717' }} />}
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} onClick={handleGitHubClick}
loading={githubLoading} loading={githubLoading}
disabled={githubButtonDisabled} disabled={githubButtonDisabled}
> >
<span className='ml-3'>{githubButtonText}</span> {githubButtonText}
</Button> </OauthButton>
)} )}
{status.discord_oauth && ( {status.discord_oauth && (
<Button <OauthButton
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={
<SiDiscord <SiDiscord style={{ color: '#5865F2', width: 18, height: 18 }} />
style={{
color: '#5865F2',
width: '20px',
height: '20px',
}}
/>
} }
onClick={handleDiscordClick} onClick={handleDiscordClick}
loading={discordLoading} loading={discordLoading}
> >
<span className='ml-3'>{t('使用 Discord 继续')}</span> {t('使用 Discord 继续')}
</Button> </OauthButton>
)} )}
{status.oidc_enabled && ( {status.oidc_enabled && (
<Button <OauthButton
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' }} />} icon={<OIDCIcon style={{ color: '#1877F2' }} />}
onClick={handleOIDCClick} onClick={handleOIDCClick}
loading={oidcLoading} loading={oidcLoading}
> >
<span className='ml-3'>{t('使用 OIDC 继续')}</span> {t('使用 OIDC 继续')}
</Button> </OauthButton>
)} )}
{status.linuxdo_oauth && ( {status.linuxdo_oauth && (
<Button <OauthButton
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={
<LinuxDoIcon <LinuxDoIcon style={{ color: '#E95420', width: 18, height: 18 }} />
style={{
color: '#E95420',
width: '20px',
height: '20px',
}}
/>
} }
onClick={handleLinuxDOClick} onClick={handleLinuxDOClick}
loading={linuxdoLoading} loading={linuxdoLoading}
> >
<span className='ml-3'>{t('使用 LinuxDO 继续')}</span> {t('使用 LinuxDO 继续')}
</Button> </OauthButton>
)} )}
{status.custom_oauth_providers && {status.custom_oauth_providers &&
status.custom_oauth_providers.map((provider) => ( status.custom_oauth_providers.map((provider) => (
<Button <OauthButton
key={provider.slug} key={provider.slug}
theme='outline' icon={getOAuthProviderIcon(provider.icon || '', 18)}
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)} onClick={() => handleCustomOAuthClick(provider)}
loading={customOAuthLoading[provider.slug]} loading={customOAuthLoading[provider.slug]}
> >
<span className='ml-3'>
{t('使用 {{name}} 继续', { name: provider.name })} {t('使用 {{name}} 继续', { name: provider.name })}
</span> </OauthButton>
</Button>
))} ))}
{status.telegram_oauth && ( {status.telegram_oauth && (
@ -705,188 +694,233 @@ const LoginForm = () => {
)} )}
{status.passkey_login && passkeySupported && ( {status.passkey_login && passkeySupported && (
<Button <OauthButton
theme='outline' icon={<IconKey size='large' style={{ color: INK }} />}
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} onClick={handlePasskeyLogin}
loading={passkeyLoading} loading={passkeyLoading}
> >
<span className='ml-3'>{t('使用 Passkey 登录')}</span> {t('使用 Passkey 登录')}
</Button> </OauthButton>
)} )}
<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> </div>
<div className='flex items-center gap-3 my-5'>
<div className='flex-1 h-px' style={{ background: LINE }} />
<span
className='text-[11px] tracking-[0.2em] font-mono'
style={{ color: DIM }}
>
{t('或')}
</span>
<div className='flex-1 h-px' style={{ background: LINE }} />
</div>
<button
type='button'
onClick={handleEmailLoginClick}
disabled={emailLoginLoading}
className='w-full h-11 inline-flex items-center justify-center gap-2 rounded-full text-[13.5px] font-semibold text-white transition-opacity hover:opacity-90 disabled:opacity-50'
style={{ background: INK }}
>
<IconMail size='large' style={{ color: '#FFFFFF' }} />
<span>{t('使用 邮箱或用户名 登录')}</span>
</button>
{(hasUserAgreement || hasPrivacyPolicy) && ( {(hasUserAgreement || hasPrivacyPolicy) && (
<div className='mt-6'> <label className='mt-5 flex items-start gap-2 cursor-pointer select-none'>
<Checkbox <span className='relative inline-flex items-center mt-0.5'>
<input
type='checkbox'
className='peer sr-only'
checked={agreedToTerms} checked={agreedToTerms}
onChange={(e) => setAgreedToTerms(e.target.checked)} onChange={(e) => setAgreedToTerms(e.target.checked)}
/>
<span
className='w-4 h-4 rounded border flex items-center justify-center transition-colors peer-checked:border-transparent'
style={{
borderColor: LINE,
background: agreedToTerms ? ACC : 'transparent',
}}
> >
<Text size='small' className='text-gray-600'> {agreedToTerms && (
<svg width='10' height='10' viewBox='0 0 10 10' fill='none'>
<path
d='M1.5 5.5L4 8L8.5 2.5'
stroke='#FFFFFF'
strokeWidth='1.6'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
)}
</span>
</span>
<span className='text-[12px] leading-[1.5]' style={{ color: SUB }}>
{t('我已阅读并同意')} {t('我已阅读并同意')}
{hasUserAgreement && ( {hasUserAgreement && (
<>
<a <a
href='/user-agreement' href='/user-agreement'
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1' className='mx-1 font-medium'
style={{ color: ACC }}
> >
{t('用户协议')} {t('用户协议')}
</a> </a>
</>
)} )}
{hasUserAgreement && hasPrivacyPolicy && t('和')} {hasUserAgreement && hasPrivacyPolicy && t('和')}
{hasPrivacyPolicy && ( {hasPrivacyPolicy && (
<>
<a <a
href='/privacy-policy' href='/privacy-policy'
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1' className='mx-1 font-medium'
style={{ color: ACC }}
> >
{t('隐私政策')} {t('隐私政策')}
</a> </a>
</>
)} )}
</Text> </span>
</Checkbox> </label>
)}
</div> </div>
)}
{!status.self_use_mode_enabled && ( {!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'> <div className='mt-5 text-center text-[13px]' style={{ color: SUB }}>
<Text>
{t('没有账户?')}{' '} {t('没有账户?')}{' '}
<Link <Link
to='/register' to='/register'
className='text-blue-600 hover:text-blue-800 font-medium' className='font-medium'
style={{ color: ACC }}
> >
{t('注册')} {t('注册')}
</Link> </Link>
</Text>
</div> </div>
)} )}
</div> </div>
</Card>
</div>
</div>
); );
}; };
const renderEmailLoginForm = () => { const renderEmailLoginForm = () => {
const inputBaseClass =
'w-full h-11 px-3 rounded-lg border bg-white text-[13.5px] outline-none transition-colors focus:border-[#635BFF] focus:ring-2 focus:ring-[#635BFF]/15';
const inputStyle = {
borderColor: LINE,
color: INK,
};
const labelClass = 'text-[12.5px] font-medium mb-1.5 block';
return ( return (
<div className='flex flex-col items-center'> <div className='w-full'>
<div className='w-full max-w-md'> <div
<div className='flex items-center justify-center mb-6 gap-2'> className='rounded-2xl border bg-white p-6 sm:p-7'
<img src={logo} alt='Logo' className='h-10 rounded-full' /> style={{ borderColor: LINE, boxShadow: '0 1px 2px rgba(15,23,42,0.04)' }}
<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> <h2
</Button> className='text-center text-[18px] font-semibold mb-1'
)} style={{ color: INK }}
<Form className='space-y-3'> >
<Form.Input {t('登 录')}
field='username' </h2>
label={t('用户名或邮箱')} <p className='text-center text-[12.5px] mb-6' style={{ color: DIM }}>
placeholder={t('请输入您的用户名或邮箱地址')} {t('使用 邮箱或用户名 继续')}
name='username' </p>
onChange={(value) => handleChange('username', value)}
prefix={<IconMail />}
/>
<Form.Input {status.passkey_login && passkeySupported && (
field='password' <button
label={t('密码')} type='button'
placeholder={t('请输入您的密码')} onClick={handlePasskeyLogin}
name='password' disabled={passkeyLoading}
mode='password' className='w-full h-11 inline-flex items-center justify-center gap-2 rounded-full border bg-white text-[13.5px] font-medium transition-colors hover:bg-[#0A254006] disabled:opacity-50 mb-4'
onChange={(value) => handleChange('password', value)} style={{ borderColor: LINE, color: INK }}
prefix={<IconLock />} >
<span className='inline-flex items-center justify-center w-5 h-5 shrink-0'>
{passkeyLoading ? (
<span
className='block w-4 h-4 rounded-full border-2 border-t-transparent animate-spin'
style={{ borderColor: `${ACC} transparent transparent transparent` }}
/> />
) : (
<IconKey size='large' style={{ color: INK }} />
)}
</span>
<span>{t('使用 Passkey 登录')}</span>
</button>
)}
<div className='space-y-3'>
<div>
<label className={labelClass} style={{ color: INK }}>
{t('用户名或邮箱')}
</label>
<div className='relative'>
<span className='absolute left-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center pointer-events-none'
style={{ color: DIM }}>
<IconMail />
</span>
<input
type='text'
name='username'
placeholder={t('请输入您的用户名或邮箱地址')}
value={username}
onChange={(e) => handleChange('username', e.target.value)}
className={inputBaseClass + ' pl-10'}
style={inputStyle}
autoComplete='username'
/>
</div>
</div>
<div>
<label className={labelClass} style={{ color: INK }}>
{t('密码')}
</label>
<div className='relative'>
<span className='absolute left-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center pointer-events-none'
style={{ color: DIM }}>
<IconLock />
</span>
<input
type='password'
name='password'
placeholder={t('请输入您的密码')}
value={password}
onChange={(e) => handleChange('password', e.target.value)}
className={inputBaseClass + ' pl-10'}
style={inputStyle}
autoComplete='current-password'
/>
</div>
</div>
{captchaEnabled && ( {captchaEnabled && (
<div style={{ marginBottom: '8px' }}> <div>
<div <label className={labelClass} style={{ color: INK }}>
style={{
fontSize: '14px',
fontWeight: 500,
color: 'var(--semi-color-text-0, #1c1f23)',
marginBottom: '8px',
}}
>
{t('验证码')} {t('验证码')}
</div> </label>
<div style={{ display: 'flex', gap: '8px', alignItems: 'stretch' }}> <div className='flex gap-2'>
<div style={{ flex: 1 }}> <input
<Input type='text'
placeholder={t('请输入验证码')} placeholder={t('请输入验证码')}
onChange={(value) => setCaptchaCode(value)} value={captchaCode}
onChange={(e) => setCaptchaCode(e.target.value)}
className={inputBaseClass + ' flex-1'}
style={inputStyle}
/> />
</div>
<div <div
style={{
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
height: '32px',
borderRadius: '4px',
overflow: 'hidden',
border: '1px solid var(--semi-color-border, #d9d9d9)',
flexShrink: 0,
}}
onClick={fetchCaptcha} onClick={fetchCaptcha}
title={t('点击刷新验证码')} title={t('点击刷新验证码')}
className='h-11 inline-flex items-center justify-center px-3 rounded-lg border cursor-pointer overflow-hidden shrink-0'
style={{ borderColor: LINE, background: '#FFFFFF' }}
> >
{captchaImage ? ( {captchaImage ? (
<img <img
src={captchaImage} src={captchaImage}
alt='验证码' alt='验证码'
style={{ height: '32px', width: 'auto', display: 'block' }} className='h-8 w-auto block'
/> />
) : ( ) : (
<span <span className='text-[12px]' style={{ color: DIM }}>
style={{
padding: '0 12px',
fontSize: '12px',
color: '#999',
whiteSpace: 'nowrap',
}}
>
{captchaLoading ? '...' : t('点击获取')} {captchaLoading ? '...' : t('点击获取')}
</span> </span>
)} )}
@ -896,107 +930,140 @@ const LoginForm = () => {
)} )}
{(hasUserAgreement || hasPrivacyPolicy) && ( {(hasUserAgreement || hasPrivacyPolicy) && (
<div className='pt-4'> <label className='pt-1 flex items-start gap-2 cursor-pointer select-none'>
<Checkbox <span className='relative inline-flex items-center mt-0.5'>
<input
type='checkbox'
className='peer sr-only'
checked={agreedToTerms} checked={agreedToTerms}
onChange={(e) => setAgreedToTerms(e.target.checked)} onChange={(e) => setAgreedToTerms(e.target.checked)}
/>
<span
className='w-4 h-4 rounded border flex items-center justify-center transition-colors peer-checked:border-transparent'
style={{
borderColor: LINE,
background: agreedToTerms ? ACC : 'transparent',
}}
> >
<Text size='small' className='text-gray-600'> {agreedToTerms && (
<svg width='10' height='10' viewBox='0 0 10 10' fill='none'>
<path
d='M1.5 5.5L4 8L8.5 2.5'
stroke='#FFFFFF'
strokeWidth='1.6'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
)}
</span>
</span>
<span className='text-[12px] leading-[1.5]' style={{ color: SUB }}>
{t('我已阅读并同意')} {t('我已阅读并同意')}
{hasUserAgreement && ( {hasUserAgreement && (
<>
<a <a
href='/user-agreement' href='/user-agreement'
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1' className='mx-1 font-medium'
style={{ color: ACC }}
> >
{t('用户协议')} {t('用户协议')}
</a> </a>
</>
)} )}
{hasUserAgreement && hasPrivacyPolicy && t('和')} {hasUserAgreement && hasPrivacyPolicy && t('和')}
{hasPrivacyPolicy && ( {hasPrivacyPolicy && (
<>
<a <a
href='/privacy-policy' href='/privacy-policy'
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1' className='mx-1 font-medium'
style={{ color: ACC }}
> >
{t('隐私政策')} {t('隐私政策')}
</a> </a>
</>
)} )}
</Text> </span>
</Checkbox> </label>
</div>
)} )}
<div className='space-y-2 pt-2'> <div className='space-y-2 pt-2'>
<Button <button
theme='solid' type='submit'
className='w-full !rounded-full'
type='primary'
htmlType='submit'
onClick={handleSubmit} onClick={handleSubmit}
loading={loginLoading}
disabled={ disabled={
(hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms loginLoading ||
((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms)
} }
className='w-full h-11 inline-flex items-center justify-center gap-2 rounded-full text-[13.5px] font-semibold text-white transition-opacity hover:opacity-90 disabled:opacity-50'
style={{ background: INK }}
> >
{t('继续')} {loginLoading ? (
</Button> <span
className='block w-4 h-4 rounded-full border-2 border-t-transparent animate-spin'
style={{ borderColor: '#FFFFFF transparent transparent transparent' }}
/>
) : null}
<span>{t('继续')}</span>
</button>
<Button <button
theme='borderless' type='button'
type='tertiary'
className='w-full !rounded-full'
onClick={handleResetPasswordClick} onClick={handleResetPasswordClick}
loading={resetPasswordLoading} disabled={resetPasswordLoading}
className='w-full h-11 inline-flex items-center justify-center rounded-full text-[13.5px] font-medium transition-colors hover:bg-[#0A254006] disabled:opacity-50'
style={{ color: SUB }}
> >
{t('忘记密码?')} {t('忘记密码?')}
</Button> </button>
</div>
</div> </div>
</Form>
{hasOAuthLoginOptions && ( {hasOAuthLoginOptions && (
<> <>
<Divider margin='12px' align='center'> <div className='flex items-center gap-3 my-5'>
{t('或')} <div className='flex-1 h-px' style={{ background: LINE }} />
</Divider> <span
className='text-[11px] tracking-[0.2em] font-mono'
<div className='mt-4 text-center'> style={{ color: DIM }}
<Button
theme='outline'
type='tertiary'
className='w-full !rounded-full'
onClick={handleOtherLoginOptionsClick}
loading={otherLoginOptionsLoading}
> >
{t('其他登录选项')} {t('或')}
</Button> </span>
<div className='flex-1 h-px' style={{ background: LINE }} />
</div> </div>
<button
type='button'
onClick={handleOtherLoginOptionsClick}
disabled={otherLoginOptionsLoading}
className='w-full h-11 inline-flex items-center justify-center gap-2 rounded-full border bg-white text-[13.5px] font-medium transition-colors hover:bg-[#0A254006] disabled:opacity-50'
style={{ borderColor: LINE, color: INK }}
>
{otherLoginOptionsLoading ? (
<span
className='block w-4 h-4 rounded-full border-2 border-t-transparent animate-spin'
style={{ borderColor: `${ACC} transparent transparent transparent` }}
/>
) : null}
<span>{t('其他登录选项')}</span>
</button>
</> </>
)} )}
</div>
{!status.self_use_mode_enabled && ( {!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'> <div className='mt-5 text-center text-[13px]' style={{ color: SUB }}>
<Text>
{t('没有账户?')}{' '} {t('没有账户?')}{' '}
<Link <Link
to='/register' to='/register'
className='text-blue-600 hover:text-blue-800 font-medium' className='font-medium'
style={{ color: ACC }}
> >
{t('注册')} {t('注册')}
</Link> </Link>
</Text>
</div> </div>
)} )}
</div> </div>
</Card>
</div>
</div>
); );
}; };
@ -1025,17 +1092,24 @@ const LoginForm = () => {
</p> </p>
</div> </div>
<Form> <div>
<Form.Input <label
field='wechat_verification_code' className='text-[12.5px] font-medium mb-1.5 block'
style={{ color: INK }}
>
{t('验证码')}
</label>
<input
type='text'
placeholder={t('验证码')} placeholder={t('验证码')}
label={t('验证码')}
value={inputs.wechat_verification_code} value={inputs.wechat_verification_code}
onChange={(value) => onChange={(e) =>
handleChange('wechat_verification_code', value) handleChange('wechat_verification_code', e.target.value)
} }
className='w-full h-11 px-3 rounded-lg border bg-white text-[13.5px] outline-none transition-colors focus:border-[#635BFF] focus:ring-2 focus:ring-[#635BFF]/15'
style={{ borderColor: LINE, color: INK }}
/> />
</Form> </div>
</Modal> </Modal>
); );
}; };
@ -1077,40 +1151,67 @@ const LoginForm = () => {
); );
}; };
const brandTagline = t('统一 AI 模型网关,聚合全球顶尖大模型,一站接入即刻使用'); const brandTagline = t('统一 AI 模型接口,几十个模型,一个 base_url。');
return ( return (
<div className='flex min-h-screen'> <div className='flex min-h-screen bg-white'>
{/* 左侧品牌展示区 */} {/* 左侧品牌展示区 — 浅紫渐变,呼应首页 hero */}
<div className='hidden lg:flex lg:w-1/2 relative overflow-hidden bg-zinc-900 flex-col items-center justify-center px-12'>
<div <div
className='blur-ball blur-ball-indigo' className='hidden lg:flex lg:w-1/2 relative overflow-hidden flex-col items-center justify-center px-12'
style={{ top: '-120px', right: '-120px', width: '400px', height: '400px' }} style={{ background: BG_GRADIENT }}
/> >
<div <div
className='blur-ball blur-ball-teal' className='pointer-events-none absolute inset-0'
style={{ bottom: '-80px', left: '-80px', width: '350px', height: '350px' }} style={{
background: `radial-gradient(60% 60% at 50% 30%, ${ACC_SOFT} 0%, transparent 70%)`,
}}
/> />
<div className='relative z-10 text-center max-w-md'> <div className='relative z-10 text-center max-w-md'>
<img src={logo} alt='Logo' className='w-20 h-20 rounded-2xl mx-auto mb-8 shadow-2xl' /> <img
<h1 className='text-4xl font-bold text-white mb-4 tracking-tight'>{systemName}</h1> src={logo}
<p className='text-lg text-zinc-400 leading-relaxed'>{brandTagline}</p> alt='Logo'
<div className='mt-12 flex gap-3 justify-center'> className='w-16 h-16 rounded-2xl mx-auto mb-8 object-cover'
<div className='w-12 h-1 rounded-full bg-emerald-400' /> style={{ boxShadow: '0 18px 40px -12px rgba(99,91,255,0.35)' }}
<div className='w-8 h-1 rounded-full bg-blue-400' /> />
<div className='w-8 h-1 rounded-full bg-violet-400' /> <div className='inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs mb-6'
<div className='w-6 h-1 rounded-full bg-amber-400' /> style={{ borderColor: LINE, color: DIM, background: 'rgba(255,255,255,0.7)' }}>
<span className='h-1.5 w-1.5 rounded-full' style={{ background: ACC }} />
<span>{t('All systems normal')}</span>
</div>
<h1
className='font-heading text-[40px] font-semibold leading-[1.1] tracking-[-0.02em] mb-5'
style={{ color: INK }}
>
{systemName}
</h1>
<p
className='text-[15px] leading-relaxed max-w-[360px] mx-auto'
style={{ color: SUB }}
>
{brandTagline}
</p>
<div
className='mt-10 mx-auto inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-[11px] font-mono'
style={{ background: 'rgba(255,255,255,0.7)', color: DIM, border: `1px solid ${LINE}` }}
>
<span className='h-1.5 w-1.5 rounded-full bg-emerald-400' />
POST /v1/chat/completions · 312ms
</div> </div>
</div> </div>
</div> </div>
{/* 右侧登录表单区 */} {/* 右侧登录表单区 */}
<div className='flex-1 flex items-center justify-center bg-white dark:bg-zinc-950 px-4 sm:px-6 lg:px-8 py-12'> <div className='flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 py-12 bg-white'>
<div className='w-full max-w-sm'> <div className='w-full max-w-sm'>
{/* 移动端显示 logo */} {/* 移动端显示 logo */}
<div className='lg:hidden flex items-center justify-center gap-2 mb-8'> <div className='lg:hidden flex items-center justify-center gap-2 mb-8'>
<img src={logo} alt='Logo' className='w-8 h-8 rounded-full' /> <img src={logo} alt='Logo' className='w-8 h-8 rounded-full' />
<span className='text-lg font-semibold'>{systemName}</span> <span
className='text-lg font-semibold'
style={{ color: INK }}
>
{systemName}
</span>
</div> </div>
{showEmailLogin || !hasOAuthLoginOptions {showEmailLogin || !hasOAuthLoginOptions
? renderEmailLoginForm() ? renderEmailLoginForm()

View File

@ -18,8 +18,8 @@ For commercial licensing, please contact support@quantumnous.com
*/ */
import React from 'react'; import React from 'react';
import { Button } from '@douyinfe/semi-ui';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button } from '@douyinfe/semi-ui';
import { useIsMobile } from '../../../hooks/common/useIsMobile'; import { useIsMobile } from '../../../hooks/common/useIsMobile';
/** /**

View File

@ -0,0 +1,69 @@
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
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 { Inbox } from 'lucide-react';
const ACC = '#635BFF';
const ACC_SOFT = '#EEEBFF';
const INK = '#0A2540';
const SUB = '#425466';
const DIM = '#697386';
const toText = (v) =>
typeof v === 'string'
? v
: v?.props?.children || '';
const EmptyState = ({ icon, title, description, children, style }) => {
const Icon = icon || Inbox;
const titleText = toText(title);
const descText = toText(description);
return (
<div
className='flex flex-col items-center justify-center py-12 text-center'
style={style}
>
<div
className='inline-flex h-12 w-12 items-center justify-center rounded-2xl'
style={{ background: ACC_SOFT, color: ACC }}
>
<Icon size={22} />
</div>
{titleText && (
<p className='mt-4 text-sm font-semibold' style={{ color: INK }}>
{titleText}
</p>
)}
{descText && (
<p className='mt-1 text-sm' style={{ color: DIM }}>
{descText}
</p>
)}
{!titleText && !descText && (
<p className='mt-4 text-sm' style={{ color: DIM }}>
暂无数据
</p>
)}
{children && <div className='mt-4'>{children}</div>}
</div>
);
};
export default EmptyState;

View File

@ -19,9 +19,10 @@ For commercial licensing, please contact support@quantumnous.com
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { reducer, initialState } from './reducer'; import { API } from '../../helpers/api';
import { initialState, reducer } from './reducer';
import { mergeSelfResponseIntoLocalUser } from '../../helpers/data';
import { normalizeLanguage } from '../../i18n/language'; import { normalizeLanguage } from '../../i18n/language';
import { API, mergeSelfResponseIntoLocalUser } from '../../helpers';
import AdminInitialSetupModal from '../../components/auth/AdminInitialSetupModal'; import AdminInitialSetupModal from '../../components/auth/AdminInitialSetupModal';
export const UserContext = React.createContext({ export const UserContext = React.createContext({

View File

@ -18,15 +18,12 @@ For commercial licensing, please contact support@quantumnous.com
*/ */
import { Toast, Pagination } from '@douyinfe/semi-ui'; import { Toast, Pagination } from '@douyinfe/semi-ui';
import { toastConstants } from '../constants';
import React from 'react';
import { toast } from 'react-toastify';
import { import {
THINK_TAG_REGEX, THINK_TAG_REGEX,
MESSAGE_ROLES, MESSAGE_ROLES,
} from '../constants/playground.constants'; } from '../constants/playground.constants';
import { TABLE_COMPACT_MODES_KEY } from '../constants'; import { TABLE_COMPACT_MODES_KEY } from '../constants';
import { USER_ROLES } from '../constants/user.constants'; import { toastConstants } from '../constants/toast.constants';
import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile'; import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile';
const HTMLToastContent = ({ htmlContent }) => { const HTMLToastContent = ({ htmlContent }) => {
@ -144,6 +141,15 @@ export function parseCommissionPercentStringToBps(s) {
export function getSystemName() { export function getSystemName() {
let system_name = localStorage.getItem('system_name'); let system_name = localStorage.getItem('system_name');
if (!system_name) return 'TuringToken'; if (!system_name) return 'TuringToken';
// TokenFactory / TokenFactoryOpen / /
if (
system_name === 'TokenFactory' ||
system_name === 'TokenFactoryOpen' ||
system_name === '开放词元工厂'
) {
localStorage.setItem('system_name', 'TuringToken');
return 'TuringToken';
}
return system_name; return system_name;
} }

22
web/src/i18n/i18n.js vendored
View File

@ -39,17 +39,17 @@ i18n
load: 'currentOnly', load: 'currentOnly',
supportedLngs: supportedLanguages, supportedLngs: supportedLanguages,
resources: { resources: {
en: enTranslation, en: { translation: enTranslation },
'zh-CN': zhCNTranslation, 'zh-CN': { translation: zhCNTranslation },
'zh-TW': zhTWTranslation, 'zh-TW': { translation: zhTWTranslation },
fr: frTranslation, fr: { translation: frTranslation },
ru: ruTranslation, ru: { translation: ruTranslation },
ja: jaTranslation, ja: { translation: jaTranslation },
vi: viTranslation, vi: { translation: viTranslation },
id: idTranslation, id: { translation: idTranslation },
ms: msTranslation, ms: { translation: msTranslation },
th: thTranslation, th: { translation: thTranslation },
sw: swTranslation, sw: { translation: swTranslation },
}, },
fallbackLng: 'zh-CN', fallbackLng: 'zh-CN',
lng: 'zh-CN', lng: 'zh-CN',

View File

@ -5695,6 +5695,21 @@
"主题切换": "Theme Toggle", "主题切换": "Theme Toggle",
"顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "Show theme toggle in header bar, allowing users to switch between light/dark mode", "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "Show theme toggle in header bar, allowing users to switch between light/dark mode",
"语言选择器": "Language Selector", "语言选择器": "Language Selector",
"顶栏显示语言选择器,允许用户切换界面语言": "Show language selector in header bar, allowing users to switch interface language" "顶栏显示语言选择器,允许用户切换界面语言": "Show language selector in header bar, allowing users to switch interface language",
"All systems normal": "All systems normal",
"函数": "function",
"一样轻。": "— that light.",
"把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url切换模型、改价格、改延迟不必动客户端。": "Bring OpenAI, Claude, Gemini, DeepSeek and dozens of other models behind a single base_url. Switch models, pricing, latency — no client changes.",
"免费注册": "Sign up free",
"联系销售": "Contact sales",
"注册即送 ¥10": "¥10 free credits on signup",
"无月费": "No monthly fee",
"比开放透明": "Transparent pricing",
"你的客户端只需一句": "Your client only needs one",
",其余的事——路由、价格、容灾——都替你做了。": ", and we handle routing, pricing, and failover for you.",
"立即接入。": "Integrate now.",
"注册送 ¥10 试用金,分键跑完一组 benchmark一拍压测。": "Get ¥10 trial credit on signup. Run a benchmark, then a load test.",
"定价": "Pricing",
"调用 AI 像调用": "Calling AI is as light as calling"
} }
} }

View File

@ -5554,6 +5554,21 @@
"主题切换": "主题切换", "主题切换": "主题切换",
"顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式", "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式": "顶栏显示主题切换按钮,允许用户切换亮色/暗色模式",
"语言选择器": "语言选择器", "语言选择器": "语言选择器",
"顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言" "顶栏显示语言选择器,允许用户切换界面语言": "顶栏显示语言选择器,允许用户切换界面语言",
"All systems normal": "All systems normal",
"函数": "函数",
"一样轻。": "一样轻。",
"把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url切换模型、改价格、改延迟不必动客户端。": "把 OpenAI、Claude、Gemini、DeepSeek 等几十个模型收进同一个 base_url切换模型、改价格、改延迟不必动客户端。",
"免费注册": "免费注册",
"联系销售": "联系销售",
"注册即送 ¥10": "注册即送 ¥10",
"无月费": "无月费",
"比开放透明": "比开放透明",
"你的客户端只需一句": "你的客户端只需一句",
",其余的事——路由、价格、容灾——都替你做了。": ",其余的事——路由、价格、容灾——都替你做了。",
"立即接入。": "立即接入。",
"注册送 ¥10 试用金,分键跑完一组 benchmark一拍压测。": "注册送 ¥10 试用金,分键跑完一组 benchmark一拍压测。",
"定价": "定价",
"调用 AI 像调用": "调用 AI 像调用"
} }
} }

View File

@ -0,0 +1,364 @@
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useState } from 'react';
import { ArrowRight, Mail, MessageSquare, Building2, ShieldCheck, Check } from 'lucide-react';
import { Link } from 'react-router-dom';
const ACC = '#635BFF';
const ACC_SOFT = '#EEEBFF';
const BG = '#FFFFFF';
const SOFT = '#FBFAF7';
const TINT = '#F6F4FF';
const INK = '#0A2540';
const SUB = '#425466';
const DIM = '#697386';
const LINE = '#E3E8EE';
function Field({ label, value, onChange, type = 'text', required = false, placeholder = '' }) {
return (
<div>
<div className="mb-2 text-xs" style={{ color: DIM }}>
{label}{required && <span style={{ color: ACC }}> *</span>}
</div>
<input
type={type}
value={value}
onChange={onChange}
required={required}
placeholder={placeholder}
className="w-full rounded-lg border bg-white px-3.5 py-2.5 text-sm outline-none transition-colors focus:border-[#635BFF]"
style={{ borderColor: LINE, color: INK }}
/>
</div>
);
}
function InfoCard({ icon, title, primary, sub }) {
return (
<div
className="rounded-xl border bg-white p-5 transition-all hover:-translate-y-0.5 hover:shadow-sm"
style={{ borderColor: LINE }}
>
<div className="flex items-center gap-2.5">
<div
className="inline-flex h-8 w-8 items-center justify-center rounded-lg shrink-0"
style={{ background: ACC_SOFT, color: ACC }}
>
{icon}
</div>
<div className="text-sm font-semibold tracking-tight" style={{ color: INK }}>{title}</div>
</div>
<div className="mt-3.5 pl-[42px]">
<div className="text-[15px] font-medium" style={{ color: ACC }}>{primary}</div>
<div className="mt-1 text-xs leading-[1.6]" style={{ color: DIM }}>{sub}</div>
</div>
</div>
);
}
function MiniTopBar() {
return (
<header className="sticky top-0 z-50 border-b border-[#E3E8EE] bg-white/85 backdrop-blur-md">
<div className="flex h-16 items-center px-6 lg:px-10 max-w-[1200px] mx-auto">
<Link to="/" className="flex items-center gap-2.5">
<img src="/logo.jpg" alt="logo" className="h-7 w-7 rounded-full" />
<span className="font-heading text-[15px] font-semibold tracking-tight" style={{ color: INK }}>TuringToken</span>
</Link>
<div className="hidden md:flex items-center gap-2 ml-6 rounded-full px-2.5 py-1 text-[11px]" style={{ background: '#F6F9FC', color: DIM }}>
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
<span>服务正常 · 99.95%</span>
</div>
<div className="flex-1" />
<Link
to="/console"
className="inline-flex h-9 items-center rounded-full px-3.5 text-sm hover:bg-[#0A254006]"
style={{ color: SUB }}
>
控制台
</Link>
<Link
to="/register"
className="ml-2 inline-flex h-9 items-center gap-1.5 rounded-full px-4 text-sm font-medium text-white hover:opacity-90"
style={{ background: INK }}
>
免费注册 <ArrowRight size={12} />
</Link>
</div>
</header>
);
}
export default function Contact() {
const [form, setForm] = useState({
company: '',
name: '',
email: '',
volume: '',
message: '',
});
const update = (k) => (e) => setForm({ ...form, [k]: e.target.value });
const submit = (e) => {
e.preventDefault();
alert('已收到,我们会在 24 小时内联系你。');
};
return (
<div className="min-h-screen" style={{ background: BG, color: INK }}>
<MiniTopBar />
{/* Hero */}
<section className="relative overflow-hidden" style={{ background: BG }}>
<div
className="pointer-events-none absolute inset-x-0 top-0 h-[600px]"
style={{ background: 'radial-gradient(80% 60% at 50% 0%, #EEEBFF 0%, transparent 70%)' }}
/>
<div className="relative max-w-[1200px] mx-auto px-6 lg:px-10 pt-20 pb-14 lg:pt-28 lg:pb-20 text-center">
<div className="inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs" style={{ borderColor: LINE, color: DIM }}>
<span className="h-1.5 w-1.5 rounded-full" style={{ background: ACC }} />
<span>contact sales</span>
</div>
<h1
className="mt-6 font-heading font-semibold leading-[1.04] tracking-[-0.025em]"
style={{ fontSize: 'clamp(40px, 5.4vw, 72px)', color: INK }}
>
告诉我们<br />
你的 <span style={{ color: ACC }}>AI 场景</span>
</h1>
<p className="mt-6 mx-auto max-w-2xl text-base leading-[1.65]" style={{ color: SUB }}>
大客户定制路由长期合约技术对接 都从这里开始
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-6 text-xs" style={{ color: DIM }}>
<span className="inline-flex items-center gap-1.5">
<Check size={12} style={{ color: ACC }} /> 24 小时内回复
</span>
<span className="inline-flex items-center gap-1.5">
<Check size={12} style={{ color: ACC }} /> 可签 NDA
</span>
<span className="inline-flex items-center gap-1.5">
<Check size={12} style={{ color: ACC }} /> 无电销骚扰
</span>
</div>
</div>
</section>
{/* Form + Info */}
<section style={{ background: BG }}>
<div className="max-w-[1200px] mx-auto px-6 lg:px-10 pb-24">
<div className="grid gap-8 lg:grid-cols-[1.4fr_1fr]">
{/* Form */}
<form
onSubmit={submit}
className="rounded-2xl border bg-white p-8 lg:p-10 shadow-sm"
style={{ borderColor: LINE }}
>
<div className="font-mono text-[11px] uppercase tracking-[0.25em]" style={{ color: DIM }}>get in touch</div>
<h2
className="mt-4 font-heading text-2xl font-semibold leading-[1.2] tracking-tight lg:text-[32px]"
style={{ color: INK }}
>
填一下<br />我们 24h 内联系你
</h2>
<div className="mt-8 grid gap-5 md:grid-cols-2">
<Field label="公司名" value={form.company} onChange={update('company')} required />
<Field label="你的姓名" value={form.name} onChange={update('name')} required />
</div>
<div className="mt-5">
<Field
label="工作邮箱"
type="email"
value={form.email}
onChange={update('email')}
required
placeholder="you@company.com"
/>
</div>
<div className="mt-5">
<div className="mb-2 text-xs" style={{ color: DIM }}>月调用量</div>
<div className="relative">
<select
value={form.volume}
onChange={update('volume')}
className="w-full appearance-none rounded-lg border bg-white pl-3.5 pr-9 py-2.5 text-sm outline-none transition-colors focus:border-[#635BFF]"
style={{ borderColor: LINE, color: form.volume ? INK : DIM }}
>
<option value="">选择大致量级</option>
<option>10 万以下</option>
<option>10 100 </option>
<option>100 1000 </option>
<option>1000 万以上</option>
<option>暂未确定</option>
</select>
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs" style={{ color: DIM }}></span>
</div>
</div>
<div className="mt-5">
<div className="mb-2 text-xs" style={{ color: DIM }}>你的场景</div>
<textarea
value={form.message}
onChange={update('message')}
rows={4}
placeholder="例如:做企业知识库客服,需要稳定的 Claude + 国产模型双备援,月调用量约 300 万..."
className="w-full rounded-lg border bg-white px-3.5 py-2.5 text-sm outline-none transition-colors focus:border-[#635BFF] resize-y"
style={{ borderColor: LINE, color: INK }}
/>
</div>
<div className="mt-7 flex flex-wrap items-center gap-4">
<button
type="submit"
className="inline-flex h-11 items-center gap-2 rounded-full px-6 text-sm font-semibold text-white hover:opacity-90"
style={{ background: INK }}
>
发送 <ArrowRight size={13} />
</button>
<span className="text-xs" style={{ color: DIM }}>
提交即同意我们通过邮箱回访仅一次
</span>
</div>
</form>
{/* Info side */}
<div className="space-y-4">
<InfoCard
icon={<Mail size={16} />}
title="销售咨询"
primary="sales@turingtoken.ai"
sub="工作日 24h 内回复;加急请电话"
/>
<InfoCard
icon={<MessageSquare size={16} />}
title="技术支持"
primary="support@turingtoken.ai"
sub="已签合约客户优先P0 故障 1h 响应"
/>
<InfoCard
icon={<ShieldCheck size={16} />}
title="法务 / NDA"
primary="legal@turingtoken.ai"
sub="签前可先发 NDA 模板,含双向保密"
/>
<InfoCard
icon={<Building2 size={16} />}
title="办公室"
primary="上海 · 北京 · 深圳"
sub="仅预约接待,不接受 walk-in"
/>
</div>
</div>
</div>
</section>
{/* FAQ */}
<section style={{ background: SOFT }}>
<div className="max-w-[1100px] mx-auto px-6 lg:px-10 py-24 lg:py-32">
<div className="text-center">
<div className="font-mono text-[11px] uppercase tracking-[0.25em]" style={{ color: DIM }}>frequently asked</div>
<h2
className="mt-4 font-heading text-3xl font-semibold tracking-tight lg:text-4xl"
style={{ color: INK }}
>
常见问题
</h2>
</div>
<div className="mt-14 grid gap-5 md:grid-cols-2">
{[
{
q: '支持私有化部署吗?',
a: '支持。签约后 24 周内完成含多模型路由、监控面板、计费、权限管理、SSO。',
},
{
q: '大客户有专属价格吗?',
a: '月调用量 100 万以上可谈。多数客户能拿到比 OpenAI 直连便宜 30%+ 的合约价。',
},
{
q: '能签长期合约吗?',
a: '可以。年付 / 季付都支持,附 SLA 99.95% 兜底,故障按比例退款。',
},
{
q: '数据合规怎么做?',
a: '全链路 TLS 1.3;境内流量走国内供应商;日志 30 天滚动删除;支持 GDPR 与个保法。',
},
].map((f, i) => (
<div
key={i}
className="rounded-xl border bg-white p-6"
style={{ borderColor: LINE }}
>
<h3 className="font-heading text-lg font-semibold tracking-tight" style={{ color: INK }}>{f.q}</h3>
<p className="mt-3 text-sm leading-[1.7]" style={{ color: SUB }}>{f.a}</p>
</div>
))}
</div>
</div>
</section>
{/* Closing */}
<section className="relative overflow-hidden" style={{ background: BG }}>
<div
className="pointer-events-none absolute inset-x-0 bottom-0 h-[400px]"
style={{ background: 'radial-gradient(60% 60% at 50% 100%, #EEEBFF 0%, transparent 70%)' }}
/>
<div className="relative mx-auto max-w-3xl px-6 lg:px-10 py-24 lg:py-32 text-center">
<h2
className="font-heading text-3xl font-semibold leading-tight tracking-[-0.02em] lg:text-[44px]"
style={{ color: INK }}
>
先用起来再聊
</h2>
<p className="mt-5 text-base leading-[1.7]" style={{ color: SUB }}>
注册送 ¥10 试跑金足够跑完一组 benchmark一轮压测聊完再决定也不迟
</p>
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<Link
to="/register"
className="inline-flex h-11 items-center gap-2 rounded-full px-6 text-sm font-semibold text-white hover:opacity-90"
style={{ background: INK }}
>
免费注册 <ArrowRight size={13} />
</Link>
<Link
to="/"
className="inline-flex h-11 items-center gap-1.5 rounded-full border px-6 text-sm hover:bg-[#0A254006]"
style={{ borderColor: INK, color: INK }}
>
先看首页
</Link>
</div>
</div>
</section>
{/* Footer */}
<footer className="border-t" style={{ borderColor: LINE, background: BG }}>
<div className="max-w-[1200px] mx-auto px-6 lg:px-10 py-12">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<Link to="/" className="inline-flex items-center gap-2.5">
<img src="/logo.jpg" alt="logo" className="h-7 w-7 rounded-full" />
<span className="font-heading text-base font-semibold tracking-tight" style={{ color: INK }}>TuringToken</span>
</Link>
<span className="text-xs" style={{ color: DIM }}>© 2026 TuringToken · Made for developers.</span>
</div>
</div>
</footer>
</div>
);
}

File diff suppressed because it is too large Load Diff