feat: 统计数据优化 - 支持细粒度时间范围和多维度分析

- 新增 StatsHourly/StatsDaily 预聚合表,支持任意时区的精确统计
- 实现 UTC datetime 范围查询策略,边界数据实时聚合
- 新增统计 API:用户/API Key 维度、成本分析、性能百分位、错误分类
- 新增前端页面:成本分析、性能分析、用户统计
- 新增 TimeRangePicker 组件和统计可视化组件
- 优化 Dashboard 和 Usage 页面支持时间范围筛选

Close #135
This commit is contained in:
fawney19
2026-02-04 02:04:54 +08:00
parent 6f363a7703
commit f3e2f84b38
59 changed files with 6392 additions and 500 deletions
+18 -2
View File
@@ -393,6 +393,14 @@
</div>
</div>
<!-- 趋势图表筛选 -->
<div class="flex flex-wrap items-center justify-between gap-3">
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
统计周期
</h3>
<TimeRangePicker v-model="dailyTimeRange" :allow-hourly="true" />
</div>
<!-- 趋势图表区域 -->
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<!-- 每日使用趋势(折线图)- 普通用户可见 -->
@@ -784,6 +792,8 @@
import { ref, onMounted, computed, onBeforeUnmount, nextTick, watch } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { dashboardApi, type DashboardStat, type DailyStat, type ProviderSummary } from '@/api/dashboard'
import { getDateRangeFromPeriod } from '@/features/usage/composables'
import type { DateRangeParams } from '@/features/usage/types'
import { announcementApi, type Announcement } from '@/api/announcements'
import {
Card,
@@ -798,6 +808,7 @@ import {
TableHead,
TableCell,
} from '@/components/ui'
import { TimeRangePicker } from '@/components/common'
import BarChart from '@/components/charts/BarChart.vue'
import DoughnutChart from '@/components/charts/DoughnutChart.vue'
import LineChart from '@/components/charts/LineChart.vue'
@@ -972,7 +983,8 @@ const tokenBreakdown = ref<{
const activeUsers = ref(0)
const dailyStats = ref<DailyStat[]>([])
const providerSummary = ref<ProviderSummary[]>([])
const selectedDays = ref(7)
const dailyTimeRange = ref<DateRangeParams>(getDateRangeFromPeriod('last7days'))
// 统计周期
const loadingDaily = ref(false)
const loading = ref(false)
@@ -1317,7 +1329,7 @@ async function loadDashboardData() {
async function loadDailyStats() {
loadingDaily.value = true
try {
const response = await dashboardApi.getDailyStats(selectedDays.value)
const response = await dashboardApi.getDailyStats(dailyTimeRange.value)
dailyStats.value = response.daily_stats
providerSummary.value = response.provider_summary || []
} catch {
@@ -1328,6 +1340,10 @@ async function loadDailyStats() {
}
}
watch(dailyTimeRange, async () => {
await loadDailyStats()
}, { deep: true })
function formatDate(dateString: string): string {
const date = new Date(dateString)
const today = new Date()