Files
Aether/frontend/src/features/overview/dashboard/PeriodComparison.vue
T
elky 066ea87d72 feat: revamp analytics dashboards and harden database migrations
Add dashboard and overview analytics, health monitoring, provider expense tracking, and announcement updates across the gateway and frontend.

Keep schema migrations free of historical backfills while preserving automatic backfill execution. Bound migration deadlines, run schema preparation before Compose replacement, and anonymize deleted dashboard users.

Include the current documentation cleanup and regression coverage.
2026-10-01 11:48:17 +08:00

68 lines
3.2 KiB
Vue

<template>
<section class="space-y-3 border-b pb-4">
<div class="flex flex-wrap items-center justify-between gap-2">
<h2 class="text-sm font-semibold">
{{ t('较前一等长时段', 'Versus previous equal period') }}
</h2><span
v-if="data"
class="text-xs text-muted-foreground"
>{{ timestamp(data.meta.range.from, range.timezone) }} - {{ timestamp(data.meta.range.to, range.timezone) }}</span>
</div>
<p
v-if="error"
role="alert"
class="text-xs text-destructive"
>
{{ t('对比数据暂不可用', 'Comparison unavailable') }}
</p><p
v-else-if="loading && !data"
class="text-xs text-muted-foreground"
>
{{ t('加载中', 'Loading') }}
</p>
<dl
v-if="data"
class="grid grid-cols-2 gap-4 sm:grid-cols-4"
>
<div
v-for="item in changes"
:key="item.label"
>
<dt class="text-xs text-muted-foreground">
{{ item.label }}
</dt><dd class="mt-1 text-sm font-semibold tabular-nums">
{{ item.value }}
</dd>
</div>
</dl>
<p
v-if="data"
class="text-xs text-muted-foreground"
>
{{ t('对比更新', 'Comparison updated') }} {{ timestamp(data.meta.generated_at, range.timezone) }} · {{ data.meta.coverage.status === 'complete' ? t('数据完整', 'Complete') : t('部分数据', 'Partial data') }}
</p>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { overviewApi, type OverviewMetrics, type OverviewRange } from '@/api/overview'
import { useOverviewRequest } from '../useOverviewRequest'
import { useOverviewI18n } from '../i18n'
import { timestamp } from '../format'
const props = defineProps<{ current: OverviewMetrics; range: OverviewRange; revision: number }>()
const { t } = useOverviewI18n()
const previousRange = computed(() => ({ from: new Date(Date.parse(props.range.from) - (Date.parse(props.range.to) - Date.parse(props.range.from))).toISOString(), to: props.range.from, timezone: props.range.timezone }))
const { data, loading, error } = useOverviewRequest(() => [previousRange.value, props.revision], signal => overviewApi.summary(previousRange.value, signal), { scopeKey: () => JSON.stringify(previousRange.value) })
function delta(current: number | null | undefined, previous: number | null | undefined) {
if (current == null || previous == null) return '-'
if (!previous) return current ? t('前期为零', 'Previous period zero') : '0.0%'
const value = (current - previous) / previous * 100
return `${value > 0 ? '+' : ''}${value.toFixed(1)}%`
}
const changes = computed(() => {
const previous = data.value?.data
return [{ label: t('请求数', 'Requests'), value: delta(props.current.request_count, previous?.request_count) }, { label: 'Tokens', value: delta(props.current.total_tokens, previous?.total_tokens) }, { label: t('计费消耗', 'Billable consumption'), value: delta(props.current.billable_amount.value == null ? null : Number(props.current.billable_amount.value), previous?.billable_amount.value == null ? null : Number(previous.billable_amount.value)) }, { label: t('有使用成员', 'Members with usage'), value: delta(props.current.usage_active_users, previous?.usage_active_users) }]
})
</script>