improve:网页中使用配置的系统名称和介绍

This commit is contained in:
qian committed 2026-06-09 23:14:27 +08:00
1 parent d5ae4f1ff2
commit 8dc9266c14
10 files changed
+53 -35

No files matched your search

+6 -1
View File
@@ -3,6 +3,7 @@ import type { RouteRecordRaw } from 'vue-router'
import adminRoutes from './admin'
import { useAuthStore } from '@/stores/auth'
import { shouldRefresh, getRefreshToken } from '@/utils/request'
import { useSiteName } from '@/composables/useSiteName'
import axios from 'axios'
const routes: RouteRecordRaw[] = [
@@ -42,12 +43,16 @@ async function checkSetupStatus(): Promise<boolean> {
return systemInitialized === true
}
const { siteName, fetchSiteName } = useSiteName()
router.beforeEach(async (to) => {
const authStore = useAuthStore()
authStore.loadFromStorage()
if (to.meta.title) {
document.title = `${to.meta.title as string} — Team-API`
fetchSiteName()
const name = siteName.value || 'Team-API'
document.title = `${to.meta.title as string} — ${name}`
}
if (to.name === 'AdminSetup') {
+7 -12
View File
@@ -6,6 +6,7 @@ import axios from 'axios'
import { marked } from 'marked'
import request from '@/utils/request'
import { useWatermark } from '@/composables/useWatermark'
import { useSiteName } from '@/composables/useSiteName'
import {
IconDashboard,
IconUserGroup,
@@ -37,6 +38,7 @@ import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const { siteName } = useSiteName()
const collapsed = ref(false)
const mobileMenuOpen = ref(false)
@@ -445,10 +447,10 @@ onUnmounted(() => {
<!-- Desktop Sidebar -->
<div class="admin-sidebar" :class="{ 'admin-sidebar--collapsed': collapsed }">
<div class="admin-sidebar__logo" @click="openUpdateModal" style="cursor: pointer;">
<div class="admin-sidebar__logo-icon">T</div>
<img src="/favicon.png" alt="Logo" class="admin-sidebar__logo-icon" />
<Transition name="fade">
<span v-if="!collapsed" class="admin-sidebar__logo-text">
Team-API
{{ siteName || 'Team-API' }}
<span class="admin-sidebar__version">
{{ appVersion === 'dev' ? 'dev' : 'v' + appVersion }}
<span v-if="hasUpdate" class="admin-sidebar__update-dot" title="有新版本可用"></span>
@@ -526,8 +528,8 @@ onUnmounted(() => {
<Transition name="slide">
<div v-if="mobileMenuOpen" class="admin-sidebar admin-sidebar--mobile">
<div class="admin-sidebar__logo">
<div class="admin-sidebar__logo-icon">T</div>
<span class="admin-sidebar__logo-text">Team-API</span>
<img src="/favicon.png" alt="Logo" class="admin-sidebar__logo-icon" />
<span class="admin-sidebar__logo-text">{{ siteName || 'Team-API' }}</span>
</div>
<div class="admin-sidebar__menu">
<div v-for="group in menuGroups" :key="group.key">
@@ -775,17 +777,10 @@ onUnmounted(() => {
.admin-sidebar__logo-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: var(--ta-primary-gradient);
border-radius: 8px;
color: #fff;
font-size: 16px;
font-weight: 700;
box-shadow: 0 4px 16px rgba(13, 148, 136, 0.4);
object-fit: contain;
}
.admin-sidebar__logo-text {
+6 -4
View File
@@ -7,12 +7,14 @@ import { useAuthStore } from '@/stores/auth'
import { extractApiError } from '@/utils/request'
import request from '@/utils/request'
import SlideCaptcha from '@/components/SlideCaptcha.vue'
import { useSiteName } from '@/composables/useSiteName'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const formRef = ref<FormInstance | null>(null)
const loading = ref(false)
const { siteName } = useSiteName()
const form = reactive({
username: '',
@@ -94,8 +96,8 @@ async function handle2FAVerify() {
<div class="login-orb login-orb--3" />
<div class="login-brand__content">
<img src="/favicon.png" alt="Team-API" class="login-brand__logo" />
<h1 class="login-brand__title">Team-API</h1>
<img src="/favicon.png" :alt="siteName" class="login-brand__logo" />
<h1 class="login-brand__title">{{ siteName }}</h1>
<p class="login-brand__desc">多租户 大模型 API 网关管理平台</p>
<div class="login-brand__features">
@@ -132,8 +134,8 @@ async function handle2FAVerify() {
<div class="login-form-wrapper">
<!-- Mobile Brand (hidden on desktop) -->
<div class="login-mobile-brand">
<img src="/favicon.png" alt="Team-API" class="login-brand__logo" />
<span class="login-mobile-brand__name">Team-API</span>
<img src="/favicon.png" :alt="siteName" class="login-brand__logo" />
<span class="login-mobile-brand__name">{{ siteName }}</span>
</div>
<div class="login-form glass animate-slideUp">
@@ -12,7 +12,7 @@ const values = useFormValues()
<AFormItem label="站点名称">
<AInput v-model="values['site_name']" placeholder="Team-API" />
</AFormItem>
<AFormItem label="站点描述">
<AFormItem label="站点描述" class="field-full">
<AInput v-model="values['site_description']" placeholder="简短描述平台用途" />
</AFormItem>
<AFormItem label="租户控制台地址" help="用于生成邀请链接等,如 https://console.example.com" class="field-full">
@@ -29,10 +29,10 @@
<!-- Logo/Brand -->
<div class="mb-8 text-center">
<div class="flex items-center gap-3 justify-center">
<img src="/favicon.png" alt="Team-API" class="h-10 w-10 rounded-lg object-contain" />
<h1 class="text-gradient text-2xl font-bold leading-tight">Team API</h1>
<img src="/favicon.png" :alt="siteName" class="h-10 w-10 rounded-lg object-contain" />
<h1 class="text-gradient text-2xl font-bold leading-tight">{{ siteName }}</h1>
</div>
<p class="mt-3 text-sm text-gray-500">开源自托管的多租户 大模型 API 网关平台</p>
<p class="mt-3 text-sm text-gray-500">{{ publicSettings.site_description || '开源自托管的多租户 大模型 API 网关平台' }}</p>
</div>
<!-- Card Container -->
@@ -59,7 +59,10 @@ import { ref, onMounted, onBeforeUnmount } from 'vue'
import Icon from '../common/Icon.vue'
import AnnouncementBanner from '../common/AnnouncementBanner.vue'
import request from '@/utils/request'
import { usePublicSettings } from '@/composables/usePublicSettings'
const { settings: publicSettings } = usePublicSettings()
const siteName = publicSettings.value.site_name || 'Team-API'
const announcements = ref<any[]>([])
let timer: ReturnType<typeof setInterval> | null = null
+4 -2
View File
@@ -3,13 +3,14 @@ import { useSeoMeta, useHead } from '@unhead/vue'
interface SeoOptions {
title: string
description: string
siteName?: string
keywords?: string
canonicalUrl?: string
ogImage?: string
ogType?: 'website' | 'article' | 'profile' | 'book'
}
const SITE_NAME = 'Team-API'
const DEFAULT_SITE_NAME = 'Team-API'
const DEFAULT_OG_IMAGE = '/og-image.png'
/**
@@ -20,6 +21,7 @@ export function useSeo(options: SeoOptions) {
const {
title,
description,
siteName = DEFAULT_SITE_NAME,
keywords,
canonicalUrl,
ogImage = DEFAULT_OG_IMAGE,
@@ -33,7 +35,7 @@ export function useSeo(options: SeoOptions) {
ogDescription: description,
keywords,
ogType,
ogSiteName: SITE_NAME,
ogSiteName: siteName,
ogLocale: 'zh_CN',
ogImage,
twitterCard: 'summary_large_image',
+4 -1
View File
@@ -3,6 +3,7 @@ import type { RouteRecordRaw } from 'vue-router'
import tenantRoutes from './tenant'
import { useTenantAuthStore } from '@/stores/tenant-auth'
import { shouldRefresh, getRefreshToken } from '@/utils/request'
import { usePublicSettings } from '@/composables/usePublicSettings'
const routes: RouteRecordRaw[] = [
{
@@ -33,7 +34,9 @@ router.beforeEach((to) => {
tenantAuthStore.loadFromStorage()
if (to.meta.title) {
document.title = to.name === 'TenantHome' ? to.meta.title : `${to.meta.title} — Team-API`
const { settings: publicSettings } = usePublicSettings()
const siteName = publicSettings.value.site_name || 'Team-API'
document.title = to.name === 'TenantHome' ? `${siteName} — 企业级多租户大模型 API 网关平台 | 开源自托管` : `${to.meta.title} — ${siteName}`
}
// Auth pages — always allow
+3
View File
@@ -177,6 +177,9 @@ onMounted(async () => {
}
await fetchPublicSettings()
if (publicSettings.site_name && route.meta.title) {
document.title = `${pageTitle.value} — ${publicSettings.site_name}`
}
if (publicSettings.demo_mode) {
demoMessage.value = publicSettings.demo_message || '演示环境,数据不可修改'
mountWatermark(document.body)
+1 -1
View File
@@ -5,7 +5,7 @@ import { useTenantAuthStore } from '@/stores/tenant-auth'
import { usePublicSettings } from '@/composables/usePublicSettings'
import AuthLayout from '@/components/layout/AuthLayout.vue'
import SlideCaptcha from '@/components/common/SlideCaptcha.vue'
import Turnstile from '@/components/common/Turnstile.vue
import Turnstile from '@/components/common/Turnstile.vue'
import Icon from '@/components/common/Icon.vue'
import request from '@/utils/request'
import { extractApiError } from '@/utils/request'
+15 -10
View File
@@ -5,8 +5,8 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center gap-2.5">
<img src="/favicon.png" alt="Team-API" class="h-8 w-8 rounded-lg" />
<span class="text-lg font-bold tracking-tight" :class="isScrolled ? 'text-gray-900' : 'text-white'">Team-API</span>
<img src="/favicon.png" :alt="siteName" class="h-8 w-8 rounded-lg" />
<span class="text-lg font-bold tracking-tight" :class="isScrolled ? 'text-gray-900' : 'text-white'">{{ siteName }}</span>
</div>
<div class="flex items-center gap-2">
<a href="https://github.com/qianfree/team-api" target="_blank" rel="noopener noreferrer"
@@ -240,7 +240,7 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-2">
<img src="/favicon.png" alt="Team-API" class="h-5 w-5 rounded" />
<img src="/favicon.png" :alt="siteName" class="h-5 w-5 rounded" />
<span class="text-xs text-gray-600">&copy; 2026 qianfree. Released under AGPL-3.0.</span>
</div>
<a href="https://github.com/qianfree/team-api" target="_blank" rel="noopener noreferrer"
@@ -257,6 +257,10 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useSeo } from '@/composables/useSeo'
import { useHead } from '@unhead/vue'
import { usePublicSettings } from '@/composables/usePublicSettings'
const { settings: publicSettings } = usePublicSettings()
const siteName = publicSettings.value.site_name || 'Team-API'
// Nav scroll state
const isScrolled = ref(false)
@@ -275,8 +279,9 @@ const activeScenario = ref(0)
// SEO
useSeo({
title: 'Team-API — 一个 API 接入所有大模型 | 企业级多租户 AI 网关',
description: 'Team-API 是开源自托管的企业级多租户大模型 API 网关平台。聚合 OpenAI、Claude、Gemini、DeepSeek 等 40+ 供应商,内置计费引擎、团队管理、用量审计与智能渠道调度。完全兼容 OpenAI SDK,只需修改 base_url 即可接入。',
title: `${siteName} — 一个 API 接入所有大模型 | 企业级多租户 AI 网关`,
description: publicSettings.value.site_description || `${siteName} 是开源自托管的企业级多租户大模型 API 网关平台。聚合 OpenAI、Claude、Gemini、DeepSeek 等 40+ 供应商,内置计费引擎、团队管理、用量审计与智能渠道调度。完全兼容 OpenAI SDK,只需修改 base_url 即可接入。`,
siteName,
keywords: 'Team-API, 大模型网关, API Gateway, 多租户, OpenAI, Claude, Gemini, DeepSeek, 阿里云百炼, 百度文心, 腾讯混元, 智谱AI, AI代理, LLM Gateway, 开源, 自托管, 计费引擎, 团队管理, API管理, SSE流式, 渠道调度',
canonicalUrl: 'https://team-api.net/',
})
@@ -288,8 +293,8 @@ useHead({
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'Team-API',
description: '企业级多租户大模型 API 网关平台,聚合 40+ 大模型供应商,内置计费引擎、团队管理、用量审计与渠道调度。',
name: siteName,
description: publicSettings.value.site_description || '企业级多租户大模型 API 网关平台,聚合 40+ 大模型供应商,内置计费引擎、团队管理、用量审计与渠道调度。',
url: 'https://github.com/qianfree/team-api',
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Linux, macOS, Windows',
@@ -311,7 +316,7 @@ useHead({
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Team-API',
name: siteName,
url: 'https://github.com/qianfree/team-api',
logo: 'https://team-api.net/favicon.png',
sameAs: ['https://github.com/qianfree/team-api'],
@@ -322,9 +327,9 @@ useHead({
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Team-API',
name: siteName,
url: 'https://team-api.net/',
description: '企业级多租户大模型 API 网关平台',
description: publicSettings.value.site_description || '企业级多租户大模型 API 网关平台',
potentialAction: {
'@type': 'SearchAction',
target: 'https://github.com/qianfree/team-api/search?q={search_term_string}',