feat(ui): 优化密钥添加和仪表盘空状态体验

- KeyFormDialog: 添加模式下保存后不关闭对话框,清除表单以便连续添加
- KeyFormDialog: 按钮文案根据编辑/添加模式动态显示
- Dashboard: 优化统计卡片加载状态和空数据占位显示
This commit is contained in:
fawney19
2026-01-10 19:32:36 +08:00
parent ad84272084
commit 7faca5512a
2 changed files with 121 additions and 54 deletions

View File

@@ -232,7 +232,7 @@
:disabled="saving" :disabled="saving"
@click="handleSave" @click="handleSave"
> >
{{ saving ? '保存中...' : '保存' }} {{ saving ? (isEditMode ? '保存中...' : '添加中...') : (isEditMode ? '保存' : '添加') }}
</Button> </Button>
</template> </template>
</Dialog> </Dialog>
@@ -408,6 +408,14 @@ function resetForm() {
} }
} }
// 添加成功后清除部分字段以便继续添加
function clearForNextAdd() {
formNonce.value = createFieldNonce()
apiKeyFocused.value = false
form.value.name = ''
form.value.api_key = ''
}
// 加载密钥数据(编辑模式) // 加载密钥数据(编辑模式)
function loadKeyData() { function loadKeyData() {
if (!props.editingKey) return if (!props.editingKey) return
@@ -530,6 +538,10 @@ async function handleSave() {
capabilities: capabilitiesData || undefined capabilities: capabilitiesData || undefined
}) })
success('密钥已添加', '成功') success('密钥已添加', '成功')
// 添加模式:不关闭对话框,只清除名称和密钥以便继续添加
emit('saved')
clearForNextAdd()
return
} }
emit('saved') emit('saved')

View File

@@ -16,7 +16,8 @@
<!-- 主要统计卡片 --> <!-- 主要统计卡片 -->
<div class="grid grid-cols-2 gap-3 sm:gap-4 xl:grid-cols-4"> <div class="grid grid-cols-2 gap-3 sm:gap-4 xl:grid-cols-4">
<template v-if="loading && stats.length === 0"> <!-- 加载中骨架屏 -->
<template v-if="loading">
<Card <Card
v-for="i in 4" v-for="i in 4"
:key="'skeleton-' + i" :key="'skeleton-' + i"
@@ -27,9 +28,10 @@
<Skeleton class="h-4 w-16" /> <Skeleton class="h-4 w-16" />
</Card> </Card>
</template> </template>
<!-- 有数据时显示统计卡片 -->
<template v-else-if="stats.length > 0">
<Card <Card
v-for="(stat, index) in stats" v-for="(stat, index) in stats"
v-else
:key="stat.name" :key="stat.name"
class="relative overflow-hidden p-3 sm:p-5" class="relative overflow-hidden p-3 sm:p-5"
:class="statCardBorders[index % statCardBorders.length]" :class="statCardBorders[index % statCardBorders.length]"
@@ -83,6 +85,41 @@
</div> </div>
</div> </div>
</Card> </Card>
</template>
<!-- 无数据时显示占位卡片 -->
<template v-else>
<Card
v-for="(placeholder, index) in emptyStatPlaceholders"
:key="'empty-' + index"
class="relative overflow-hidden p-3 sm:p-5"
:class="statCardBorders[index % statCardBorders.length]"
>
<div
class="pointer-events-none absolute -right-4 -top-6 h-28 w-28 rounded-full blur-3xl opacity-20"
:class="statCardGlows[index % statCardGlows.length]"
/>
<div
class="absolute top-3 right-3 sm:top-5 sm:right-5 rounded-xl sm:rounded-2xl border border-border bg-card/50 p-2 sm:p-3 shadow-inner backdrop-blur-sm"
:class="getStatIconColor(index)"
>
<component
:is="placeholder.icon"
class="h-4 w-4 sm:h-5 sm:w-5"
/>
</div>
<div>
<p class="text-[9px] sm:text-[11px] font-semibold uppercase tracking-[0.2em] sm:tracking-[0.4em] text-muted-foreground pr-10 sm:pr-14">
{{ placeholder.name }}
</p>
<p class="mt-2 sm:mt-4 text-xl sm:text-3xl font-semibold text-muted-foreground/50">
--
</p>
<p class="mt-0.5 sm:mt-1 text-[10px] sm:text-sm text-muted-foreground/50">
暂无数据
</p>
</div>
</Card>
</template>
</div> </div>
<!-- 管理员系统健康摘要 --> <!-- 管理员系统健康摘要 -->
@@ -872,6 +909,24 @@ const iconMap: Record<string, any> = {
Users, Activity, TrendingUp, DollarSign, Key, Hash, Database Users, Activity, TrendingUp, DollarSign, Key, Hash, Database
} }
// 空状态占位卡片
const emptyStatPlaceholders = computed(() => {
if (isAdmin.value) {
return [
{ name: '今日请求', icon: Activity },
{ name: '今日 Tokens', icon: Hash },
{ name: '活跃用户', icon: Users },
{ name: '今日费用', icon: DollarSign }
]
}
return [
{ name: '今日请求', icon: Activity },
{ name: '今日 Tokens', icon: Hash },
{ name: 'API Keys', icon: Key },
{ name: '今日费用', icon: DollarSign }
]
})
const totalStats = computed(() => { const totalStats = computed(() => {
if (dailyStats.value.length === 0) { if (dailyStats.value.length === 0) {
return { requests: 0, tokens: 0, cost: 0, avgResponseTime: 0 } return { requests: 0, tokens: 0, cost: 0, avgResponseTime: 0 }