Merge branch 'main' into um-5

This commit is contained in:
Feng Yue
2025-08-25 17:19:24 +08:00
74 changed files with 9466 additions and 1901 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div class="gradient-bg min-h-screen p-4 md:p-6">
<div class="min-h-screen p-4 md:p-6" :class="isDarkMode ? 'gradient-bg-dark' : 'gradient-bg'">
<!-- 顶部导航 -->
<div class="glass-strong mb-6 rounded-3xl p-4 shadow-xl md:mb-8 md:p-6">
<div class="flex flex-col items-center justify-between gap-4 md:flex-row">
@@ -9,7 +9,18 @@
:subtitle="currentTab === 'stats' ? 'API Key 使用统计' : '使用教程'"
:title="oemSettings.siteName"
/>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2 md:gap-4">
<!-- 主题切换按钮 -->
<div class="flex items-center">
<ThemeToggle mode="dropdown" />
</div>
<!-- 分隔线 -->
<div
class="h-8 w-px bg-gradient-to-b from-transparent via-gray-300 to-transparent opacity-50 dark:via-gray-600"
/>
<!-- 管理后台按钮 -->
<router-link
class="user-login-button flex items-center gap-2 rounded-xl px-3 py-2 text-white transition-all duration-300 md:px-4 md:py-2"
to="/user-login"
@@ -18,11 +29,11 @@
<span class="text-xs font-medium md:text-sm">用户登录</span>
</router-link>
<router-link
class="admin-button flex items-center gap-2 rounded-xl px-3 py-2 text-white transition-all duration-300 md:px-4 md:py-2"
class="admin-button-refined flex items-center gap-2 rounded-2xl px-4 py-2 transition-all duration-300 md:px-5 md:py-2.5"
to="/dashboard"
>
<i class="fas fa-cog text-sm" />
<span class="text-xs font-medium md:text-sm">管理后台</span>
<i class="fas fa-shield-alt text-sm md:text-base" />
<span class="text-xs font-semibold tracking-wide md:text-sm">管理后台</span>
</router-link>
</div>
</div>
@@ -60,7 +71,7 @@
<!-- 错误提示 -->
<div v-if="error" class="mb-6 md:mb-8">
<div
class="rounded-xl border border-red-500/30 bg-red-500/20 p-3 text-sm text-red-800 backdrop-blur-sm md:p-4 md:text-base"
class="rounded-xl border border-red-500/30 bg-red-500/20 p-3 text-sm text-red-800 backdrop-blur-sm dark:border-red-500/20 dark:bg-red-500/10 dark:text-red-200 md:p-4 md:text-base"
>
<i class="fas fa-exclamation-triangle mr-2" />
{{ error }}
@@ -71,13 +82,15 @@
<div v-if="statsData" class="fade-in">
<div class="glass-strong rounded-3xl p-4 shadow-xl md:p-6">
<!-- 时间范围选择器 -->
<div class="mb-4 border-b border-gray-200 pb-4 md:mb-6 md:pb-6">
<div class="mb-4 border-b border-gray-200 pb-4 dark:border-gray-700 md:mb-6 md:pb-6">
<div
class="flex flex-col items-start justify-between gap-3 md:flex-row md:items-center md:gap-4"
>
<div class="flex items-center gap-2 md:gap-3">
<i class="fas fa-clock text-base text-blue-500 md:text-lg" />
<span class="text-base font-medium text-gray-700 md:text-lg">统计时间范围</span>
<span class="text-base font-medium text-gray-700 dark:text-gray-200 md:text-lg"
>统计时间范围</span
>
</div>
<div class="flex w-full gap-2 md:w-auto">
<button
@@ -127,11 +140,13 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useApiStatsStore } from '@/stores/apistats'
import { useThemeStore } from '@/stores/theme'
import LogoTitle from '@/components/common/LogoTitle.vue'
import ThemeToggle from '@/components/common/ThemeToggle.vue'
import ApiKeyInput from '@/components/apistats/ApiKeyInput.vue'
import StatsOverview from '@/components/apistats/StatsOverview.vue'
import TokenDistribution from '@/components/apistats/TokenDistribution.vue'
@@ -141,10 +156,14 @@ import TutorialView from './TutorialView.vue'
const route = useRoute()
const apiStatsStore = useApiStatsStore()
const themeStore = useThemeStore()
// 当前标签页
const currentTab = ref('stats')
// 主题相关
const isDarkMode = computed(() => themeStore.isDarkMode)
const {
apiKey,
apiId,
@@ -179,6 +198,9 @@ const handleKeyDown = (event) => {
onMounted(() => {
console.log('API Stats Page loaded')
// 初始化主题(因为该页面不在 MainLayout 内)
themeStore.initTheme()
// 加载 OEM 设置
loadOemSettings()
@@ -224,6 +246,14 @@ watch(apiKey, (newValue) => {
position: relative;
}
/* 暗色模式的渐变背景 */
.gradient-bg-dark {
background: linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%);
background-attachment: fixed;
min-height: 100vh;
position: relative;
}
.gradient-bg::before {
content: '';
position: fixed;
@@ -239,11 +269,27 @@ watch(apiKey, (newValue) => {
z-index: 0;
}
/* 玻璃态效果 */
/* 暗色模式的背景覆盖 */
.gradient-bg-dark::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(100, 116, 139, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(71, 85, 105, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(30, 41, 59, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
/* 玻璃态效果 - 使用CSS变量 */
.glass-strong {
background: rgba(255, 255, 255, 0.95);
background: var(--glass-strong-color);
backdrop-filter: blur(25px);
border: 1px solid rgba(255, 255, 255, 0.3);
border: 1px solid var(--border-color);
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.05),
@@ -252,6 +298,14 @@ watch(apiKey, (newValue) => {
z-index: 1;
}
/* 暗色模式的玻璃态效果 */
:global(.dark) .glass-strong {
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.7),
0 0 0 1px rgba(55, 65, 81, 0.3),
inset 0 1px 0 rgba(75, 85, 99, 0.2);
}
/* 标题渐变 */
.header-title {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -296,38 +350,76 @@ watch(apiKey, (newValue) => {
left: 100%;
}
/* 管理后台按钮 */
.admin-button {
/* 管理后台按钮 - 精致版本 */
.admin-button-refined {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
text-decoration: none;
box-shadow:
0 4px 6px -1px rgba(102, 126, 234, 0.3),
0 2px 4px -1px rgba(102, 126, 234, 0.1);
0 4px 12px rgba(102, 126, 234, 0.25),
inset 0 1px 1px rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
font-weight: 600;
}
.admin-button::before {
/* 暗色模式下的管理后台按钮 */
:global(.dark) .admin-button-refined {
background: rgba(55, 65, 81, 0.8);
border: 1px solid rgba(107, 114, 128, 0.4);
color: #f3f4f6;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.3),
inset 0 1px 1px rgba(255, 255, 255, 0.05);
}
.admin-button-refined::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.admin-button:hover {
transform: translateY(-2px);
.admin-button-refined:hover {
transform: translateY(-2px) scale(1.02);
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
box-shadow:
0 10px 15px -3px rgba(102, 126, 234, 0.4),
0 4px 6px -2px rgba(102, 126, 234, 0.15);
0 8px 20px rgba(118, 75, 162, 0.35),
inset 0 1px 1px rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.4);
color: white;
}
.admin-button:hover::before {
left: 100%;
.admin-button-refined:hover::before {
opacity: 1;
}
/* 暗色模式下的悬停效果 */
:global(.dark) .admin-button-refined:hover {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: rgba(147, 51, 234, 0.4);
box-shadow:
0 8px 20px rgba(102, 126, 234, 0.3),
inset 0 1px 1px rgba(255, 255, 255, 0.1);
color: white;
}
.admin-button-refined:active {
transform: translateY(-1px) scale(1);
}
/* 确保图标和文字在所有模式下都清晰可见 */
.admin-button-refined i,
.admin-button-refined span {
position: relative;
z-index: 1;
}
/* 时间范围按钮 */
@@ -353,12 +445,26 @@ watch(apiKey, (newValue) => {
.period-btn:not(.active) {
color: #374151;
background: transparent;
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(229, 231, 235, 0.5);
}
:global(html.dark) .period-btn:not(.active) {
color: #e5e7eb;
background: rgba(55, 65, 81, 0.4);
border: 1px solid rgba(75, 85, 99, 0.5);
}
.period-btn:not(.active):hover {
background: rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.8);
color: #1f2937;
border-color: rgba(209, 213, 219, 0.8);
}
:global(html.dark) .period-btn:not(.active):hover {
background: rgba(75, 85, 99, 0.6);
color: #ffffff;
border-color: rgba(107, 114, 128, 0.8);
}
/* Tab 胶囊按钮样式 */
@@ -380,6 +486,11 @@ watch(apiKey, (newValue) => {
justify-content: center;
}
/* 暗夜模式下的Tab按钮基础样式 */
:global(html.dark) .tab-pill-button {
color: rgba(209, 213, 219, 0.8);
}
@media (min-width: 768px) {
.tab-pill-button {
padding: 0.625rem 1.25rem;
@@ -392,6 +503,11 @@ watch(apiKey, (newValue) => {
background: rgba(255, 255, 255, 0.1);
}
:global(html.dark) .tab-pill-button:hover {
color: #f3f4f6;
background: rgba(100, 116, 139, 0.2);
}
.tab-pill-button.active {
background: white;
color: #764ba2;
@@ -400,6 +516,14 @@ watch(apiKey, (newValue) => {
0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
:global(html.dark) .tab-pill-button.active {
background: rgba(71, 85, 105, 0.9);
color: #f3f4f6;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.3),
0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.tab-pill-button i {
font-size: 0.875rem;
}