mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 13:10:21 +08:00
fix(mobile): improve usage and pool management layouts
- Fix pool account batch dialog scrolling on mobile - Rework usage records mobile filters into clearer rows - Align user filter styling with other select filters - Improve request detail drawer metric layout on mobile
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<Select
|
||||
v-model="selectedPreset"
|
||||
>
|
||||
<SelectTrigger class="h-8 w-32 text-xs border-border/60">
|
||||
<SelectTrigger :class="['h-8 w-32 text-xs border-border/60', presetTriggerClass]">
|
||||
<SelectValue placeholder="选择时间段" />
|
||||
</SelectTrigger>
|
||||
<SelectContent :searchable="false">
|
||||
@@ -91,6 +91,7 @@ const props = withDefaults(defineProps<{
|
||||
showGranularity?: boolean
|
||||
allowHourly?: boolean
|
||||
presetOptions?: SelectablePreset[]
|
||||
presetTriggerClass?: string
|
||||
}>(), {
|
||||
presetOptions: () => ['today', 'yesterday', 'last7days', 'last30days', 'last90days', 'custom']
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
persistent
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div class="max-h-[calc(100dvh-13rem)] space-y-4 overflow-y-auto overscroll-contain pr-1 sm:max-h-[min(72vh,44rem)] sm:pr-2">
|
||||
<div class="space-y-3 rounded-lg border bg-muted/20 px-3 py-2.5">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-xs font-medium text-foreground">快捷多选</span>
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_19rem]">
|
||||
<div class="min-w-0 space-y-3">
|
||||
<div class="max-h-[420px] overflow-y-auto rounded-lg border">
|
||||
<div class="rounded-lg border lg:max-h-[420px] lg:overflow-y-auto">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="py-10 text-center text-sm text-muted-foreground"
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
<!-- 固定头部 - 整合基本信息 -->
|
||||
<div class="sticky top-0 z-10 bg-background border-b px-3 sm:px-6 py-3 sm:py-4 flex-shrink-0">
|
||||
<!-- 第一行:标题、模型、状态、操作按钮 -->
|
||||
<div class="flex items-center justify-between gap-4 mb-3">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<div class="flex items-center justify-between gap-2 sm:gap-4 mb-3">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2 sm:gap-3 flex-wrap">
|
||||
<h3 class="text-lg font-semibold">
|
||||
请求详情
|
||||
</h3>
|
||||
<div class="flex items-center gap-1 text-sm font-mono text-muted-foreground bg-muted px-2 py-0.5 rounded">
|
||||
<span>{{ detail?.model || '-' }}</span>
|
||||
<div class="flex min-w-0 max-w-[10rem] items-center gap-1 text-sm font-mono text-muted-foreground bg-muted px-2 py-0.5 rounded sm:max-w-none">
|
||||
<span class="truncate">{{ detail?.model || '-' }}</span>
|
||||
<template v-if="detail?.target_model && detail.target_model !== detail.model">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -38,7 +38,7 @@
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>{{ detail.target_model }}</span>
|
||||
<span class="truncate">{{ detail.target_model }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<Badge
|
||||
@@ -112,20 +112,20 @@
|
||||
<!-- 第二行:关键元信息 -->
|
||||
<div
|
||||
v-if="detail"
|
||||
class="flex items-center flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground"
|
||||
class="flex items-center flex-wrap gap-x-2 gap-y-1 text-xs text-muted-foreground sm:gap-x-4"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="flex min-w-0 items-center gap-1">
|
||||
<span class="font-medium text-foreground">ID:</span>
|
||||
<span
|
||||
class="font-mono"
|
||||
:title="fullRequestId"
|
||||
>{{ displayRequestId }}</span>
|
||||
</span>
|
||||
<span class="opacity-40">|</span>
|
||||
<span class="hidden opacity-40 sm:inline">|</span>
|
||||
<span>{{ formatDateTime(detail.created_at) }}</span>
|
||||
<span class="opacity-40">|</span>
|
||||
<span class="hidden opacity-40 sm:inline">|</span>
|
||||
<span>{{ formatApiFormat(detail.api_format) }}</span>
|
||||
<span class="opacity-40">|</span>
|
||||
<span class="hidden opacity-40 sm:inline">|</span>
|
||||
<span>用户: {{ detail.user?.username || 'Unknown' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,60 +204,55 @@
|
||||
<Card>
|
||||
<div class="p-3 sm:p-4">
|
||||
<!-- 总费用和响应时间(独立显示) -->
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-muted-foreground w-[56px]">总费用</span>
|
||||
<span class="text-lg font-bold text-green-600 dark:text-green-400">
|
||||
<div
|
||||
class="grid gap-1.5 mb-4 sm:gap-2"
|
||||
:class="detailOutputRate != null ? 'grid-cols-3' : 'grid-cols-2'"
|
||||
>
|
||||
<div class="min-w-0 rounded-md border border-border/50 bg-muted/20 px-2 py-2 sm:px-3">
|
||||
<span class="mb-0.5 block truncate text-[10px] text-muted-foreground sm:text-xs">总费用</span>
|
||||
<span class="block truncate text-sm font-bold text-green-600 dark:text-green-400 sm:text-lg">
|
||||
${{ ((typeof detail.cost === 'object' ? detail.cost?.total : detail.cost) || detail.total_cost || 0).toFixed(6) }}
|
||||
</span>
|
||||
</div>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="h-6 mx-6"
|
||||
/>
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-muted-foreground w-[56px]">响应时间</span>
|
||||
<span class="text-lg font-bold">{{ detail.response_time_ms ? formatResponseTime(detail.response_time_ms).value : 'N/A' }}</span>
|
||||
<span class="text-sm text-muted-foreground ml-1">{{ detail.response_time_ms ? formatResponseTime(detail.response_time_ms).unit : '' }}</span>
|
||||
<div class="min-w-0 rounded-md border border-border/50 bg-muted/20 px-2 py-2 sm:px-3">
|
||||
<span class="mb-0.5 block truncate text-[10px] text-muted-foreground sm:text-xs">响应时间</span>
|
||||
<span class="text-sm font-bold sm:text-lg">{{ detail.response_time_ms ? formatResponseTime(detail.response_time_ms).value : 'N/A' }}</span>
|
||||
<span class="ml-0.5 text-[10px] text-muted-foreground sm:ml-1 sm:text-sm">{{ detail.response_time_ms ? formatResponseTime(detail.response_time_ms).unit : '' }}</span>
|
||||
</div>
|
||||
<template v-if="detailOutputRate != null">
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="h-6 mx-6"
|
||||
/>
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-muted-foreground w-[56px]">输出速度</span>
|
||||
<span class="text-lg font-bold text-primary">{{ formatOutputRateValue(detailOutputRate) }}</span>
|
||||
<span class="text-sm text-muted-foreground ml-1">tps</span>
|
||||
<div class="min-w-0 rounded-md border border-border/50 bg-muted/20 px-2 py-2 sm:px-3">
|
||||
<span class="mb-0.5 block truncate text-[10px] text-muted-foreground sm:text-xs">输出速度</span>
|
||||
<span class="text-sm font-bold text-primary sm:text-lg">{{ formatOutputRateValue(detailOutputRate) }}</span>
|
||||
<span class="ml-0.5 text-[10px] text-muted-foreground sm:ml-1 sm:text-sm">tps</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasDetailPerformanceBreakdown"
|
||||
class="grid grid-cols-1 sm:grid-cols-3 gap-2 mb-4 text-xs"
|
||||
class="grid grid-cols-3 gap-1.5 mb-4 text-xs sm:gap-2"
|
||||
>
|
||||
<div class="rounded-md border border-border/50 bg-muted/20 px-3 py-2">
|
||||
<div class="text-muted-foreground mb-1">
|
||||
<div class="min-w-0 rounded-md border border-border/50 bg-muted/20 px-2 py-2 sm:px-3">
|
||||
<div class="mb-1 truncate text-[10px] text-muted-foreground sm:text-xs">
|
||||
首字时间
|
||||
</div>
|
||||
<div class="font-mono text-foreground">
|
||||
<div class="truncate font-mono text-xs text-foreground sm:text-sm">
|
||||
{{ formatDurationMs(detail.first_byte_time_ms) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-md border border-border/50 bg-muted/20 px-3 py-2">
|
||||
<div class="text-muted-foreground mb-1">
|
||||
<div class="min-w-0 rounded-md border border-border/50 bg-muted/20 px-2 py-2 sm:px-3">
|
||||
<div class="mb-1 truncate text-[10px] text-muted-foreground sm:text-xs">
|
||||
生成耗时
|
||||
</div>
|
||||
<div class="font-mono text-foreground">
|
||||
<div class="truncate font-mono text-xs text-foreground sm:text-sm">
|
||||
{{ formatDurationMs(detailGenerationTimeMs) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-md border border-border/50 bg-muted/20 px-3 py-2">
|
||||
<div class="text-muted-foreground mb-1">
|
||||
<div class="min-w-0 rounded-md border border-border/50 bg-muted/20 px-2 py-2 sm:px-3">
|
||||
<div class="mb-1 truncate text-[10px] text-muted-foreground sm:text-xs">
|
||||
输出 Tokens
|
||||
</div>
|
||||
<div class="font-mono text-foreground">
|
||||
<div class="truncate font-mono text-xs text-foreground sm:text-sm">
|
||||
{{ formatNumber(detailOutputTokens) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<button
|
||||
v-if="dropdown"
|
||||
type="button"
|
||||
class="flex h-8 w-full min-w-0 items-center justify-between gap-2 rounded-md border border-border/60 bg-background px-3 text-left text-xs"
|
||||
class="flex h-8 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-2xl border border-border/60 bg-card/80 px-4 py-2 text-left text-xs text-foreground shadow-sm backdrop-blur transition-all focus:border-primary/60 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
@click="toggleOpen"
|
||||
>
|
||||
<span class="truncate">{{ selectedLabel }}</span>
|
||||
<ChevronDown class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<ChevronDown class="h-4 w-4 shrink-0 text-muted-foreground opacity-50" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
|
||||
@@ -5,27 +5,51 @@
|
||||
<TimeRangePicker
|
||||
v-model="timeRangeModel"
|
||||
:show-granularity="false"
|
||||
class="hidden shrink-0 md:flex"
|
||||
/>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<div class="hidden sm:block h-4 w-px bg-border" />
|
||||
|
||||
<!-- 通用搜索 -->
|
||||
<div class="relative">
|
||||
<Search class="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground z-10 pointer-events-none" />
|
||||
<Input
|
||||
id="usage-records-search"
|
||||
v-model="localSearch"
|
||||
:placeholder="isAdmin ? '搜索用户/密钥' : '搜索密钥/模型'"
|
||||
class="w-[7.5rem] sm:w-48 h-8 text-xs border-border/60 pl-8"
|
||||
/>
|
||||
<div class="order-1 flex w-full items-center gap-2 md:order-none md:w-auto">
|
||||
<div class="relative min-w-0 flex-1 md:w-48 md:flex-none">
|
||||
<Search class="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground z-10 pointer-events-none" />
|
||||
<Input
|
||||
id="usage-records-search"
|
||||
v-model="localSearch"
|
||||
:placeholder="isAdmin ? '搜索用户/密钥' : '搜索密钥/模型'"
|
||||
class="h-8 w-full text-xs border-border/60 pl-8"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 shrink-0 md:hidden"
|
||||
:class="autoRefresh ? 'text-primary' : ''"
|
||||
:title="autoRefresh ? '点击关闭自动刷新' : '点击开启自动刷新'"
|
||||
@click="$emit('update:autoRefresh', !autoRefresh)"
|
||||
>
|
||||
<RefreshCcw
|
||||
class="w-3.5 h-3.5"
|
||||
:class="autoRefresh ? 'animate-spin' : ''"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="contents md:hidden">
|
||||
<div class="order-3 grid w-full grid-cols-2 gap-2 md:hidden">
|
||||
<!-- 时间范围筛选 -->
|
||||
<TimeRangePicker
|
||||
v-model="timeRangeModel"
|
||||
:show-granularity="false"
|
||||
class="min-w-0"
|
||||
preset-trigger-class="!w-full"
|
||||
/>
|
||||
|
||||
<!-- 用户筛选(仅管理员可见) -->
|
||||
<ServerUserSelector
|
||||
v-if="isAdmin"
|
||||
class="flex-1 min-w-0 sm:flex-none sm:w-40"
|
||||
class="min-w-0"
|
||||
:model-value="filterUser"
|
||||
:initial-users="availableUsers"
|
||||
dropdown
|
||||
@@ -37,7 +61,7 @@
|
||||
:model-value="filterModel"
|
||||
@update:model-value="$emit('update:filterModel', $event)"
|
||||
>
|
||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-40 h-8 text-xs border-border/60">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 text-xs border-border/60">
|
||||
<SelectValue placeholder="模型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -60,7 +84,7 @@
|
||||
:model-value="filterProvider"
|
||||
@update:model-value="$emit('update:filterProvider', $event)"
|
||||
>
|
||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-32 h-8 text-xs border-border/60">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 text-xs border-border/60">
|
||||
<SelectValue placeholder="提供商" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -82,7 +106,7 @@
|
||||
:model-value="filterApiFormat"
|
||||
@update:model-value="$emit('update:filterApiFormat', $event)"
|
||||
>
|
||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-32 h-8 text-xs border-border/60">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 text-xs border-border/60">
|
||||
<SelectValue placeholder="格式" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -104,7 +128,7 @@
|
||||
:model-value="filterStatus"
|
||||
@update:model-value="$emit('update:filterStatus', $event)"
|
||||
>
|
||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-28 h-8 text-xs border-border/60">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 text-xs border-border/60">
|
||||
<SelectValue placeholder="状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -142,9 +166,10 @@
|
||||
<!-- 列显示配置(桌面端) -->
|
||||
<MultiSelect
|
||||
v-model="visibleColumnIds"
|
||||
class="hidden md:block"
|
||||
:options="columnSelectOptions"
|
||||
placeholder="显示列"
|
||||
trigger-class="hidden md:flex w-40 h-8 text-xs border-border/60"
|
||||
trigger-class="w-40 h-8 text-xs border-border/60"
|
||||
dropdown-min-width="14rem"
|
||||
:searchable="false"
|
||||
/>
|
||||
@@ -156,7 +181,7 @@
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
class="hidden h-8 w-8 shrink-0 md:inline-flex"
|
||||
:class="autoRefresh ? 'text-primary' : ''"
|
||||
:title="autoRefresh ? '点击关闭自动刷新' : '点击开启自动刷新'"
|
||||
@click="$emit('update:autoRefresh', !autoRefresh)"
|
||||
|
||||
Reference in New Issue
Block a user