feat: 实现ApiStatsView页面完整国际化

- 集成vue-i18n到ApiStatsView,支持动态语言切换
- 国际化所有用户界面文本:页面标题、按钮、Tab标签、时间选择器
- 实现LogoTitle动态subtitle,根据当前tab显示对应语言的标题
- 添加语言切换组件到页面header,与主题切换并列显示
- 实现教程内容的整体替换机制,支持基于语言的动态组件选择
- 确保用户登录、管理后台、统计查询等核心功能完全本地化
This commit is contained in:
Wangnov
2025-09-08 15:42:23 +08:00
parent 87591365bc
commit 1eadc94592

View File

@@ -6,10 +6,15 @@
<LogoTitle <LogoTitle
:loading="oemLoading" :loading="oemLoading"
:logo-src="oemSettings.siteIconData || oemSettings.siteIcon" :logo-src="oemSettings.siteIconData || oemSettings.siteIcon"
:subtitle="currentTab === 'stats' ? 'API Key 使用统计' : '使用教程'" :subtitle="currentTab === 'stats' ? t('apiStats.title') : t('apiStats.tutorialTitle')"
:title="oemSettings.siteName" :title="oemSettings.siteName"
/> />
<div class="flex items-center gap-2 md:gap-4"> <div class="flex items-center gap-2 md:gap-4">
<!-- 语言切换按钮 -->
<div class="flex items-center">
<LanguageSwitch mode="dropdown" size="medium" />
</div>
<!-- 主题切换按钮 --> <!-- 主题切换按钮 -->
<div class="flex items-center"> <div class="flex items-center">
<ThemeToggle mode="dropdown" /> <ThemeToggle mode="dropdown" />
@@ -28,7 +33,7 @@
to="/user-login" to="/user-login"
> >
<i class="fas fa-user text-sm md:text-base" /> <i class="fas fa-user text-sm md:text-base" />
<span class="text-xs font-semibold tracking-wide md:text-sm">用户登录</span> <span class="text-xs font-semibold tracking-wide md:text-sm">{{ t('apiStats.userLogin') }}</span>
</router-link> </router-link>
<!-- 管理后台按钮 --> <!-- 管理后台按钮 -->
<router-link <router-link
@@ -37,7 +42,7 @@
to="/dashboard" to="/dashboard"
> >
<i class="fas fa-shield-alt text-sm md:text-base" /> <i class="fas fa-shield-alt text-sm md:text-base" />
<span class="text-xs font-semibold tracking-wide md:text-sm">管理后台</span> <span class="text-xs font-semibold tracking-wide md:text-sm">{{ t('apiStats.adminPanel') }}</span>
</router-link> </router-link>
</div> </div>
</div> </div>
@@ -54,14 +59,14 @@
@click="currentTab = 'stats'" @click="currentTab = 'stats'"
> >
<i class="fas fa-chart-line mr-1 md:mr-2" /> <i class="fas fa-chart-line mr-1 md:mr-2" />
<span class="text-sm md:text-base">统计查询</span> <span class="text-sm md:text-base">{{ t('apiStats.statsQuery') }}</span>
</button> </button>
<button <button
:class="['tab-pill-button', currentTab === 'tutorial' ? 'active' : '']" :class="['tab-pill-button', currentTab === 'tutorial' ? 'active' : '']"
@click="currentTab = 'tutorial'" @click="currentTab = 'tutorial'"
> >
<i class="fas fa-graduation-cap mr-1 md:mr-2" /> <i class="fas fa-graduation-cap mr-1 md:mr-2" />
<span class="text-sm md:text-base">使用教程</span> <span class="text-sm md:text-base">{{ t('apiStats.tutorial') }}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -93,7 +98,7 @@
<div class="flex items-center gap-2 md:gap-3"> <div class="flex items-center gap-2 md:gap-3">
<i class="fas fa-clock text-base text-blue-500 md:text-lg" /> <i class="fas fa-clock text-base text-blue-500 md:text-lg" />
<span class="text-base font-medium text-gray-700 dark:text-gray-200 md:text-lg" <span class="text-base font-medium text-gray-700 dark:text-gray-200 md:text-lg"
>统计时间范围</span >{{ t('apiStats.timeRange') }}</span
> >
</div> </div>
<div class="flex w-full gap-2 md:w-auto"> <div class="flex w-full gap-2 md:w-auto">
@@ -104,7 +109,7 @@
@click="switchPeriod('daily')" @click="switchPeriod('daily')"
> >
<i class="fas fa-calendar-day text-xs md:text-sm" /> <i class="fas fa-calendar-day text-xs md:text-sm" />
今日 {{ t('apiStats.today') }}
</button> </button>
<button <button
class="flex flex-1 items-center justify-center gap-1 px-4 py-2 text-xs font-medium md:flex-none md:gap-2 md:px-6 md:text-sm" class="flex flex-1 items-center justify-center gap-1 px-4 py-2 text-xs font-medium md:flex-none md:gap-2 md:px-6 md:text-sm"
@@ -113,7 +118,7 @@
@click="switchPeriod('monthly')" @click="switchPeriod('monthly')"
> >
<i class="fas fa-calendar-alt text-xs md:text-sm" /> <i class="fas fa-calendar-alt text-xs md:text-sm" />
本月 {{ t('apiStats.thisMonth') }}
</button> </button>
</div> </div>
</div> </div>
@@ -140,7 +145,7 @@
<!-- 教程内容 --> <!-- 教程内容 -->
<div v-if="currentTab === 'tutorial'" class="tab-content"> <div v-if="currentTab === 'tutorial'" class="tab-content">
<div class="glass-strong rounded-3xl shadow-xl"> <div class="glass-strong rounded-3xl shadow-xl">
<TutorialView /> <component :is="currentTutorialComponent" />
</div> </div>
</div> </div>
</div> </div>
@@ -150,21 +155,28 @@
import { ref, onMounted, onUnmounted, watch, computed } from 'vue' import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import { useApiStatsStore } from '@/stores/apistats' import { useApiStatsStore } from '@/stores/apistats'
import { useThemeStore } from '@/stores/theme' import { useThemeStore } from '@/stores/theme'
import { useLocaleStore } from '@/stores/locale'
import LogoTitle from '@/components/common/LogoTitle.vue' import LogoTitle from '@/components/common/LogoTitle.vue'
import ThemeToggle from '@/components/common/ThemeToggle.vue' import ThemeToggle from '@/components/common/ThemeToggle.vue'
import LanguageSwitch from '@/components/common/LanguageSwitch.vue'
import ApiKeyInput from '@/components/apistats/ApiKeyInput.vue' import ApiKeyInput from '@/components/apistats/ApiKeyInput.vue'
import StatsOverview from '@/components/apistats/StatsOverview.vue' import StatsOverview from '@/components/apistats/StatsOverview.vue'
import TokenDistribution from '@/components/apistats/TokenDistribution.vue' import TokenDistribution from '@/components/apistats/TokenDistribution.vue'
import LimitConfig from '@/components/apistats/LimitConfig.vue' import LimitConfig from '@/components/apistats/LimitConfig.vue'
import AggregatedStatsCard from '@/components/apistats/AggregatedStatsCard.vue' import AggregatedStatsCard from '@/components/apistats/AggregatedStatsCard.vue'
import ModelUsageStats from '@/components/apistats/ModelUsageStats.vue' import ModelUsageStats from '@/components/apistats/ModelUsageStats.vue'
import TutorialView from './TutorialView.vue' import TutorialViewZhCn from './TutorialView.vue'
import TutorialViewZhTw from './TutorialView.vue'
import TutorialViewEn from './TutorialView.vue'
const route = useRoute() const route = useRoute()
const { t } = useI18n()
const apiStatsStore = useApiStatsStore() const apiStatsStore = useApiStatsStore()
const themeStore = useThemeStore() const themeStore = useThemeStore()
const localeStore = useLocaleStore()
// 当前标签页 // 当前标签页
const currentTab = ref('stats') const currentTab = ref('stats')
@@ -172,6 +184,17 @@ const currentTab = ref('stats')
// 主题相关 // 主题相关
const isDarkMode = computed(() => themeStore.isDarkMode) const isDarkMode = computed(() => themeStore.isDarkMode)
// 根据当前语言选择教程组件
const currentTutorialComponent = computed(() => {
const locale = localeStore.currentLocale
const components = {
'zh-cn': TutorialViewZhCn,
'zh-tw': TutorialViewZhTw,
'en': TutorialViewEn
}
return components[locale] || TutorialViewZhCn
})
const { const {
apiKey, apiKey,
apiId, apiId,