design: redesign homepage with tech-minimalist style, remove blur-ball and shine-text
Docker Build / Build and Push Docker Image (push) Successful in 4m27s

This commit is contained in:
2026-06-14 11:28:31 +08:00
parent a318279d5f
commit 6f1f8b60f1
4 changed files with 326 additions and 210 deletions
+11
View File
@@ -1379,6 +1379,17 @@
"已复制:{{name}}": "Copied: {{name}}",
"已复制全部数据": "All data copied",
"已复制到剪切板": "Copied to clipboard",
"统一 AI 接入": "Unified AI Access",
"大模型": "LLM",
"统一接口网关": "Unified API Gateway",
"一个端点,接入所有模型。兼容 OpenAI 格式,即换即用。": "One endpoint for all models. OpenAI-compatible, switch and go.",
"登录控制台": "Console",
"availability": "Availability",
"latency": "Latency",
"compatibility": "Compatibility",
"providers": "Providers",
"Supported Providers": "Supported Providers",
"加载首页内容失败...": "Failed to load homepage content...",
"已复制到剪贴板": "Copied to clipboard",
"已复制到剪贴板!": "Copied to clipboard!",
"已复制字段:{{name}}": "Field copied: {{name}}",
+11
View File
@@ -920,6 +920,17 @@
"已复制:{{name}}": "已复制:{{name}}",
"已复制全部数据": "已复制全部数据",
"已复制到剪切板": "已复制到剪切板",
"统一 AI 接入": "统一 AI 接入",
"大模型": "大模型",
"统一接口网关": "统一接口网关",
"一个端点,接入所有模型。兼容 OpenAI 格式,即换即用。": "一个端点,接入所有模型。兼容 OpenAI 格式,即换即用。",
"登录控制台": "登录控制台",
"availability": "可用性",
"latency": "延迟",
"compatibility": "兼容性",
"providers": "供应商",
"Supported Providers": "支持的供应商",
"加载首页内容失败...": "加载首页内容失败...",
"已复制到剪贴板": "已复制到剪贴板",
"已复制到剪贴板!": "已复制到剪贴板!",
"已复制模型名称": "已复制模型名称",
+202 -25
View File
@@ -769,40 +769,217 @@ html.dark .sbg-variant-green {
}
/* ==================== Banner 背景模糊球 ==================== */
.blur-ball {
/* ==================== Home Page Styles ==================== */
.home-hero {
flex: 1 0 auto;
position: relative;
overflow: hidden;
}
/* Grid background - tech feel */
.home-grid-bg {
position: absolute;
width: 360px;
height: 360px;
border-radius: 50%;
filter: blur(120px);
inset: 0;
background-image:
linear-gradient(rgba(var(--semi-grey-1), 0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(var(--semi-grey-1), 0.06) 1px, transparent 1px);
background-size: 60px 60px;
mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, black 30%, transparent 100%);
-webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, black 30%, transparent 100%);
pointer-events: none;
z-index: -1;
z-index: 0;
}
.blur-ball-indigo {
background: #6366f1;
/* indigo-500 */
top: 40px;
left: 50%;
transform: translateX(-50%);
html.dark .home-grid-bg {
background-image:
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
}
/* Tag badge */
.home-tag {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
border-radius: 9999px;
border: 1px solid var(--semi-color-border);
background: var(--semi-color-bg-1);
font-size: 13px;
color: var(--semi-color-text-1);
letter-spacing: 0.02em;
}
.home-tag-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #10b981;
box-shadow: 0 0 6px #10b981;
}
/* Title */
.home-title {
font-size: 2.5rem;
font-weight: 700;
line-height: 1.15;
color: var(--semi-color-text-0);
letter-spacing: -0.02em;
}
@media (min-width: 768px) {
.home-title {
font-size: 3.5rem;
}
}
@media (min-width: 1024px) {
.home-title {
font-size: 4rem;
}
}
.home-title-accent {
background: linear-gradient(135deg, var(--semi-color-primary) 0%, #06b6d4 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Subtitle */
.home-subtitle {
margin-top: 16px;
font-size: 1rem;
line-height: 1.6;
color: var(--semi-color-text-2);
max-width: 420px;
}
@media (min-width: 768px) {
.home-subtitle {
font-size: 1.125rem;
margin-top: 20px;
}
}
/* URL bar */
.home-url-bar {
margin-top: 24px;
width: 100%;
max-width: 460px;
}
.home-url-input {
border-radius: 10px !important;
}
.home-url-input .semi-input-wrapper {
border-radius: 10px !important;
}
/* CTA button */
.home-cta-btn {
border-radius: 10px !important;
padding-left: 24px;
padding-right: 24px;
}
/* Metrics */
.home-metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
margin-top: 48px;
width: 100%;
max-width: 520px;
border: 1px solid var(--semi-color-border);
border-radius: 12px;
overflow: hidden;
background: var(--semi-color-bg-1);
}
.home-metric-item {
padding: 16px 12px;
text-align: center;
border-right: 1px solid var(--semi-color-border);
}
.home-metric-item:last-child {
border-right: none;
}
.home-metric-value {
font-size: 1.5rem;
font-weight: 700;
color: var(--semi-color-text-0);
font-variant-numeric: tabular-nums;
line-height: 1.2;
}
@media (min-width: 768px) {
.home-metric-value {
font-size: 1.75rem;
}
}
.home-metric-suffix {
font-size: 0.875rem;
font-weight: 500;
color: var(--semi-color-text-1);
}
.home-metric-label {
font-size: 12px;
color: var(--semi-color-text-2);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Providers */
.home-providers {
margin-top: 48px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.home-provider-grid {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 16px;
max-width: 600px;
}
@media (min-width: 768px) {
.home-provider-grid {
gap: 20px;
}
}
.home-provider-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.5;
transition: opacity 0.2s ease;
}
.blur-ball-teal {
background: #14b8a6;
/* teal-400 */
top: 200px;
left: 30%;
opacity: 0.4;
.home-provider-icon:hover {
opacity: 1;
}
/* 浅色主题下让模糊球更柔和 */
html:not(.dark) .blur-ball-indigo {
opacity: 0.25;
}
html:not(.dark) .blur-ball-teal {
opacity: 0.2;
@media (min-width: 768px) {
.home-provider-icon {
width: 36px;
height: 36px;
}
}
/* ==================== 卡片马卡龙模糊球(类封装) ==================== */
+102 -185
View File
@@ -22,49 +22,57 @@ import {
Button,
Typography,
Input,
ScrollList,
ScrollItem,
} from '@douyinfe/semi-ui';
import { API, showError, copy, showSuccess } from '../../helpers';
import { useIsMobile } from '../../hooks/common/useIsMobile';
import { API_ENDPOINTS } from '../../constants/common.constant';
import { StatusContext } from '../../context/Status';
import { useActualTheme } from '../../context/Theme';
import { marked } from 'marked';
import { useTranslation } from 'react-i18next';
import {
IconGithubLogo,
IconPlay,
IconFile,
IconCopy,
IconArrowRight,
} from '@douyinfe/semi-icons';
import { Link } from 'react-router-dom';
import NoticeModal from '../../components/layout/NoticeModal';
import {
Moonshot,
OpenAI,
XAI,
Zhipu,
Volcengine,
Cohere,
Claude,
Gemini,
Suno,
Minimax,
Wenxin,
Spark,
Qingyan,
DeepSeek,
Qwen,
Midjourney,
Grok,
AzureAI,
Zhipu,
Volcengine,
Hunyuan,
Wenxin,
Minimax,
Spark,
Grok,
Cohere,
Moonshot,
Midjourney,
Suno,
AzureAI,
Xinference,
Qingyan,
XAI,
} from '@lobehub/icons';
const { Text } = Typography;
const METRICS = [
{ key: 'availability', value: '99.98', suffix: '%' },
{ key: 'latency', value: '0.25', suffix: 's' },
{ key: 'compatibility', value: '100', suffix: '%' },
{ key: 'providers', value: '30', suffix: '+' },
];
const PROVIDERS = [
OpenAI, Claude, Gemini, DeepSeek, Qwen, Zhipu, Volcengine,
Hunyuan, Wenxin, Minimax, Spark, Grok, Cohere, Moonshot,
Midjourney, Suno, AzureAI, Xinference, Qingyan, XAI,
];
const Home = () => {
const { t, i18n } = useTranslation();
const [statusState] = useContext(StatusContext);
@@ -73,12 +81,8 @@ const Home = () => {
const [homePageContent, setHomePageContent] = useState('');
const [noticeVisible, setNoticeVisible] = useState(false);
const isMobile = useIsMobile();
const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
const docsLink = statusState?.status?.docs_link || '';
const serverAddress =
statusState?.status?.server_address || `${window.location.origin}`;
const endpointItems = API_ENDPOINTS.map((e) => ({ value: e }));
const [endpointIndex, setEndpointIndex] = useState(0);
const isChinese = i18n.language.startsWith('zh');
const displayHomePageContent = async () => {
@@ -93,7 +97,6 @@ const Home = () => {
setHomePageContent(content);
localStorage.setItem('home_page_content', content);
// 如果内容是 URL,则发送主题模式
if (data.startsWith('https://')) {
const iframe = document.querySelector('iframe');
if (iframe) {
@@ -105,7 +108,7 @@ const Home = () => {
}
} else {
showError(message);
setHomePageContent('加载首页内容失败...');
setHomePageContent(t('加载首页内容失败...'));
}
setHomePageContentLoaded(true);
};
@@ -133,7 +136,6 @@ const Home = () => {
}
}
};
checkNoticeAndShow();
}, []);
@@ -141,13 +143,6 @@ const Home = () => {
displayHomePageContent().then();
}, []);
useEffect(() => {
const timer = setInterval(() => {
setEndpointIndex((prev) => (prev + 1) % endpointItems.length);
}, 3000);
return () => clearInterval(timer);
}, [endpointItems.length]);
return (
<div className='classic-page-fill classic-home-page w-full overflow-x-hidden'>
<NoticeModal
@@ -157,176 +152,98 @@ const Home = () => {
/>
{homePageContentLoaded && homePageContent === '' ? (
<div className='classic-home-default w-full overflow-x-hidden'>
{/* Banner 部分 */}
<div className='classic-home-hero w-full border-b border-semi-color-border relative overflow-x-hidden'>
{/* 背景模糊晕染球 */}
<div className='blur-ball blur-ball-indigo' />
<div className='blur-ball blur-ball-teal' />
<div className='flex items-center justify-center px-4 pt-24 pb-8'>
{/* 居中内容区 */}
<div className='flex flex-col items-center justify-center text-center max-w-4xl mx-auto'>
<div className='flex flex-col items-center justify-center mb-6 md:mb-8'>
<h1
className={`text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-semi-color-text-0 leading-tight ${isChinese ? 'tracking-wide md:tracking-wider' : ''}`}
>
<>
{t('统一的')}
<br />
<span className='shine-text'>{t('大模型接口网关')}</span>
</>
</h1>
<p className='text-base md:text-lg lg:text-xl text-semi-color-text-1 mt-4 md:mt-6 max-w-xl'>
{t('多模型统一接入,只需将基址替换为:')}
</p>
{/* BASE URL 与端点选择 */}
<div className='flex flex-col md:flex-row items-center justify-center gap-4 w-full mt-4 md:mt-6 max-w-md'>
<Input
readonly
value={serverAddress}
className='flex-1 !rounded-full'
size={isMobile ? 'default' : 'large'}
suffix={
<div className='flex items-center gap-2'>
<ScrollList
bodyHeight={32}
style={{ border: 'unset', boxShadow: 'unset' }}
>
<ScrollItem
mode='wheel'
cycled={true}
list={endpointItems}
selectedIndex={endpointIndex}
onSelect={({ index }) => setEndpointIndex(index)}
/>
</ScrollList>
<Button
type='primary'
onClick={handleCopyBaseURL}
icon={<IconCopy />}
className='!rounded-full'
/>
</div>
}
/>
</div>
<div className='home-hero w-full relative'>
{/* Grid background */}
<div className='home-grid-bg' />
<div className='flex items-center justify-center px-4 pt-20 pb-16 md:pt-28 md:pb-24'>
<div className='flex flex-col items-center justify-center text-center max-w-3xl mx-auto relative z-10'>
{/* Tag */}
<div className='home-tag mb-6'>
<span className='home-tag-dot' />
<span>{t('统一 AI 接入')}</span>
</div>
{/* 操作按钮 */}
<div className='flex flex-row gap-4 justify-center items-center'>
{/* Title */}
<h1 className='home-title'>
{t('大模型')}
<br />
<span className='home-title-accent'>{t('统一接口网关')}</span>
</h1>
{/* Subtitle */}
<p className='home-subtitle'>
{t('一个端点,接入所有模型。兼容 OpenAI 格式,即换即用。')}
</p>
{/* Base URL */}
<div className='home-url-bar'>
<Input
readonly
value={serverAddress}
className='home-url-input'
size={isMobile ? 'default' : 'large'}
suffix={
<Button
type='primary'
onClick={handleCopyBaseURL}
icon={<IconCopy />}
className='!rounded-lg'
size={isMobile ? 'default' : 'large'}
/>
}
/>
</div>
{/* CTA */}
<div className='flex flex-row gap-3 justify-center items-center mt-6'>
<Link to='/console'>
<Button
theme='solid'
type='primary'
size={isMobile ? 'default' : 'large'}
className='!rounded-3xl px-8 py-2'
icon={<IconPlay />}
className='home-cta-btn'
icon={<IconArrowRight />}
iconPosition='right'
>
{t('获取密钥')}
</Button>
</Link>
{isDemoSiteMode && statusState?.status?.version ? (
<Link to='/login'>
<Button
size={isMobile ? 'default' : 'large'}
className='flex items-center !rounded-3xl px-6 py-2'
icon={<IconGithubLogo />}
onClick={() =>
window.open(
'https://github.com/QuantumNous/new-api',
'_blank',
)
}
className='!rounded-lg px-6'
>
{statusState.status.version}
{t('登录控制台')}
</Button>
) : (
docsLink && (
<Button
size={isMobile ? 'default' : 'large'}
className='flex items-center !rounded-3xl px-6 py-2'
icon={<IconFile />}
onClick={() => window.open(docsLink, '_blank')}
>
{t('文档')}
</Button>
)
)}
</Link>
</div>
{/* 框架兼容性图标 */}
<div className='mt-12 md:mt-16 lg:mt-20 w-full'>
<div className='flex items-center mb-6 md:mb-8 justify-center'>
<Text
type='tertiary'
className='text-lg md:text-xl lg:text-2xl font-light'
>
{t('支持众多的大模型供应商')}
</Text>
</div>
<div className='flex flex-wrap items-center justify-center gap-3 sm:gap-4 md:gap-6 lg:gap-8 max-w-5xl mx-auto px-4'>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Moonshot size={40} />
{/* Metrics */}
<div className='home-metrics'>
{METRICS.map((m) => (
<div key={m.key} className='home-metric-item'>
<div className='home-metric-value'>
{m.value}<span className='home-metric-suffix'>{m.suffix}</span>
</div>
<div className='home-metric-label'>{t(m.key)}</div>
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<OpenAI size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<XAI size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Zhipu.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Volcengine.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Cohere.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Claude.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Gemini.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Suno size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Minimax.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Wenxin.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Spark.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Qingyan.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<DeepSeek.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Qwen.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Midjourney size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Grok size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<AzureAI.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Hunyuan.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Xinference.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Typography.Text className='!text-lg sm:!text-xl md:!text-2xl lg:!text-3xl font-bold'>
30+
</Typography.Text>
))}
</div>
{/* Providers */}
<div className='home-providers'>
<Text type='tertiary' className='!text-xs !tracking-widest !uppercase'>
{t('Supported Providers')}
</Text>
<div className='home-provider-grid'>
{PROVIDERS.map((Icon, i) => (
<div key={i} className='home-provider-icon'>
<Icon size={isMobile ? 24 : 28} />
</div>
))}
<div className='home-provider-icon'>
<Text className='!text-sm !font-semibold'>30+</Text>
</div>
</div>
</div>