Просмотр исходного кода

♻️refactor: Home Page and Footer

Apple\Apple 9 месяцев назад
Родитель
Сommit
c04a816e59

+ 1 - 0
web/package.json

@@ -6,6 +6,7 @@
   "dependencies": {
     "@douyinfe/semi-icons": "^2.63.1",
     "@douyinfe/semi-ui": "^2.69.1",
+    "@lobehub/icons": "^2.0.0",
     "@visactor/react-vchart": "~1.8.8",
     "@visactor/vchart": "~1.8.8",
     "@visactor/vchart-semi-theme": "~1.8.8",

+ 84 - 33
web/src/components/Footer.js

@@ -1,10 +1,16 @@
-import React, { useEffect, useState, useMemo } from 'react';
+import React, { useEffect, useState, useMemo, useContext } from 'react';
 import { useTranslation } from 'react-i18next';
-import { getFooterHTML } from '../helpers';
+import { Typography } from '@douyinfe/semi-ui';
+import { getFooterHTML, getLogo, getSystemName } from '../helpers';
+import { StatusContext } from '../context/Status';
 
 const FooterBar = () => {
   const { t } = useTranslation();
   const [footer, setFooter] = useState(getFooterHTML());
+  const systemName = getSystemName();
+  const logo = getLogo();
+  const [statusState] = useContext(StatusContext);
+  const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
 
   const loadFooter = () => {
     let footer_html = localStorage.getItem('footer_html');
@@ -13,48 +19,93 @@ const FooterBar = () => {
     }
   };
 
-  const defaultFooter = useMemo(() => (
-    <div className='custom-footer'>
-      <a
-        href='https://github.com/Calcium-Ion/new-api'
-        target='_blank'
-        rel='noreferrer'
-      >
-        New API {import.meta.env.VITE_REACT_APP_VERSION}{' '}
-      </a>
-      {t('由')}{' '}
-      <a href='https://github.com/Calcium-Ion' target='_blank' rel='noreferrer'>
-        Calcium-Ion
-      </a>{' '}
-      {t('开发,基于')}{' '}
-      <a
-        href='https://github.com/songquanpeng/one-api'
-        target='_blank'
-        rel='noreferrer'
-      >
-        One API
-      </a>
-    </div>
-  ), [t]);
+  const currentYear = new Date().getFullYear();
+
+  const customFooter = useMemo(() => (
+    <footer className="relative bg-gray-900 dark:bg-[#1C1F23] mt-[60px] h-auto py-16 px-6 md:px-24 w-full flex flex-col items-center justify-between overflow-hidden">
+      <div className="absolute hidden md:block top-[204px] left-[-100px] w-[151px] h-[151px] rounded-full bg-[#FFD166]"></div>
+      <div className="absolute md:hidden bottom-[20px] left-[-50px] w-[80px] h-[80px] rounded-full bg-[#FFD166] opacity-60"></div>
+
+      {isDemoSiteMode && (
+        <div className="flex flex-col md:flex-row justify-between w-full max-w-[1110px] mb-10 gap-8">
+          <div className="flex-shrink-0">
+            <img
+              src={logo}
+              alt={systemName}
+              className="w-16 h-16 rounded-full bg-gray-800 p-1.5 object-contain"
+            />
+          </div>
+
+          <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8 w-full">
+            <div className="text-left">
+              <p className="!text-[#d9dbe1] font-semibold mb-5">{t('关于我们')}</p>
+              <div className="flex flex-col gap-4">
+                <a href="https://docs.newapi.pro/wiki/project-introduction/" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">{t('关于项目')}</a>
+                <a href="https://docs.newapi.pro/support/community-interaction/" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">{t('联系我们')}</a>
+                <a href="https://docs.newapi.pro/wiki/features-introduction/" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">{t('功能特性')}</a>
+              </div>
+            </div>
+
+            <div className="text-left">
+              <p className="!text-[#d9dbe1] font-semibold mb-5">{t('文档')}</p>
+              <div className="flex flex-col gap-4">
+                <a href="https://docs.newapi.pro/getting-started/" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">{t('快速开始')}</a>
+                <a href="https://docs.newapi.pro/installation/" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">{t('安装指南')}</a>
+                <a href="https://docs.newapi.pro/api/" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">{t('API 文档')}</a>
+              </div>
+            </div>
+
+            <div className="text-left">
+              <p className="!text-[#d9dbe1] font-semibold mb-5">{t('相关项目')}</p>
+              <div className="flex flex-col gap-4">
+                <a href="https://github.com/songquanpeng/one-api" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">One API</a>
+                <a href="https://github.com/novicezk/midjourney-proxy" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">Midjourney-Proxy</a>
+                <a href="https://github.com/Deeptrain-Community/chatnio" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">chatnio</a>
+                <a href="https://github.com/Calcium-Ion/neko-api-key-tool" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">neko-api-key-tool</a>
+              </div>
+            </div>
+
+            <div className="text-left">
+              <p className="!text-[#d9dbe1] font-semibold mb-5">{t('基于New API的项目')}</p>
+              <div className="flex flex-col gap-4">
+                <a href="https://github.com/Calcium-Ion/new-api-horizon" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">new-api-horizon</a>
+                <a href="https://github.com/VoAPI/VoAPI" target="_blank" rel="noopener noreferrer" className="!text-[#d9dbe1]">VoAPI</a>
+              </div>
+            </div>
+          </div>
+        </div>
+      )}
+
+      <div className="flex flex-col md:flex-row items-center justify-between w-full max-w-[1110px] gap-6">
+        <div className="flex flex-wrap items-center gap-2">
+          <Typography.Text className="text-sm !text-[#d9dbe1]">© {currentYear} {systemName}. {t('版权所有')}</Typography.Text>
+        </div>
+
+        {isDemoSiteMode && (
+          <div className="text-sm">
+            <span className="!text-[#d9dbe1]">{t('设计与开发由')} </span>
+            <span className="!text-[#01ffc3]">Douyin FE</span>
+            <span className="!text-[#d9dbe1]"> & </span>
+            <a href="https://github.com/QuantumNous" target="_blank" rel="noreferrer" className="!text-[#01ffc3] hover:!text-[#01ffc3]">QuantumNous</a>
+          </div>
+        )}
+      </div>
+    </footer>
+  ), [logo, systemName, t, currentYear, isDemoSiteMode]);
 
   useEffect(() => {
     loadFooter();
   }, []);
 
   return (
-    <div
-      style={{
-        textAlign: 'center',
-        paddingBottom: '5px',
-      }}
-    >
+    <div className="w-full">
       {footer ? (
         <div
-          className='custom-footer'
+          className="custom-footer"
           dangerouslySetInnerHTML={{ __html: footer }}
         ></div>
       ) : (
-        defaultFooter
+        customFooter
       )}
     </div>
   );

+ 3 - 1
web/src/components/HeaderBar.js

@@ -356,7 +356,7 @@ const HeaderBar = () => {
               {isLoading ? (
                 <Skeleton.Image className="h-7 md:h-8 !rounded-full" style={{ width: 32, height: 32 }} />
               ) : (
-                <img src={logo} alt="logo" className="h-7 md:h-8 transition-transform duration-300 ease-in-out group-hover:scale-105" />
+                <img src={logo} alt="logo" className="h-7 md:h-8 transition-transform duration-300 ease-in-out group-hover:scale-105 rounded-full" />
               )}
               <div className="hidden md:flex items-center gap-2">
                 <div className="flex items-center gap-2">
@@ -374,6 +374,7 @@ const HeaderBar = () => {
                       color={isSelfUseMode ? 'purple' : 'blue'}
                       className="text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm"
                       size="small"
+                      shape='circle'
                     >
                       {isSelfUseMode ? t('自用模式') : t('演示站点')}
                     </Tag>
@@ -387,6 +388,7 @@ const HeaderBar = () => {
                   color={isSelfUseMode ? 'purple' : 'blue'}
                   className="ml-2 text-xs px-1 py-0.5 rounded whitespace-nowrap shadow-sm"
                   size="small"
+                  shape='circle'
                 >
                   {isSelfUseMode ? t('自用模式') : t('演示站点')}
                 </Tag>

+ 13 - 4
web/src/components/Loading.js

@@ -1,11 +1,20 @@
 import React from 'react';
 import { Spin } from '@douyinfe/semi-ui';
 
-const Loading = ({ prompt: name = 'page' }) => {
+const Loading = ({ prompt: name = '', size = 'large' }) => {
   return (
-    <Spin style={{ height: 100 }} spinning={true}>
-      加载{name}中...
-    </Spin>
+    <div className="fixed inset-0 w-screen h-screen flex items-center justify-center bg-white/80 z-[1000]">
+      <div className="flex flex-col items-center">
+        <Spin 
+          size={size} 
+          spinning={true} 
+          tip={null}
+        />
+        <span className="whitespace-nowrap mt-2 text-center" style={{ color: 'var(--semi-color-primary)' }}>
+          {name ? `加载${name}中...` : '加载中...'}
+        </span>
+      </div>
+    </div>
   );
 };
 

+ 15 - 1
web/src/i18n/locales/en.json

@@ -1373,5 +1373,19 @@
   "不需要设置模型价格,系统将弱化用量计算,您可专注于使用模型。": "No need to set the model price, the system will weaken the usage calculation, you can focus on using the model.",
   "适用于展示系统功能的场景。": "Suitable for scenarios where the system functions are displayed.",
   "可在初始化后修改": "Can be modified after initialization",
-  "初始化系统": "Initialize system"
+  "初始化系统": "Initialize system",
+  "支持众多的大模型供应商": "Supporting various LLM providers",
+  "新一代大模型网关与AI资产管理系统,一键接入主流大模型,轻松管理您的AI资产": "Next-generation LLM gateway and AI asset management system, one-click integration with mainstream models, easily manage your AI assets",
+  "开始使用": "Get Started",
+  "关于我们": "About Us",
+  "关于项目": "About Project",
+  "联系我们": "Contact Us",
+  "功能特性": "Features",
+  "快速开始": "Quick Start",
+  "安装指南": "Installation Guide",
+  "API 文档": "API Documentation",
+  "相关项目": "Related Projects",
+  "基于New API的项目": "Projects Based on New API",
+  "版权所有": "All rights reserved",
+  "设计与开发由": "Designed & Developed with love by"
 }

BIN
web/src/images/example.png


+ 146 - 134
web/src/pages/Home/index.js

@@ -1,17 +1,23 @@
 import React, { useContext, useEffect, useState } from 'react';
-import { Card, Col, Row } from '@douyinfe/semi-ui';
-import { API, showError, showNotice, timestamp2string } from '../../helpers';
+import { Button, Typography, Tag } from '@douyinfe/semi-ui';
+import { API, showError, showNotice } from '../../helpers';
 import { StatusContext } from '../../context/Status';
 import { marked } from 'marked';
-import { StyleContext } from '../../context/Style/index.js';
 import { useTranslation } from 'react-i18next';
+import { IconGithubLogo } from '@douyinfe/semi-icons';
+import exampleImage from '../../images/example.png';
+import { Link } from 'react-router-dom';
+import { Moonshot, OpenAI, XAI, Zhipu, Volcengine, Cohere, Claude, Gemini, Suno, Minimax, Wenxin, Spark, Qingyan, DeepSeek, Qwen, Midjourney, Grok, AzureAI, Hunyuan, Xinference } from '@lobehub/icons';
+
+const { Text } = Typography;
 
 const Home = () => {
   const { t, i18n } = useTranslation();
   const [statusState] = useContext(StatusContext);
   const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
   const [homePageContent, setHomePageContent] = useState('');
-  const [styleState, styleDispatch] = useContext(StyleContext);
+
+  const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
 
   const displayNotice = async () => {
     const res = await API.get('/api/notice');
@@ -45,8 +51,6 @@ const Home = () => {
         const iframe = document.querySelector('iframe');
         if (iframe) {
           const theme = localStorage.getItem('theme-mode') || 'light';
-          // 测试是否正确传递theme-mode给iframe
-          // console.log('Sending theme-mode to iframe:', theme);
           iframe.onload = () => {
             iframe.contentWindow.postMessage({ themeMode: theme }, '*');
             iframe.contentWindow.postMessage({ lang: i18n.language }, '*');
@@ -60,153 +64,161 @@ const Home = () => {
     setHomePageContentLoaded(true);
   };
 
-  const getStartTimeString = () => {
-    const timestamp = statusState?.status?.start_time;
-    return statusState.status ? timestamp2string(timestamp) : '';
-  };
-
   useEffect(() => {
     displayNotice().then();
     displayHomePageContent().then();
   }, []);
 
   return (
-    <>
+    <div className="w-full overflow-x-hidden">
       {homePageContentLoaded && homePageContent === '' ? (
-        <>
-          <Card
-            bordered={false}
-            headerLine={false}
-            title={t('系统状况')}
-            bodyStyle={{ padding: '10px 20px' }}
-          >
-            <Row gutter={16}>
-              <Col span={12}>
-                <Card
-                  title={t('系统信息')}
-                  headerExtraContent={
-                    <span
-                      style={{
-                        fontSize: '12px',
-                        color: 'var(--semi-color-text-1)',
-                      }}
-                    >
-                      {t('系统信息总览')}
-                    </span>
-                  }
-                >
-                  <p>
-                    {t('名称')}:{statusState?.status?.system_name}
-                  </p>
-                  <p>
-                    {t('版本')}:
-                    {statusState?.status?.version
-                      ? statusState?.status?.version
-                      : 'unknown'}
-                  </p>
-                  <p>
-                    {t('源码')}:
-                    <a
-                      href='https://github.com/Calcium-Ion/new-api'
-                      target='_blank'
-                      rel='noreferrer'
-                    >
-                      https://github.com/Calcium-Ion/new-api
-                    </a>
-                  </p>
-                  
-                  <p>
-                    {t('协议')}:
-                    <a
-                      href='https://www.apache.org/licenses/LICENSE-2.0'
-                      target='_blank'
-                      rel='noreferrer'
-                    >
-                      Apache-2.0 License
-                    </a>
-                  </p>
-                  <p>
-                    {t('启动时间')}:{getStartTimeString()}
-                  </p>
-                </Card>
-              </Col>
-              <Col span={12}>
-                <Card
-                  title={t('系统配置')}
-                  headerExtraContent={
-                    <span
-                      style={{
-                        fontSize: '12px',
-                        color: 'var(--semi-color-text-1)',
-                      }}
+        <div className="w-full overflow-x-hidden">
+          {/* Banner 部分 */}
+          <div className="w-full border-b border-semi-color-border min-h-[500px] md:h-[650px] lg:h-[750px] relative overflow-x-hidden">
+            <div className="flex flex-col md:flex-row items-center justify-center h-full px-4 py-8 md:py-0">
+              {/* 左侧内容区 */}
+              <div className="flex-shrink-0 w-full md:w-[480px] md:mr-[60px] lg:mr-[120px] mb-8 md:mb-0">
+                <div className="flex items-center gap-2 justify-center md:justify-start">
+                  <h1 className="text-3xl md:text-4xl lg:text-5xl font-semibold text-semi-color-text-0 w-auto leading-normal md:leading-[67px]">
+                    {statusState?.status?.system_name}
+                  </h1>
+                  {statusState?.status?.version && (
+                    <Tag color='light-blue' size='large' shape='circle' className="ml-1">
+                      {statusState.status.version}
+                    </Tag>
+                  )}
+                </div>
+                <p className="text-base md:text-lg text-semi-color-text-0 mt-4 md:mt-8 w-full md:w-[480px] leading-7 md:leading-8 text-center md:text-left">
+                  {t('新一代大模型网关与AI资产管理系统,一键接入主流大模型,轻松管理您的AI资产')}
+                </p>
+
+                {/* 操作按钮 */}
+                <div className="mt-6 md:mt-10 flex flex-wrap gap-4 justify-center md:justify-start">
+                  <Link to="/login">
+                    <Button theme="solid" type="primary" size="large">
+                      {t('开始使用')}
+                    </Button>
+                  </Link>
+                  {isDemoSiteMode && (
+                    <Button
+                      size="large"
+                      className="flex items-center"
+                      icon={<IconGithubLogo />}
+                      onClick={() => window.open('https://github.com/QuantumNous/new-api', '_blank')}
                     >
-                      {t('系统配置总览')}
-                    </span>
-                  }
-                >
-                  <p>
-                    {t('邮箱验证')}:
-                    {statusState?.status?.email_verification === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                  <p>
-                    {t('GitHub 身份验证')}:
-                    {statusState?.status?.github_oauth === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                  <p>
-                    {t('OIDC 身份验证')}:
-                    {statusState?.status?.oidc === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                  <p>
-                    {t('微信身份验证')}:
-                    {statusState?.status?.wechat_login === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                  <p>
-                    {t('Turnstile 用户校验')}:
-                    {statusState?.status?.turnstile_check === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                  <p>
-                    {t('Telegram 身份验证')}:
-                    {statusState?.status?.telegram_oauth === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                  <p>
-                    {t('Linux DO 身份验证')}:
-                    {statusState?.status?.linuxdo_oauth === true
-                      ? t('已启用')
-                      : t('未启用')}
-                  </p>
-                </Card>
-              </Col>
-            </Row>
-          </Card>
-        </>
+                      GitHub
+                    </Button>
+                  )}
+                </div>
+
+                {/* 框架兼容性图标 */}
+                <div className="mt-8 md:mt-16">
+                  <div className="flex items-center mb-3 justify-center md:justify-start">
+                    <Text type="tertiary" className="text-lg md:text-xl font-light">
+                      {t('支持众多的大模型供应商')}
+                    </Text>
+                  </div>
+                  <div className="flex flex-wrap items-center relative mt-6 md:mt-8 gap-6 md:gap-8 justify-center md:justify-start">
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Moonshot size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <OpenAI size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <XAI size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Zhipu.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Volcengine.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Cohere.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Claude.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Gemini.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Suno size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Minimax.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Wenxin.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Spark.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Qingyan.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <DeepSeek.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Qwen.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Midjourney size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Grok size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <AzureAI.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Hunyuan.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Xinference.Color size={40} />
+                    </div>
+                    <div className="relative w-8 md:w-10 h-8 md:h-10 flex items-center justify-center">
+                      <Typography.Text className="!text-2xl font-bold">30+</Typography.Text>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              {/* 右侧图片区域 - 在小屏幕上隐藏或调整位置 */}
+              <div className="flex-shrink-0 relative md:mr-[-200px] lg:mr-[-400px] hidden md:block">
+                <div className="absolute w-[320px] md:w-[500px] lg:w-[640px] h-[320px] md:h-[500px] lg:h-[640px] left-[-25px] md:left-[-40px] lg:left-[-50px] top-[-10px] md:top-[-15px] lg:top-[-20px] opacity-60"
+                  style={{ filter: 'blur(120px)' }}>
+                  <div className="absolute w-[320px] md:w-[400px] lg:w-[474px] h-[320px] md:h-[400px] lg:h-[474px] top-[80px] md:top-[100px] lg:top-[132px] bg-semi-color-primary rounded-full opacity-30"></div>
+                  <div className="absolute w-[320px] md:w-[400px] lg:w-[474px] h-[320px] md:h-[400px] lg:h-[474px] left-[80px] md:left-[120px] lg:left-[166px] bg-semi-color-tertiary rounded-full opacity-30"></div>
+                </div>
+
+                <img
+                  src={exampleImage}
+                  alt="application demo"
+                  className="relative h-[400px] md:h-[600px] lg:h-[721px] ml-[-15px] md:ml-[-20px] lg:ml-[-30px] mt-[-15px] md:mt-[-20px] lg:mt-[-30px]"
+                />
+              </div>
+            </div>
+          </div>
+        </div>
       ) : (
-        <>
+        <div className="overflow-x-hidden w-full">
           {homePageContent.startsWith('https://') ? (
             <iframe
               src={homePageContent}
-              style={{ width: '100%', height: '100vh', border: 'none' }}
+              className="w-full h-screen border-none"
             />
           ) : (
             <div
-              style={{ fontSize: 'larger' }}
+              className="text-base md:text-lg p-4 md:p-6 overflow-x-hidden"
               dangerouslySetInnerHTML={{ __html: homePageContent }}
             ></div>
           )}
-        </>
+        </div>
       )}
-    </>
+    </div>
   );
 };