mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-05 16:00:21 +08:00
feat: squash merge all changes from develop-0.1.75
Squash of 124 commits from the legacy develop branch (develop-0.1.75) onto a clean v0.1.75 upstream base, to simplify future upstream merges. Key changes included: - Refactor scope-level rate limiting to model-level rate limiting - Antigravity gateway service improvements (smart retry, error policy) - Digest session store (flat cache replacing Trie-based store) - Client disconnect detection during streaming - Gemini messages compatibility service enhancements - Scheduler shuffle for thundering herd prevention - Session hash generation improvements - Frontend customizations (WeChat service, HomeView, etc.) - Ops monitoring scope cleanup
This commit is contained in:
@@ -376,7 +376,6 @@ export interface PlatformAvailability {
|
||||
total_accounts: number
|
||||
available_count: number
|
||||
rate_limit_count: number
|
||||
scope_rate_limit_count?: Record<string, number>
|
||||
error_count: number
|
||||
}
|
||||
|
||||
@@ -387,7 +386,6 @@ export interface GroupAvailability {
|
||||
total_accounts: number
|
||||
available_count: number
|
||||
rate_limit_count: number
|
||||
scope_rate_limit_count?: Record<string, number>
|
||||
error_count: number
|
||||
}
|
||||
|
||||
@@ -402,7 +400,6 @@ export interface AccountAvailability {
|
||||
is_rate_limited: boolean
|
||||
rate_limit_reset_at?: string
|
||||
rate_limit_remaining_sec?: number
|
||||
scope_rate_limits?: Record<string, number>
|
||||
is_overloaded: boolean
|
||||
overload_until?: string
|
||||
overload_remaining_sec?: number
|
||||
|
||||
@@ -76,26 +76,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scope Rate Limit Indicators (Antigravity) -->
|
||||
<template v-if="activeScopeRateLimits.length > 0">
|
||||
<div v-for="item in activeScopeRateLimits" :key="item.scope" class="group relative">
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded bg-orange-100 px-1.5 py-0.5 text-xs font-medium text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
||||
>
|
||||
<Icon name="exclamationTriangle" size="xs" :stroke-width="2" />
|
||||
{{ formatScopeName(item.scope) }}
|
||||
</span>
|
||||
<!-- Tooltip -->
|
||||
<div
|
||||
class="pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 whitespace-nowrap rounded bg-gray-900 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-700"
|
||||
>
|
||||
{{ t('admin.accounts.status.scopeRateLimitedUntil', { scope: formatScopeName(item.scope), time: formatTime(item.reset_at) }) }}
|
||||
<div
|
||||
class="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-700" ></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Model Rate Limit Indicators (Antigravity OAuth Smart Retry) -->
|
||||
<template v-if="activeModelRateLimits.length > 0">
|
||||
<div v-for="item in activeModelRateLimits" :key="item.model" class="group relative">
|
||||
@@ -160,16 +140,6 @@ const isRateLimited = computed(() => {
|
||||
return new Date(props.account.rate_limit_reset_at) > new Date()
|
||||
})
|
||||
|
||||
// Computed: active scope rate limits (Antigravity)
|
||||
const activeScopeRateLimits = computed(() => {
|
||||
const scopeLimits = props.account.scope_rate_limits
|
||||
if (!scopeLimits) return []
|
||||
const now = new Date()
|
||||
return Object.entries(scopeLimits)
|
||||
.filter(([, info]) => new Date(info.reset_at) > now)
|
||||
.map(([scope, info]) => ({ scope, reset_at: info.reset_at }))
|
||||
})
|
||||
|
||||
// Computed: active model rate limits (Antigravity OAuth Smart Retry)
|
||||
const activeModelRateLimits = computed(() => {
|
||||
const modelLimits = (props.account.extra as Record<string, unknown> | undefined)?.model_rate_limits as
|
||||
|
||||
@@ -1038,10 +1038,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Custom Error Codes Section -->
|
||||
<div
|
||||
v-if="form.platform !== 'gemini'"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.customErrorCodes') }}</label>
|
||||
|
||||
104
frontend/src/components/common/WechatServiceButton.vue
Normal file
104
frontend/src/components/common/WechatServiceButton.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<!-- 悬浮按钮 - 使用主题色 -->
|
||||
<button
|
||||
@click="showModal = true"
|
||||
class="fixed bottom-6 right-6 z-50 flex items-center gap-2 rounded-full bg-gradient-to-r from-primary-500 to-primary-600 px-4 py-3 text-white shadow-lg shadow-primary-500/25 transition-all hover:from-primary-600 hover:to-primary-700 hover:shadow-xl hover:shadow-primary-500/30"
|
||||
>
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 01.213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.328.328 0 00.186-.059l2.114-1.225a.87.87 0 01.415-.106.807.807 0 01.213.026 10.07 10.07 0 002.696.37c.262 0 .52-.011.776-.028a5.91 5.91 0 01-.193-1.479c0-3.644 3.374-6.6 7.536-6.6.262 0 .52.011.776.028-.628-3.513-4.27-6.472-8.885-6.472zM5.785 5.97a1.1 1.1 0 110 2.2 1.1 1.1 0 010-2.2zm5.813 0a1.1 1.1 0 110 2.2 1.1 1.1 0 010-2.2zm5.192 2.642c-3.703 0-6.71 2.567-6.71 5.73 0 3.163 3.007 5.73 6.71 5.73a7.9 7.9 0 002.126-.288.644.644 0 01.17-.022.69.69 0 01.329.085l1.672.97a.262.262 0 00.147.046c.128 0 .23-.104.23-.233a.403.403 0 00-.038-.168l-.309-1.17a.468.468 0 01.168-.527c1.449-1.065 2.374-2.643 2.374-4.423 0-3.163-3.007-5.73-6.71-5.73h-.159zm-2.434 3.34a.88.88 0 110 1.76.88.88 0 010-1.76zm4.868 0a.88.88 0 110 1.76.88.88 0 010-1.76z"/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium">客服</span>
|
||||
</button>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="showModal"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm"
|
||||
@click.self="showModal = false"
|
||||
>
|
||||
<Transition name="scale">
|
||||
<div
|
||||
v-if="showModal"
|
||||
class="relative w-full max-w-sm rounded-2xl bg-white p-6 shadow-2xl dark:bg-dark-700"
|
||||
>
|
||||
<!-- 关闭按钮 -->
|
||||
<button
|
||||
@click="showModal = false"
|
||||
class="absolute right-4 top-4 text-gray-400 transition-colors hover:text-gray-600 dark:text-dark-400 dark:hover:text-dark-200"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 标题 -->
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-gradient-to-br from-primary-500 to-primary-600">
|
||||
<svg class="h-6 w-6 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 01.213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.328.328 0 00.186-.059l2.114-1.225a.87.87 0 01.415-.106.807.807 0 01.213.026 10.07 10.07 0 002.696.37c.262 0 .52-.011.776-.028a5.91 5.91 0 01-.193-1.479c0-3.644 3.374-6.6 7.536-6.6.262 0 .52.011.776.028-.628-3.513-4.27-6.472-8.885-6.472zM5.785 5.97a1.1 1.1 0 110 2.2 1.1 1.1 0 010-2.2zm5.813 0a1.1 1.1 0 110 2.2 1.1 1.1 0 010-2.2zm5.192 2.642c-3.703 0-6.71 2.567-6.71 5.73 0 3.163 3.007 5.73 6.71 5.73a7.9 7.9 0 002.126-.288.644.644 0 01.17-.022.69.69 0 01.329.085l1.672.97a.262.262 0 00.147.046c.128 0 .23-.104.23-.233a.403.403 0 00-.038-.168l-.309-1.17a.468.468 0 01.168-.527c1.449-1.065 2.374-2.643 2.374-4.423 0-3.163-3.007-5.73-6.71-5.73h-.159zm-2.434 3.34a.88.88 0 110 1.76.88.88 0 010-1.76zm4.868 0a.88.88 0 110 1.76.88.88 0 010-1.76z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">联系客服</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-dark-400">扫码添加好友</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码卡片 -->
|
||||
<div class="mb-4 overflow-hidden rounded-xl border border-primary-100 bg-gradient-to-br from-primary-50 to-white p-3 dark:border-primary-800/30 dark:from-primary-900/10 dark:to-dark-800">
|
||||
<img
|
||||
src="/wechat-qr.jpg"
|
||||
alt="微信二维码"
|
||||
class="w-full rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 提示文字 -->
|
||||
<div class="text-center">
|
||||
<p class="mb-2 text-sm font-medium text-primary-600 dark:text-primary-400">
|
||||
微信扫码添加客服
|
||||
</p>
|
||||
<p class="flex items-center justify-center gap-1 text-xs text-gray-500 dark:text-dark-400">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
工作时间:周一至周五 9:00-18:00
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const showModal = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.scale-enter-from,
|
||||
.scale-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
@@ -121,23 +121,6 @@
|
||||
<Icon name="key" size="sm" />
|
||||
{{ t('nav.apiKeys') }}
|
||||
</router-link>
|
||||
|
||||
<a
|
||||
href="https://github.com/Wei-Shaw/sub2api"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@click="closeDropdown"
|
||||
class="dropdown-item"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('nav.github') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Contact Support (only show if configured) -->
|
||||
|
||||
@@ -1356,7 +1356,6 @@ export default {
|
||||
overloaded: 'Overloaded',
|
||||
tempUnschedulable: 'Temp Unschedulable',
|
||||
rateLimitedUntil: 'Rate limited until {time}',
|
||||
scopeRateLimitedUntil: '{scope} rate limited until {time}',
|
||||
modelRateLimitedUntil: '{model} rate limited until {time}',
|
||||
overloadedUntil: 'Overloaded until {time}',
|
||||
viewTempUnschedDetails: 'View temp unschedulable details'
|
||||
@@ -3059,7 +3058,6 @@ export default {
|
||||
empty: 'No data',
|
||||
queued: 'Queue {count}',
|
||||
rateLimited: 'Rate-limited {count}',
|
||||
scopeRateLimitedTooltip: '{scope} rate-limited ({count} accounts)',
|
||||
errorAccounts: 'Errors {count}',
|
||||
loadFailed: 'Failed to load concurrency data'
|
||||
},
|
||||
|
||||
@@ -1492,7 +1492,6 @@ export default {
|
||||
overloaded: '过载中',
|
||||
tempUnschedulable: '临时不可调度',
|
||||
rateLimitedUntil: '限流中,重置时间:{time}',
|
||||
scopeRateLimitedUntil: '{scope} 限流中,重置时间:{time}',
|
||||
modelRateLimitedUntil: '{model} 限流至 {time}',
|
||||
overloadedUntil: '负载过重,重置时间:{time}',
|
||||
viewTempUnschedDetails: '查看临时不可调度详情'
|
||||
@@ -3232,7 +3231,6 @@ export default {
|
||||
empty: '暂无数据',
|
||||
queued: '队列 {count}',
|
||||
rateLimited: '限流 {count}',
|
||||
scopeRateLimitedTooltip: '{scope} 限流中 ({count} 个账号)',
|
||||
errorAccounts: '异常 {count}',
|
||||
loadFailed: '加载并发数据失败'
|
||||
},
|
||||
|
||||
@@ -591,9 +591,6 @@ export interface Account {
|
||||
temp_unschedulable_until: string | null
|
||||
temp_unschedulable_reason: string | null
|
||||
|
||||
// Antigravity scope 级限流状态
|
||||
scope_rate_limits?: Record<string, { reset_at: string; remaining_sec: number }>
|
||||
|
||||
// Session window fields (5-hour window)
|
||||
session_window_start: string | null
|
||||
session_window_end: string | null
|
||||
|
||||
@@ -122,8 +122,11 @@
|
||||
>
|
||||
{{ siteName }}
|
||||
</h1>
|
||||
<p class="mb-8 text-lg text-gray-600 dark:text-dark-300 md:text-xl">
|
||||
{{ siteSubtitle }}
|
||||
<p class="mb-3 text-xl font-semibold text-primary-600 dark:text-primary-400 md:text-2xl">
|
||||
{{ t('home.heroSubtitle') }}
|
||||
</p>
|
||||
<p class="mb-8 text-base text-gray-600 dark:text-dark-300 md:text-lg">
|
||||
{{ t('home.heroDescription') }}
|
||||
</p>
|
||||
|
||||
<!-- CTA Button -->
|
||||
@@ -177,7 +180,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Feature Tags - Centered -->
|
||||
<div class="mb-12 flex flex-wrap items-center justify-center gap-4 md:gap-6">
|
||||
<div class="mb-16 flex flex-wrap items-center justify-center gap-4 md:gap-6">
|
||||
<div
|
||||
class="inline-flex items-center gap-2.5 rounded-full border border-gray-200/50 bg-white/80 px-5 py-2.5 shadow-sm backdrop-blur-sm dark:border-dark-700/50 dark:bg-dark-800/80"
|
||||
>
|
||||
@@ -204,6 +207,63 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pain Points Section -->
|
||||
<div class="mb-16">
|
||||
<h2 class="mb-8 text-center text-2xl font-bold text-gray-900 dark:text-white md:text-3xl">
|
||||
{{ t('home.painPoints.title') }}
|
||||
</h2>
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<!-- Pain Point 1: Expensive -->
|
||||
<div class="rounded-xl border border-red-200/50 bg-red-50/50 p-5 dark:border-red-900/30 dark:bg-red-950/20">
|
||||
<div class="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-red-100 dark:bg-red-900/30">
|
||||
<svg class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mb-1.5 font-semibold text-gray-900 dark:text-white">{{ t('home.painPoints.items.expensive.title') }}</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-dark-400">{{ t('home.painPoints.items.expensive.desc') }}</p>
|
||||
</div>
|
||||
<!-- Pain Point 2: Complex -->
|
||||
<div class="rounded-xl border border-orange-200/50 bg-orange-50/50 p-5 dark:border-orange-900/30 dark:bg-orange-950/20">
|
||||
<div class="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-orange-100 dark:bg-orange-900/30">
|
||||
<svg class="h-5 w-5 text-orange-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mb-1.5 font-semibold text-gray-900 dark:text-white">{{ t('home.painPoints.items.complex.title') }}</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-dark-400">{{ t('home.painPoints.items.complex.desc') }}</p>
|
||||
</div>
|
||||
<!-- Pain Point 3: Unstable -->
|
||||
<div class="rounded-xl border border-yellow-200/50 bg-yellow-50/50 p-5 dark:border-yellow-900/30 dark:bg-yellow-950/20">
|
||||
<div class="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-yellow-100 dark:bg-yellow-900/30">
|
||||
<svg class="h-5 w-5 text-yellow-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mb-1.5 font-semibold text-gray-900 dark:text-white">{{ t('home.painPoints.items.unstable.title') }}</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-dark-400">{{ t('home.painPoints.items.unstable.desc') }}</p>
|
||||
</div>
|
||||
<!-- Pain Point 4: No Control -->
|
||||
<div class="rounded-xl border border-gray-200/50 bg-gray-50/50 p-5 dark:border-dark-700/50 dark:bg-dark-800/50">
|
||||
<div class="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-dark-700">
|
||||
<svg class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="mb-1.5 font-semibold text-gray-900 dark:text-white">{{ t('home.painPoints.items.noControl.title') }}</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-dark-400">{{ t('home.painPoints.items.noControl.desc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Solutions Section Title -->
|
||||
<div class="mb-8 text-center">
|
||||
<h2 class="mb-2 text-2xl font-bold text-gray-900 dark:text-white md:text-3xl">
|
||||
{{ t('home.solutions.title') }}
|
||||
</h2>
|
||||
<p class="text-gray-600 dark:text-dark-400">{{ t('home.solutions.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Features Grid -->
|
||||
<div class="mb-12 grid gap-6 md:grid-cols-3">
|
||||
<!-- Feature 1: Unified Gateway -->
|
||||
@@ -369,6 +429,77 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comparison Table -->
|
||||
<div class="mb-16">
|
||||
<h2 class="mb-8 text-center text-2xl font-bold text-gray-900 dark:text-white md:text-3xl">
|
||||
{{ t('home.comparison.title') }}
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full rounded-xl border border-gray-200/50 bg-white/60 backdrop-blur-sm dark:border-dark-700/50 dark:bg-dark-800/60">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200/50 dark:border-dark-700/50">
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold text-gray-900 dark:text-white">{{ t('home.comparison.headers.feature') }}</th>
|
||||
<th class="px-6 py-4 text-center text-sm font-semibold text-gray-500 dark:text-dark-400">{{ t('home.comparison.headers.official') }}</th>
|
||||
<th class="px-6 py-4 text-center text-sm font-semibold text-primary-600 dark:text-primary-400">{{ t('home.comparison.headers.us') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200/50 dark:divide-dark-700/50">
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-white">{{ t('home.comparison.items.pricing.feature') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm text-gray-500 dark:text-dark-400">{{ t('home.comparison.items.pricing.official') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-medium text-primary-600 dark:text-primary-400">{{ t('home.comparison.items.pricing.us') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-white">{{ t('home.comparison.items.models.feature') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm text-gray-500 dark:text-dark-400">{{ t('home.comparison.items.models.official') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-medium text-primary-600 dark:text-primary-400">{{ t('home.comparison.items.models.us') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-white">{{ t('home.comparison.items.management.feature') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm text-gray-500 dark:text-dark-400">{{ t('home.comparison.items.management.official') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-medium text-primary-600 dark:text-primary-400">{{ t('home.comparison.items.management.us') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-white">{{ t('home.comparison.items.stability.feature') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm text-gray-500 dark:text-dark-400">{{ t('home.comparison.items.stability.official') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-medium text-primary-600 dark:text-primary-400">{{ t('home.comparison.items.stability.us') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-white">{{ t('home.comparison.items.control.feature') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm text-gray-500 dark:text-dark-400">{{ t('home.comparison.items.control.official') }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-medium text-primary-600 dark:text-primary-400">{{ t('home.comparison.items.control.us') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<div class="mb-8 rounded-2xl bg-gradient-to-r from-primary-500 to-primary-600 p-8 text-center shadow-xl shadow-primary-500/20 md:p-12">
|
||||
<h2 class="mb-3 text-2xl font-bold text-white md:text-3xl">
|
||||
{{ t('home.cta.title') }}
|
||||
</h2>
|
||||
<p class="mb-6 text-primary-100">
|
||||
{{ t('home.cta.description') }}
|
||||
</p>
|
||||
<router-link
|
||||
v-if="!isAuthenticated"
|
||||
to="/register"
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white px-8 py-3 font-semibold text-primary-600 shadow-lg transition-all hover:bg-gray-50 hover:shadow-xl"
|
||||
>
|
||||
{{ t('home.cta.button') }}
|
||||
<Icon name="arrowRight" size="md" :stroke-width="2" />
|
||||
</router-link>
|
||||
<router-link
|
||||
v-else
|
||||
:to="dashboardPath"
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white px-8 py-3 font-semibold text-primary-600 shadow-lg transition-all hover:bg-gray-50 hover:shadow-xl"
|
||||
>
|
||||
{{ t('home.goToDashboard') }}
|
||||
<Icon name="arrowRight" size="md" :stroke-width="2" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -380,27 +511,20 @@
|
||||
<p class="text-sm text-gray-500 dark:text-dark-400">
|
||||
© {{ currentYear }} {{ siteName }}. {{ t('home.footer.allRightsReserved') }}
|
||||
</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
v-if="docUrl"
|
||||
:href="docUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-dark-400 dark:hover:text-white"
|
||||
>
|
||||
{{ t('home.docs') }}
|
||||
</a>
|
||||
<a
|
||||
:href="githubUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-dark-400 dark:hover:text-white"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
v-if="docUrl"
|
||||
:href="docUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-dark-400 dark:hover:text-white"
|
||||
>
|
||||
{{ t('home.docs') }}
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- 微信客服悬浮按钮 -->
|
||||
<WechatServiceButton />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -410,6 +534,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore, useAppStore } from '@/stores'
|
||||
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import WechatServiceButton from '@/components/common/WechatServiceButton.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -419,7 +544,6 @@ const appStore = useAppStore()
|
||||
// Site settings - directly from appStore (already initialized from injected config)
|
||||
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
|
||||
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
|
||||
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
|
||||
const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '')
|
||||
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
|
||||
|
||||
@@ -432,9 +556,6 @@ const isHomeContentUrl = computed(() => {
|
||||
// Theme
|
||||
const isDark = ref(document.documentElement.classList.contains('dark'))
|
||||
|
||||
// GitHub URL
|
||||
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
|
||||
|
||||
// Auth state
|
||||
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
||||
const isAdmin = computed(() => authStore.isAdmin)
|
||||
|
||||
@@ -56,7 +56,6 @@ interface SummaryRow {
|
||||
total_accounts: number
|
||||
available_accounts: number
|
||||
rate_limited_accounts: number
|
||||
scope_rate_limit_count?: Record<string, number>
|
||||
error_accounts: number
|
||||
// 并发统计
|
||||
total_concurrency: number
|
||||
@@ -122,7 +121,6 @@ const platformRows = computed((): SummaryRow[] => {
|
||||
total_accounts: totalAccounts,
|
||||
available_accounts: availableAccounts,
|
||||
rate_limited_accounts: safeNumber(avail.rate_limit_count),
|
||||
scope_rate_limit_count: avail.scope_rate_limit_count,
|
||||
error_accounts: safeNumber(avail.error_count),
|
||||
total_concurrency: totalConcurrency,
|
||||
used_concurrency: usedConcurrency,
|
||||
@@ -162,7 +160,6 @@ const groupRows = computed((): SummaryRow[] => {
|
||||
total_accounts: totalAccounts,
|
||||
available_accounts: availableAccounts,
|
||||
rate_limited_accounts: safeNumber(avail.rate_limit_count),
|
||||
scope_rate_limit_count: avail.scope_rate_limit_count,
|
||||
error_accounts: safeNumber(avail.error_count),
|
||||
total_concurrency: totalConcurrency,
|
||||
used_concurrency: usedConcurrency,
|
||||
@@ -329,15 +326,6 @@ function formatDuration(seconds: number): string {
|
||||
return `${hours}h`
|
||||
}
|
||||
|
||||
function formatScopeName(scope: string): string {
|
||||
const names: Record<string, string> = {
|
||||
claude: 'Claude',
|
||||
gemini_text: 'Gemini',
|
||||
gemini_image: 'Image'
|
||||
}
|
||||
return names[scope] || scope
|
||||
}
|
||||
|
||||
watch(
|
||||
() => realtimeEnabled.value,
|
||||
async (enabled) => {
|
||||
@@ -505,18 +493,6 @@ watch(
|
||||
{{ t('admin.ops.concurrency.rateLimited', { count: row.rate_limited_accounts }) }}
|
||||
</span>
|
||||
|
||||
<!-- Scope 限流 (仅 Antigravity) -->
|
||||
<template v-if="row.scope_rate_limit_count && Object.keys(row.scope_rate_limit_count).length > 0">
|
||||
<span
|
||||
v-for="(count, scope) in row.scope_rate_limit_count"
|
||||
:key="scope"
|
||||
class="rounded-full bg-orange-100 px-1.5 py-0.5 font-semibold text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
||||
:title="t('admin.ops.concurrency.scopeRateLimitedTooltip', { scope, count })"
|
||||
>
|
||||
{{ formatScopeName(scope as string) }} {{ count }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 异常账号 -->
|
||||
<span
|
||||
v-if="row.error_accounts > 0"
|
||||
|
||||
Reference in New Issue
Block a user