mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-05-13 22:36:37 +00:00
feat: 全新的Vue3管理后台(admin-spa)和路由重构
🎨 新增功能: - 使用Vue3 + Vite构建的全新管理后台界面 - 支持Tab切换的API统计页面(统计查询/使用教程) - 优雅的胶囊式Tab切换设计 - 同步了PR #106的会话窗口管理功能 - 完整的响应式设计和骨架屏加载状态 🔧 路由调整: - 新版管理后台部署在 /admin-next/ 路径 - 将根路径 / 重定向到 /admin-next/api-stats - 将 /web 页面路由重定向到新版,保留 /web/auth/* 认证路由 - 将 /apiStats 页面路由重定向到新版,保留API端点 🗑️ 清理工作: - 删除旧版 web/admin/ 静态文件 - 删除旧版 web/apiStats/ 静态文件 - 清理相关的文件服务代码 🐛 修复问题: - 修复重定向循环问题 - 修复环境变量配置 - 修复路由404错误 - 优化构建配置 🚀 生成方式:使用 Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
167
web/admin-spa/src/components/dashboard/UsageTrend.vue
Normal file
167
web/admin-spa/src/components/dashboard/UsageTrend.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="glass-strong rounded-3xl p-6 mb-8">
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
|
||||
<h2 class="text-xl font-bold text-gray-800 flex items-center">
|
||||
<i class="fas fa-chart-area mr-2 text-blue-500"></i>
|
||||
使用趋势
|
||||
</h2>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<el-radio-group v-model="granularity" size="small" @change="handleGranularityChange">
|
||||
<el-radio-button label="day">按天</el-radio-button>
|
||||
<el-radio-button label="hour">按小时</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-select v-model="trendPeriod" size="small" style="width: 120px" @change="handlePeriodChange">
|
||||
<el-option :label="`最近${period.days}天`" :value="period.days" v-for="period in periodOptions" :key="period.days" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative" style="height: 300px;">
|
||||
<canvas ref="chartCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { Chart } from 'chart.js/auto'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
import { useChartConfig } from '@/composables/useChartConfig'
|
||||
|
||||
const dashboardStore = useDashboardStore()
|
||||
const chartCanvas = ref(null)
|
||||
let chart = null
|
||||
|
||||
const trendPeriod = ref(7)
|
||||
const granularity = ref('day')
|
||||
|
||||
const periodOptions = [
|
||||
{ days: 1, label: '24小时' },
|
||||
{ days: 7, label: '7天' },
|
||||
{ days: 30, label: '30天' }
|
||||
]
|
||||
|
||||
const createChart = () => {
|
||||
if (!chartCanvas.value || !dashboardStore.trendData.length) return
|
||||
|
||||
if (chart) {
|
||||
chart.destroy()
|
||||
}
|
||||
|
||||
const { getGradient } = useChartConfig()
|
||||
const ctx = chartCanvas.value.getContext('2d')
|
||||
|
||||
const labels = dashboardStore.trendData.map(item => {
|
||||
if (granularity.value === 'hour') {
|
||||
const date = new Date(item.date)
|
||||
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:00`
|
||||
}
|
||||
return item.date
|
||||
})
|
||||
|
||||
chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '请求次数',
|
||||
data: dashboardStore.trendData.map(item => item.requests),
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: getGradient(ctx, '#667eea', 0.1),
|
||||
yAxisID: 'y',
|
||||
tension: 0.4
|
||||
},
|
||||
{
|
||||
label: 'Token使用量',
|
||||
data: dashboardStore.trendData.map(item => item.tokens),
|
||||
borderColor: '#f093fb',
|
||||
backgroundColor: getGradient(ctx, '#f093fb', 0.1),
|
||||
yAxisID: 'y1',
|
||||
tension: 0.4
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: '请求次数'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Token使用量'
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePeriodChange = async () => {
|
||||
await dashboardStore.loadUsageTrend(trendPeriod.value, granularity.value)
|
||||
createChart()
|
||||
}
|
||||
|
||||
const handleGranularityChange = async () => {
|
||||
// 根据粒度调整时间范围
|
||||
if (granularity.value === 'hour' && trendPeriod.value > 7) {
|
||||
trendPeriod.value = 1
|
||||
}
|
||||
await dashboardStore.loadUsageTrend(trendPeriod.value, granularity.value)
|
||||
createChart()
|
||||
}
|
||||
|
||||
watch(() => dashboardStore.trendData, () => {
|
||||
createChart()
|
||||
}, { deep: true })
|
||||
|
||||
onMounted(() => {
|
||||
createChart()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chart) {
|
||||
chart.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 组件特定样式 */
|
||||
</style>
|
||||
Reference in New Issue
Block a user