首页改版:紫色主题 + 登录页重构
This commit is contained in:
parent
6836012ab4
commit
f55309e953
|
|
@ -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...")
|
||||||
|
|
|
||||||
|
|
@ -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={
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 像调用"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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: '支持。签约后 2–4 周内完成,含多模型路由、监控面板、计费、权限管理、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
Loading…
Reference in New Issue