refactor(frontend): 优化功能模块组件

- 更新 api-keys 模块: StandaloneKeyFormDialog
- 改进 auth 模块: LoginDialog
- 优化 models 模块: AliasDialog, GlobalModelFormDialog, ModelDetailDrawer, TieredPricingEditor
- 重构 providers 模块: 多个表单和对话框组件
- 更新 usage 模块: 时间线、表格和详情组件
- 调整 users 模块: UserFormDialog
This commit is contained in:
fawney19
2025-12-12 16:15:36 +08:00
parent e9a6233655
commit 06c0a47b21
29 changed files with 2572 additions and 1051 deletions

View File

@@ -8,7 +8,10 @@
@click.self="handleBackdropClick"
>
<!-- 背景遮罩 -->
<div class="absolute inset-0 bg-black/30 backdrop-blur-sm" @click="handleBackdropClick"></div>
<div
class="absolute inset-0 bg-black/30 backdrop-blur-sm"
@click="handleBackdropClick"
/>
<!-- 抽屉内容 -->
<Card class="relative h-full w-[700px] rounded-none shadow-2xl overflow-y-auto">
@@ -16,8 +19,13 @@
<div class="flex items-start justify-between gap-4">
<div class="space-y-1 flex-1 min-w-0">
<div class="flex items-center gap-2">
<h3 class="text-xl font-bold truncate">{{ model.display_name }}</h3>
<Badge :variant="model.is_active ? 'default' : 'secondary'" class="text-xs shrink-0">
<h3 class="text-xl font-bold truncate">
{{ model.display_name }}
</h3>
<Badge
:variant="model.is_active ? 'default' : 'secondary'"
class="text-xs shrink-0"
>
{{ model.is_active ? '活跃' : '停用' }}
</Badge>
</div>
@@ -32,23 +40,36 @@
</button>
<template v-if="model.description">
<span class="shrink-0">·</span>
<span class="text-xs truncate" :title="model.description">{{ model.description }}</span>
<span
class="text-xs truncate"
:title="model.description"
>{{ model.description }}</span>
</template>
</div>
</div>
<div class="flex items-center gap-1 shrink-0">
<Button variant="ghost" size="icon" @click="$emit('edit-model', model)" title="编辑模型">
<Button
variant="ghost"
size="icon"
title="编辑模型"
@click="$emit('edit-model', model)"
>
<Edit class="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
@click="$emit('toggle-model-status', model)"
:title="model.is_active ? '点击停用' : '点击启用'"
@click="$emit('toggle-model-status', model)"
>
<Power class="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" @click="handleClose" title="关闭">
<Button
variant="ghost"
size="icon"
title="关闭"
@click="handleClose"
>
<X class="w-4 h-4" />
</Button>
</div>
@@ -56,110 +77,154 @@
</div>
<div class="p-6">
<!-- 自定义 Tab 切换 -->
<div class="flex gap-1 p-1 bg-muted/40 rounded-lg mb-4">
<button
type="button"
:class="[
'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200',
detailTab === 'basic'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'basic'"
>
基本信息
</button>
<button
type="button"
:class="[
'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200',
detailTab === 'providers'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'providers'"
>
关联提供商
</button>
<button
type="button"
:class="[
'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200',
detailTab === 'aliases'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'aliases'"
>
别名/映射
</button>
</div>
<!-- 自定义 Tab 切换 -->
<div class="flex gap-1 p-1 bg-muted/40 rounded-lg mb-4">
<button
type="button"
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
:class="[
detailTab === 'basic'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'basic'"
>
基本信息
</button>
<button
type="button"
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
:class="[
detailTab === 'providers'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'providers'"
>
关联提供商
</button>
<button
type="button"
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
:class="[
detailTab === 'aliases'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'aliases'"
>
别名/映射
</button>
</div>
<!-- Tab 内容 -->
<div v-show="detailTab === 'basic'" class="space-y-6">
<!-- Tab 内容 -->
<div
v-show="detailTab === 'basic'"
class="space-y-6"
>
<!-- 基础属性 -->
<div class="space-y-4">
<h4 class="font-semibold text-sm">基础属性</h4>
<h4 class="font-semibold text-sm">
基础属性
</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<Label class="text-xs text-muted-foreground">创建时间</Label>
<p class="text-sm mt-1">{{ formatDate(model.created_at) }}</p>
<p class="text-sm mt-1">
{{ formatDate(model.created_at) }}
</p>
</div>
</div>
</div>
<!-- 模型能力 -->
<div class="space-y-3">
<h4 class="font-semibold text-sm">模型能力</h4>
<h4 class="font-semibold text-sm">
模型能力
</h4>
<div class="grid grid-cols-2 gap-3">
<div class="flex items-center gap-2 p-3 rounded-lg border">
<Zap class="w-5 h-5 text-muted-foreground" />
<div class="flex-1">
<p class="text-sm font-medium">Streaming</p>
<p class="text-xs text-muted-foreground">流式输出</p>
<p class="text-sm font-medium">
Streaming
</p>
<p class="text-xs text-muted-foreground">
流式输出
</p>
</div>
<Badge :variant="model.default_supports_streaming ?? false ? 'default' : 'secondary'" class="text-xs">
<Badge
:variant="model.default_supports_streaming ?? false ? 'default' : 'secondary'"
class="text-xs"
>
{{ model.default_supports_streaming ?? false ? '支持' : '不支持' }}
</Badge>
</div>
<div class="flex items-center gap-2 p-3 rounded-lg border">
<Image class="w-5 h-5 text-muted-foreground" />
<div class="flex-1">
<p class="text-sm font-medium">Image Generation</p>
<p class="text-xs text-muted-foreground">图像生成</p>
<p class="text-sm font-medium">
Image Generation
</p>
<p class="text-xs text-muted-foreground">
图像生成
</p>
</div>
<Badge :variant="model.default_supports_image_generation ?? false ? 'default' : 'secondary'" class="text-xs">
<Badge
:variant="model.default_supports_image_generation ?? false ? 'default' : 'secondary'"
class="text-xs"
>
{{ model.default_supports_image_generation ?? false ? '支持' : '不支持' }}
</Badge>
</div>
<div class="flex items-center gap-2 p-3 rounded-lg border">
<Eye class="w-5 h-5 text-muted-foreground" />
<div class="flex-1">
<p class="text-sm font-medium">Vision</p>
<p class="text-xs text-muted-foreground">视觉理解</p>
<p class="text-sm font-medium">
Vision
</p>
<p class="text-xs text-muted-foreground">
视觉理解
</p>
</div>
<Badge :variant="model.default_supports_vision ?? false ? 'default' : 'secondary'" class="text-xs">
<Badge
:variant="model.default_supports_vision ?? false ? 'default' : 'secondary'"
class="text-xs"
>
{{ model.default_supports_vision ?? false ? '支持' : '不支持' }}
</Badge>
</div>
<div class="flex items-center gap-2 p-3 rounded-lg border">
<Wrench class="w-5 h-5 text-muted-foreground" />
<div class="flex-1">
<p class="text-sm font-medium">Tool Use</p>
<p class="text-xs text-muted-foreground">工具调用</p>
<p class="text-sm font-medium">
Tool Use
</p>
<p class="text-xs text-muted-foreground">
工具调用
</p>
</div>
<Badge :variant="model.default_supports_function_calling ?? false ? 'default' : 'secondary'" class="text-xs">
<Badge
:variant="model.default_supports_function_calling ?? false ? 'default' : 'secondary'"
class="text-xs"
>
{{ model.default_supports_function_calling ?? false ? '支持' : '不支持' }}
</Badge>
</div>
<div class="flex items-center gap-2 p-3 rounded-lg border">
<Brain class="w-5 h-5 text-muted-foreground" />
<div class="flex-1">
<p class="text-sm font-medium">Extended Thinking</p>
<p class="text-xs text-muted-foreground">深度思考</p>
<p class="text-sm font-medium">
Extended Thinking
</p>
<p class="text-xs text-muted-foreground">
深度思考
</p>
</div>
<Badge :variant="model.default_supports_extended_thinking ?? false ? 'default' : 'secondary'" class="text-xs">
<Badge
:variant="model.default_supports_extended_thinking ?? false ? 'default' : 'secondary'"
class="text-xs"
>
{{ model.default_supports_extended_thinking ?? false ? '支持' : '不支持' }}
</Badge>
</div>
@@ -167,8 +232,13 @@
</div>
<!-- 模型偏好 -->
<div v-if="model.supported_capabilities && model.supported_capabilities.length > 0" class="space-y-3">
<h4 class="font-semibold text-sm">模型偏好</h4>
<div
v-if="model.supported_capabilities && model.supported_capabilities.length > 0"
class="space-y-3"
>
<h4 class="font-semibold text-sm">
模型偏好
</h4>
<div class="flex flex-wrap gap-2">
<Badge
v-for="cap in model.supported_capabilities"
@@ -183,10 +253,15 @@
<!-- 默认定价 -->
<div class="space-y-3">
<h4 class="font-semibold text-sm">默认定价</h4>
<h4 class="font-semibold text-sm">
默认定价
</h4>
<!-- 单阶梯固定价格展示 -->
<div v-if="getTierCount(model.default_tiered_pricing) <= 1" class="space-y-3">
<div
v-if="getTierCount(model.default_tiered_pricing) <= 1"
class="space-y-3"
>
<div class="grid grid-cols-2 gap-3">
<!-- Token 计费 -->
<div class="p-3 rounded-lg border">
@@ -215,19 +290,28 @@
</div>
</div>
<!-- 1h 缓存 -->
<div v-if="getFirst1hCachePrice(model.default_tiered_pricing) !== '-'" class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20">
<div
v-if="getFirst1hCachePrice(model.default_tiered_pricing) !== '-'"
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
>
<Label class="text-xs text-muted-foreground whitespace-nowrap">1h 缓存创建</Label>
<span class="text-sm font-mono">{{ getFirst1hCachePrice(model.default_tiered_pricing) }}</span>
</div>
<!-- 按次计费 -->
<div v-if="model.default_price_per_request && model.default_price_per_request > 0" class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20">
<div
v-if="model.default_price_per_request && model.default_price_per_request > 0"
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
>
<Label class="text-xs text-muted-foreground whitespace-nowrap">按次计费</Label>
<span class="text-sm font-mono">${{ model.default_price_per_request.toFixed(3) }}/</span>
</div>
</div>
<!-- 多阶梯计费展示 -->
<div v-else class="space-y-3">
<div
v-else
class="space-y-3"
>
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<Layers class="w-4 h-4" />
<span>阶梯计费 ({{ getTierCount(model.default_tiered_pricing) }} )</span>
@@ -238,12 +322,24 @@
<Table>
<TableHeader>
<TableRow class="bg-muted/30">
<TableHead class="text-xs h-9">阶梯</TableHead>
<TableHead class="text-xs h-9 text-right">输入 ($/M)</TableHead>
<TableHead class="text-xs h-9 text-right">输出 ($/M)</TableHead>
<TableHead class="text-xs h-9 text-right">缓存创建</TableHead>
<TableHead class="text-xs h-9 text-right">缓存读取</TableHead>
<TableHead class="text-xs h-9 text-right">1h 缓存</TableHead>
<TableHead class="text-xs h-9">
阶梯
</TableHead>
<TableHead class="text-xs h-9 text-right">
输入 ($/M)
</TableHead>
<TableHead class="text-xs h-9 text-right">
输出 ($/M)
</TableHead>
<TableHead class="text-xs h-9 text-right">
缓存创建
</TableHead>
<TableHead class="text-xs h-9 text-right">
缓存读取
</TableHead>
<TableHead class="text-xs h-9 text-right">
1h 缓存
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -253,7 +349,10 @@
class="text-xs"
>
<TableCell class="py-2">
<span v-if="tier.up_to === null" class="text-muted-foreground">
<span
v-if="tier.up_to === null"
class="text-muted-foreground"
>
{{ index === 0 ? '所有' : `> ${formatTierLimit((model.default_tiered_pricing?.tiers || [])[index - 1]?.up_to)}` }}
</span>
<span v-else>
@@ -281,7 +380,10 @@
</div>
<!-- 按次计费多阶梯时也显示 -->
<div v-if="model.default_price_per_request && model.default_price_per_request > 0" class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20">
<div
v-if="model.default_price_per_request && model.default_price_per_request > 0"
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
>
<Label class="text-xs text-muted-foreground whitespace-nowrap">按次计费</Label>
<span class="text-sm font-mono">${{ model.default_price_per_request.toFixed(3) }}/</span>
</div>
@@ -290,42 +392,50 @@
<!-- 统计信息 -->
<div class="space-y-3">
<h4 class="font-semibold text-sm">统计信息</h4>
<h4 class="font-semibold text-sm">
统计信息
</h4>
<div class="grid grid-cols-2 gap-3">
<div class="p-3 rounded-lg border bg-muted/20">
<div class="flex items-center justify-between">
<Label class="text-xs text-muted-foreground">关联提供商</Label>
<Building2 class="w-4 h-4 text-muted-foreground" />
</div>
<p class="text-2xl font-bold mt-1">{{ model.provider_count || 0 }}</p>
<p class="text-2xl font-bold mt-1">
{{ model.provider_count || 0 }}
</p>
</div>
<div class="p-3 rounded-lg border bg-muted/20">
<div class="flex items-center justify-between">
<Label class="text-xs text-muted-foreground">别名数量</Label>
<Tag class="w-4 h-4 text-muted-foreground" />
</div>
<p class="text-2xl font-bold mt-1">{{ model.alias_count || 0 }}</p>
<p class="text-2xl font-bold mt-1">
{{ model.alias_count || 0 }}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Tab 2: 关联提供商 -->
<div v-show="detailTab === 'providers'">
<!-- Tab 2: 关联提供商 -->
<div v-show="detailTab === 'providers'">
<Card class="overflow-hidden">
<!-- 标题栏 -->
<div class="px-4 py-3 border-b border-border/60">
<div class="flex items-center justify-between gap-4">
<div>
<h4 class="text-sm font-semibold">关联提供商列表</h4>
<h4 class="text-sm font-semibold">
关联提供商列表
</h4>
</div>
<div class="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
@click="$emit('add-provider')"
title="添加关联"
@click="$emit('add-provider')"
>
<Plus class="w-3.5 h-3.5" />
</Button>
@@ -333,27 +443,41 @@
variant="ghost"
size="icon"
class="h-8 w-8"
@click="$emit('refresh-providers')"
title="刷新"
@click="$emit('refresh-providers')"
>
<RefreshCw class="w-3.5 h-3.5" :class="loadingProviders ? 'animate-spin' : ''" />
<RefreshCw
class="w-3.5 h-3.5"
:class="loadingProviders ? 'animate-spin' : ''"
/>
</Button>
</div>
</div>
</div>
<!-- 表格内容 -->
<div v-if="loadingProviders" class="flex items-center justify-center py-12">
<div
v-if="loadingProviders"
class="flex items-center justify-center py-12"
>
<Loader2 class="w-6 h-6 animate-spin text-primary" />
</div>
<Table v-else-if="providers.length > 0">
<TableHeader>
<TableRow class="border-b border-border/60 hover:bg-transparent">
<TableHead class="h-10 font-semibold">Provider</TableHead>
<TableHead class="w-[120px] h-10 font-semibold">能力</TableHead>
<TableHead class="w-[180px] h-10 font-semibold">价格 ($/M)</TableHead>
<TableHead class="w-[80px] h-10 font-semibold text-center">操作</TableHead>
<TableHead class="h-10 font-semibold">
Provider
</TableHead>
<TableHead class="w-[120px] h-10 font-semibold">
能力
</TableHead>
<TableHead class="w-[180px] h-10 font-semibold">
价格 ($/M)
</TableHead>
<TableHead class="w-[80px] h-10 font-semibold text-center">
操作
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -368,15 +492,27 @@
class="w-2 h-2 rounded-full shrink-0"
:class="provider.is_active ? 'bg-green-500' : 'bg-gray-300'"
:title="provider.is_active ? '活跃' : '停用'"
></span>
/>
<span class="font-medium truncate">{{ provider.display_name }}</span>
</div>
</TableCell>
<TableCell class="py-3">
<div class="flex gap-0.5">
<Zap v-if="provider.supports_streaming" class="w-3.5 h-3.5 text-muted-foreground" title="流式输出" />
<Eye v-if="provider.supports_vision" class="w-3.5 h-3.5 text-muted-foreground" title="视觉理解" />
<Wrench v-if="provider.supports_function_calling" class="w-3.5 h-3.5 text-muted-foreground" title="工具调用" />
<Zap
v-if="provider.supports_streaming"
class="w-3.5 h-3.5 text-muted-foreground"
title="流式输出"
/>
<Eye
v-if="provider.supports_vision"
class="w-3.5 h-3.5 text-muted-foreground"
title="视觉理解"
/>
<Wrench
v-if="provider.supports_function_calling"
class="w-3.5 h-3.5 text-muted-foreground"
title="工具调用"
/>
</div>
</TableCell>
<TableCell class="py-3">
@@ -386,15 +522,25 @@
<span class="text-muted-foreground">输入/输出:</span>
<span class="ml-1">${{ (provider.input_price_per_1m || 0).toFixed(1) }}/${{ (provider.output_price_per_1m || 0).toFixed(1) }}</span>
<!-- 阶梯标记 -->
<span v-if="(provider.tier_count || 1) > 1" class="ml-1 text-muted-foreground" title="阶梯计费">[阶梯]</span>
<span
v-if="(provider.tier_count || 1) > 1"
class="ml-1 text-muted-foreground"
title="阶梯计费"
>[阶梯]</span>
</div>
<!-- 缓存价格 -->
<div v-if="(provider.cache_creation_price_per_1m || 0) > 0 || (provider.cache_read_price_per_1m || 0) > 0" class="text-muted-foreground">
<div
v-if="(provider.cache_creation_price_per_1m || 0) > 0 || (provider.cache_read_price_per_1m || 0) > 0"
class="text-muted-foreground"
>
<span>缓存:</span>
<span class="ml-1">${{ (provider.cache_creation_price_per_1m || 0).toFixed(2) }}/${{ (provider.cache_read_price_per_1m || 0).toFixed(2) }}</span>
</div>
<!-- 1h 缓存价格 -->
<div v-if="(provider.cache_1h_creation_price_per_1m || 0) > 0" class="text-muted-foreground">
<div
v-if="(provider.cache_1h_creation_price_per_1m || 0) > 0"
class="text-muted-foreground"
>
<span>1h 缓存:</span>
<span class="ml-1">${{ (provider.cache_1h_creation_price_per_1m || 0).toFixed(2) }}</span>
</div>
@@ -404,7 +550,10 @@
<span class="ml-1">${{ (provider.price_per_request || 0).toFixed(3) }}/</span>
</div>
<!-- 无定价 -->
<span v-if="!(provider.input_price_per_1m || 0) && !(provider.output_price_per_1m || 0) && !(provider.price_per_request || 0)" class="text-muted-foreground">-</span>
<span
v-if="!(provider.input_price_per_1m || 0) && !(provider.output_price_per_1m || 0) && !(provider.price_per_request || 0)"
class="text-muted-foreground"
>-</span>
</div>
</TableCell>
<TableCell class="py-3 text-center">
@@ -413,8 +562,8 @@
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('edit-provider', provider)"
title="编辑此关联"
@click="$emit('edit-provider', provider)"
>
<Edit class="w-3.5 h-3.5" />
</Button>
@@ -422,8 +571,8 @@
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('toggle-provider-status', provider)"
:title="provider.is_active ? '停用此关联' : '启用此关联'"
@click="$emit('toggle-provider-status', provider)"
>
<Power class="w-3.5 h-3.5" />
</Button>
@@ -431,8 +580,8 @@
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('delete-provider', provider)"
title="删除此关联"
@click="$emit('delete-provider', provider)"
>
<Trash2 class="w-3.5 h-3.5" />
</Button>
@@ -443,33 +592,45 @@
</Table>
<!-- 空状态 -->
<div v-else class="text-center py-12">
<div
v-else
class="text-center py-12"
>
<Building2 class="w-12 h-12 mx-auto text-muted-foreground/30 mb-3" />
<p class="text-sm text-muted-foreground">暂无关联提供商</p>
<Button size="sm" variant="outline" class="mt-4" @click="$emit('add-provider')">
<p class="text-sm text-muted-foreground">
暂无关联提供商
</p>
<Button
size="sm"
variant="outline"
class="mt-4"
@click="$emit('add-provider')"
>
<Plus class="w-4 h-4 mr-1" />
添加第一个关联
</Button>
</div>
</Card>
</div>
</div>
<!-- Tab 3: 别名 -->
<div v-show="detailTab === 'aliases'">
<!-- Tab 3: 别名 -->
<div v-show="detailTab === 'aliases'">
<Card class="overflow-hidden">
<!-- 标题栏 -->
<div class="px-4 py-3 border-b border-border/60">
<div class="flex items-center justify-between gap-4">
<div>
<h4 class="text-sm font-semibold">别名与映射</h4>
<h4 class="text-sm font-semibold">
别名与映射
</h4>
</div>
<div class="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
@click="$emit('add-alias')"
title="添加别名/映射"
@click="$emit('add-alias')"
>
<Plus class="w-3.5 h-3.5" />
</Button>
@@ -477,27 +638,41 @@
variant="ghost"
size="icon"
class="h-8 w-8"
@click="$emit('refresh-aliases')"
title="刷新"
@click="$emit('refresh-aliases')"
>
<RefreshCw class="w-3.5 h-3.5" :class="loadingAliases ? 'animate-spin' : ''" />
<RefreshCw
class="w-3.5 h-3.5"
:class="loadingAliases ? 'animate-spin' : ''"
/>
</Button>
</div>
</div>
</div>
<!-- 表格内容 -->
<div v-if="loadingAliases" class="flex items-center justify-center py-12">
<div
v-if="loadingAliases"
class="flex items-center justify-center py-12"
>
<Loader2 class="w-6 h-6 animate-spin text-primary" />
</div>
<Table v-else-if="aliases.length > 0">
<TableHeader>
<TableRow class="border-b border-border/60 hover:bg-transparent">
<TableHead class="h-10 font-semibold">别名</TableHead>
<TableHead class="w-[80px] h-10 font-semibold">类型</TableHead>
<TableHead class="w-[100px] h-10 font-semibold">作用域</TableHead>
<TableHead class="w-[100px] h-10 font-semibold text-center">操作</TableHead>
<TableHead class="h-10 font-semibold">
别名
</TableHead>
<TableHead class="w-[80px] h-10 font-semibold">
类型
</TableHead>
<TableHead class="w-[100px] h-10 font-semibold">
作用域
</TableHead>
<TableHead class="w-[100px] h-10 font-semibold text-center">
操作
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -512,12 +687,15 @@
class="w-2 h-2 rounded-full shrink-0"
:class="alias.is_active ? 'bg-green-500' : 'bg-gray-300'"
:title="alias.is_active ? '活跃' : '停用'"
></span>
/>
<code class="text-sm font-medium bg-muted px-1.5 py-0.5 rounded">{{ alias.alias }}</code>
</div>
</TableCell>
<TableCell class="py-3">
<Badge variant="secondary" class="text-xs">
<Badge
variant="secondary"
class="text-xs"
>
{{ alias.mapping_type === 'mapping' ? '映射' : '别名' }}
</Badge>
</TableCell>
@@ -530,7 +708,13 @@
>
{{ alias.provider_name || 'Provider' }}
</Badge>
<Badge v-else variant="default" class="text-xs">全局</Badge>
<Badge
v-else
variant="default"
class="text-xs"
>
全局
</Badge>
</TableCell>
<TableCell class="py-3 text-center">
<div class="flex items-center justify-center gap-0.5">
@@ -538,8 +722,8 @@
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('edit-alias', alias)"
title="编辑"
@click="$emit('edit-alias', alias)"
>
<Edit class="w-3.5 h-3.5" />
</Button>
@@ -547,8 +731,8 @@
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('toggle-alias-status', alias)"
:title="alias.is_active ? '停用' : '启用'"
@click="$emit('toggle-alias-status', alias)"
>
<Power class="w-3.5 h-3.5" />
</Button>
@@ -556,8 +740,8 @@
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('delete-alias', alias)"
title="删除"
@click="$emit('delete-alias', alias)"
>
<Trash2 class="w-3.5 h-3.5" />
</Button>
@@ -568,17 +752,27 @@
</Table>
<!-- 空状态 -->
<div v-else class="text-center py-12">
<div
v-else
class="text-center py-12"
>
<Tag class="w-12 h-12 mx-auto text-muted-foreground/30 mb-3" />
<p class="text-sm text-muted-foreground">暂无别名或映射</p>
<Button size="sm" variant="outline" class="mt-4" @click="$emit('add-alias')">
<p class="text-sm text-muted-foreground">
暂无别名或映射
</p>
<Button
size="sm"
variant="outline"
class="mt-4"
@click="$emit('add-alias')"
>
<Plus class="w-4 h-4 mr-1" />
添加别名/映射
</Button>
</div>
</Card>
</div>
</div>
</div>
</Card>
</div>
</Transition>
@@ -607,6 +801,26 @@ import {
} from 'lucide-vue-next'
import { useToast } from '@/composables/useToast'
const props = withDefaults(defineProps<Props>(), {
loadingProviders: false,
loadingAliases: false,
hasBlockingDialogOpen: false
})
const emit = defineEmits<{
'update:open': [value: boolean]
'edit-model': [model: GlobalModelResponse]
'toggle-model-status': [model: GlobalModelResponse]
'add-provider': []
'edit-provider': [provider: any]
'delete-provider': [provider: any]
'toggle-provider-status': [provider: any]
'refresh-providers': []
'add-alias': []
'edit-alias': [alias: ModelAlias]
'toggle-alias-status': [alias: ModelAlias]
'delete-alias': [alias: ModelAlias]
'refresh-aliases': []
}>()
const { success: showSuccess, error: showError } = useToast()
import Card from '@/components/ui/card.vue'
import Badge from '@/components/ui/badge.vue'
@@ -636,34 +850,12 @@ interface Props {
capabilities?: CapabilityDefinition[]
}
const props = withDefaults(defineProps<Props>(), {
loadingProviders: false,
loadingAliases: false,
hasBlockingDialogOpen: false
})
// 根据能力名称获取显示名称
function getCapabilityDisplayName(capName: string): string {
const cap = props.capabilities?.find(c => c.name === capName)
return cap?.display_name || capName
}
const emit = defineEmits<{
'update:open': [value: boolean]
'edit-model': [model: GlobalModelResponse]
'toggle-model-status': [model: GlobalModelResponse]
'add-provider': []
'edit-provider': [provider: any]
'delete-provider': [provider: any]
'toggle-provider-status': [provider: any]
'refresh-providers': []
'add-alias': []
'edit-alias': [alias: ModelAlias]
'toggle-alias-status': [alias: ModelAlias]
'delete-alias': [alias: ModelAlias]
'refresh-aliases': []
}>()
const detailTab = ref('basic')
// 处理背景点击