初始提交:OCI 面板前端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<script setup lang="ts">
|
||||
import { NSelect, NSpin } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { getCosts } from '@/api/analytics'
|
||||
import CostChart from '@/components/CostChart.vue'
|
||||
import EmptyCard from '@/components/EmptyCard.vue'
|
||||
import FootNote from '@/components/FootNote.vue'
|
||||
import { useAsync } from '@/composables/useAsync'
|
||||
|
||||
const props = defineProps<{ cfgId: number }>()
|
||||
|
||||
const rangeDays = ref(14)
|
||||
const rangeOptions = [
|
||||
{ label: '近 14 天', value: 14 },
|
||||
{ label: '近 30 天', value: 30 },
|
||||
]
|
||||
|
||||
const costs = useAsync(() => getCosts(props.cfgId, { granularity: 'DAILY', groupBy: 'service' }))
|
||||
watch(rangeDays, () => void costs.run())
|
||||
|
||||
const daily = computed(() => {
|
||||
const byDay = new Map<string, number>()
|
||||
for (const item of costs.data.value ?? []) {
|
||||
const day = item.timeStart.slice(5, 10)
|
||||
byDay.set(day, (byDay.get(day) ?? 0) + item.computedAmount)
|
||||
}
|
||||
const labels = [...byDay.keys()].sort()
|
||||
return { labels, values: labels.map((l) => +(byDay.get(l) ?? 0).toFixed(2)) }
|
||||
})
|
||||
|
||||
const total = computed(() => daily.value.values.reduce((s, v) => s + v, 0))
|
||||
|
||||
const byService = computed(() => {
|
||||
const map = new Map<string, number>()
|
||||
for (const item of costs.data.value ?? []) {
|
||||
map.set(item.groupValue, (map.get(item.groupValue) ?? 0) + item.computedAmount)
|
||||
}
|
||||
const rows = [...map.entries()].sort((a, b) => b[1] - a[1])
|
||||
const max = rows[0]?.[1] ?? 1
|
||||
return rows.map(([name, amount]) => ({
|
||||
name,
|
||||
amount,
|
||||
pct: total.value ? Math.round((amount / total.value) * 100) : 0,
|
||||
barWidth: `${Math.round((amount / max) * 100)}%`,
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel">
|
||||
<div class="flex flex-wrap items-center gap-2.5 border-b border-line-soft px-4.5 py-3.5">
|
||||
<div class="text-sm font-semibold">成本分析</div>
|
||||
<div class="flex-1" />
|
||||
<div class="w-28">
|
||||
<NSelect v-model:value="rangeDays" size="small" :options="rangeOptions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="costs.loading.value" class="flex items-center justify-center py-14">
|
||||
<NSpin size="small" />
|
||||
</div>
|
||||
<template v-else-if="costs.data.value?.length">
|
||||
<div class="flex flex-wrap items-baseline gap-2.5 px-4.5 pt-3.5">
|
||||
<div class="text-[26px] font-semibold tabular-nums">${{ total.toFixed(2) }}</div>
|
||||
<div class="text-[12.5px] text-ink-3">
|
||||
{{ daily.labels[0] }} 至 {{ daily.labels.at(-1) }} 合计
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 pb-1">
|
||||
<CostChart :labels="daily.labels" :values="daily.values" />
|
||||
</div>
|
||||
|
||||
<div class="border-t border-line-soft px-4.5 py-3">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="text-[13px] font-semibold">按服务拆分</div>
|
||||
<div class="text-[12.5px] text-ink-3">本期合计 ${{ total.toFixed(2) }}</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="svc in byService"
|
||||
:key="svc.name"
|
||||
class="flex items-center gap-3 py-1.5 max-md:gap-2"
|
||||
>
|
||||
<div class="w-32 flex-none truncate text-[13px] max-md:w-24" :title="svc.name">
|
||||
{{ svc.name }}
|
||||
</div>
|
||||
<div class="h-1.5 min-w-0 flex-1 overflow-hidden rounded-full bg-line-soft">
|
||||
<div class="h-full rounded-full bg-chart" :style="{ width: svc.barWidth }" />
|
||||
</div>
|
||||
<div class="w-17 flex-none text-right text-[13px] tabular-nums">
|
||||
${{ svc.amount.toFixed(2) }}
|
||||
</div>
|
||||
<div class="w-10 flex-none text-right text-xs tabular-nums text-ink-3">{{ svc.pct }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<EmptyCard
|
||||
v-else
|
||||
title="暂无成本数据"
|
||||
note="试用租户无消费时成本为空;Usage API 数据通常延迟数小时至一天"
|
||||
/>
|
||||
<FootNote>
|
||||
每日 03:30 自动同步 Usage API · 本地快照 · 免费类别租户不参与成本同步 · 点击刷新可即时拉取
|
||||
</FootNote>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user