improve:网页中使用配置的系统名称和介绍
This commit is contained in:
1 parent
d5ae4f1ff2
commit
8dc9266c14
10 files changed
+53
-35
No files matched your search
@@ -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') {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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">© 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}',
|
||||
|
||||
Reference in new issue
Block a user