style(frontend): 优化 Components 代码风格和结构

- 统一移除语句末尾分号,规范代码格式
- 优化组件类型定义和 props 声明
- 改进组件文档和示例代码
- 提升代码可读性和一致性
This commit is contained in:
ianshaw
2025-12-25 08:40:12 -08:00
parent 1ac8b1f03e
commit 5deef27e1d
38 changed files with 2582 additions and 1485 deletions

View File

@@ -1,37 +1,59 @@
<template>
<div class="card p-4">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">{{ t('admin.dashboard.modelDistribution') }}</h3>
<div v-if="loading" class="flex items-center justify-center h-48">
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.dashboard.modelDistribution') }}
</h3>
<div v-if="loading" class="flex h-48 items-center justify-center">
<LoadingSpinner />
</div>
<div v-else-if="modelStats.length > 0 && chartData" class="flex items-center gap-6">
<div class="w-48 h-48">
<div class="h-48 w-48">
<Doughnut :data="chartData" :options="doughnutOptions" />
</div>
<div class="flex-1 max-h-48 overflow-y-auto">
<div class="max-h-48 flex-1 overflow-y-auto">
<table class="w-full text-xs">
<thead>
<tr class="text-gray-500 dark:text-gray-400">
<th class="text-left pb-2">{{ t('admin.dashboard.model') }}</th>
<th class="text-right pb-2">{{ t('admin.dashboard.requests') }}</th>
<th class="text-right pb-2">{{ t('admin.dashboard.tokens') }}</th>
<th class="text-right pb-2">{{ t('admin.dashboard.actual') }}</th>
<th class="text-right pb-2">{{ t('admin.dashboard.standard') }}</th>
<th class="pb-2 text-left">{{ t('admin.dashboard.model') }}</th>
<th class="pb-2 text-right">{{ t('admin.dashboard.requests') }}</th>
<th class="pb-2 text-right">{{ t('admin.dashboard.tokens') }}</th>
<th class="pb-2 text-right">{{ t('admin.dashboard.actual') }}</th>
<th class="pb-2 text-right">{{ t('admin.dashboard.standard') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="model in modelStats" :key="model.model" class="border-t border-gray-100 dark:border-gray-700">
<td class="py-1.5 text-gray-900 dark:text-white font-medium truncate max-w-[100px]" :title="model.model">{{ model.model }}</td>
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">{{ formatNumber(model.requests) }}</td>
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">{{ formatTokens(model.total_tokens) }}</td>
<td class="py-1.5 text-right text-green-600 dark:text-green-400">${{ formatCost(model.actual_cost) }}</td>
<td class="py-1.5 text-right text-gray-400 dark:text-gray-500">${{ formatCost(model.cost) }}</td>
<tr
v-for="model in modelStats"
:key="model.model"
class="border-t border-gray-100 dark:border-gray-700"
>
<td
class="max-w-[100px] truncate py-1.5 font-medium text-gray-900 dark:text-white"
:title="model.model"
>
{{ model.model }}
</td>
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">
{{ formatNumber(model.requests) }}
</td>
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">
{{ formatTokens(model.total_tokens) }}
</td>
<td class="py-1.5 text-right text-green-600 dark:text-green-400">
${{ formatCost(model.actual_cost) }}
</td>
<td class="py-1.5 text-right text-gray-400 dark:text-gray-500">
${{ formatCost(model.cost) }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else class="flex items-center justify-center h-48 text-gray-500 dark:text-gray-400 text-sm">
<div
v-else
class="flex h-48 items-center justify-center text-sm text-gray-500 dark:text-gray-400"
>
{{ t('admin.dashboard.noDataAvailable') }}
</div>
</div>
@@ -40,12 +62,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import {
Chart as ChartJS,
ArcElement,
Tooltip,
Legend
} from 'chart.js'
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
import { Doughnut } from 'vue-chartjs'
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
import type { ModelStat } from '@/types'
@@ -60,20 +77,30 @@ const props = defineProps<{
}>()
const chartColors = [
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
'#ec4899', '#14b8a6', '#f97316', '#6366f1', '#84cc16'
'#3b82f6',
'#10b981',
'#f59e0b',
'#ef4444',
'#8b5cf6',
'#ec4899',
'#14b8a6',
'#f97316',
'#6366f1',
'#84cc16'
]
const chartData = computed(() => {
if (!props.modelStats?.length) return null
return {
labels: props.modelStats.map(m => m.model),
datasets: [{
data: props.modelStats.map(m => m.total_tokens),
backgroundColor: chartColors.slice(0, props.modelStats.length),
borderWidth: 0,
}],
labels: props.modelStats.map((m) => m.model),
datasets: [
{
data: props.modelStats.map((m) => m.total_tokens),
backgroundColor: chartColors.slice(0, props.modelStats.length),
borderWidth: 0
}
]
}
})
@@ -82,7 +109,7 @@ const doughnutOptions = computed(() => ({
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
display: false
},
tooltip: {
callbacks: {
@@ -91,10 +118,10 @@ const doughnutOptions = computed(() => ({
const total = context.dataset.data.reduce((a: number, b: number) => a + b, 0)
const percentage = ((value / total) * 100).toFixed(1)
return `${context.label}: ${formatTokens(value)} (${percentage}%)`
},
},
},
},
}
}
}
}
}))
const formatTokens = (value: number): string => {

View File

@@ -1,13 +1,18 @@
<template>
<div class="card p-4">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">{{ t('admin.dashboard.tokenUsageTrend') }}</h3>
<div v-if="loading" class="flex items-center justify-center h-48">
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.dashboard.tokenUsageTrend') }}
</h3>
<div v-if="loading" class="flex h-48 items-center justify-center">
<LoadingSpinner />
</div>
<div v-else-if="trendData.length > 0 && chartData" class="h-48">
<Line :data="chartData" :options="lineOptions" />
</div>
<div v-else class="flex items-center justify-center h-48 text-gray-500 dark:text-gray-400 text-sm">
<div
v-else
class="flex h-48 items-center justify-center text-sm text-gray-500 dark:text-gray-400"
>
{{ t('admin.dashboard.noDataAvailable') }}
</div>
</div>
@@ -58,40 +63,40 @@ const chartColors = computed(() => ({
grid: isDarkMode.value ? '#374151' : '#e5e7eb',
input: '#3b82f6',
output: '#10b981',
cache: '#f59e0b',
cache: '#f59e0b'
}))
const chartData = computed(() => {
if (!props.trendData?.length) return null
return {
labels: props.trendData.map(d => d.date),
labels: props.trendData.map((d) => d.date),
datasets: [
{
label: 'Input',
data: props.trendData.map(d => d.input_tokens),
data: props.trendData.map((d) => d.input_tokens),
borderColor: chartColors.value.input,
backgroundColor: `${chartColors.value.input}20`,
fill: true,
tension: 0.3,
tension: 0.3
},
{
label: 'Output',
data: props.trendData.map(d => d.output_tokens),
data: props.trendData.map((d) => d.output_tokens),
borderColor: chartColors.value.output,
backgroundColor: `${chartColors.value.output}20`,
fill: true,
tension: 0.3,
tension: 0.3
},
{
label: 'Cache',
data: props.trendData.map(d => d.cache_tokens),
data: props.trendData.map((d) => d.cache_tokens),
borderColor: chartColors.value.cache,
backgroundColor: `${chartColors.value.cache}20`,
fill: true,
tension: 0.3,
},
],
tension: 0.3
}
]
}
})
@@ -100,7 +105,7 @@ const lineOptions = computed(() => ({
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index' as const,
mode: 'index' as const
},
plugins: {
legend: {
@@ -111,9 +116,9 @@ const lineOptions = computed(() => ({
pointStyle: 'circle',
padding: 15,
font: {
size: 11,
},
},
size: 11
}
}
},
tooltip: {
callbacks: {
@@ -127,35 +132,35 @@ const lineOptions = computed(() => ({
return `Actual: $${formatCost(data.actual_cost)} | Standard: $${formatCost(data.cost)}`
}
return ''
},
},
},
}
}
}
},
scales: {
x: {
grid: {
color: chartColors.value.grid,
color: chartColors.value.grid
},
ticks: {
color: chartColors.value.text,
font: {
size: 10,
},
},
size: 10
}
}
},
y: {
grid: {
color: chartColors.value.grid,
color: chartColors.value.grid
},
ticks: {
color: chartColors.value.text,
font: {
size: 10,
size: 10
},
callback: (value: string | number) => formatTokens(Number(value)),
},
},
},
callback: (value: string | number) => formatTokens(Number(value))
}
}
}
}))
const formatTokens = (value: number): string => {