前端: 用量页移动端卡片优化 + canceled 灰色 + 接口类型短标识
- 移动端卡片重排: 成本/状态右置, 协议在模型下方, 元信息分隔行 - canceled 状态徽章由 warn 改为 neutral(与成功/失败区分) - 接口类型列改用短标识 chat/completions | responses | messages - 管理后台页头搜索框移动端纵向排列 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { http, errMsg } from '@/api/client'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { protocolName } from '@/lib/protocol'
|
||||
import { protocolShort } from '@/lib/protocol'
|
||||
import { fmtCost, fmtTime } from '@/lib/format'
|
||||
import Badge from '@/components/ui/Badge.vue'
|
||||
import Button from '@/components/ui/Button.vue'
|
||||
@@ -42,7 +42,7 @@ onMounted(load)
|
||||
|
||||
<template>
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-lg font-semibold">用量统计</h1>
|
||||
<p class="text-sm text-muted">全局请求明细与成本</p>
|
||||
@@ -51,15 +51,39 @@ onMounted(load)
|
||||
<input
|
||||
v-model="modelFilter"
|
||||
placeholder="按模型过滤"
|
||||
class="h-10 w-48 rounded-md border border-edge2 bg-surface px-3 font-mono text-xs outline-none focus:border-accent"
|
||||
class="h-10 min-w-0 flex-1 rounded-md border border-edge2 bg-surface px-3 font-mono text-xs outline-none focus:border-accent sm:w-48 sm:flex-none"
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
<Button variant="ghost" @click="search">搜索</Button>
|
||||
<Button variant="ghost" class="shrink-0" @click="search">搜索</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="overflow-x-auto">
|
||||
<!-- 移动端:卡片列表 -->
|
||||
<div class="space-y-3 p-3 md:hidden">
|
||||
<div v-for="l in logs" :key="l.id" class="card p-3">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate font-mono text-sm text-ink">{{ l.model }}</p>
|
||||
<p class="mt-0.5 truncate text-xs text-muted">{{ l.user }}</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-col items-end gap-1">
|
||||
<Badge :variant="l.status === 'success' ? 'ok' : l.status === 'canceled' ? 'neutral' : 'err'">{{ l.status }}</Badge>
|
||||
<span class="mono-num text-sm font-medium text-ink">{{ fmtCost(l.cost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 border-t border-edge pt-2 text-xs text-muted">
|
||||
<span class="font-mono">{{ protocolShort(l.protocol) }}</span>
|
||||
<span class="mono-num">{{ l.input_tokens }}/{{ l.output_tokens }} tok</span>
|
||||
<span class="mono-num">{{ l.latency_ms }}ms</span>
|
||||
<span class="mono-num w-full">{{ fmtTime(l.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="logs.length === 0" class="card px-4 py-8 text-center text-sm text-muted">暂无请求记录</p>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端:表格 -->
|
||||
<div class="hidden overflow-x-auto md:block">
|
||||
<table class="w-full text-sm min-w-[760px]">
|
||||
<thead>
|
||||
<tr class="border-b border-edge text-left text-xs text-muted">
|
||||
@@ -77,12 +101,12 @@ onMounted(load)
|
||||
<tr v-for="l in logs" :key="l.id" class="table-row">
|
||||
<td class="px-4 py-2.5 text-xs text-ink">{{ l.user }}</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-ink">{{ l.model }}</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ protocolName(l.protocol) }}</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ protocolShort(l.protocol) }}</td>
|
||||
<td class="px-4 py-2.5 mono-num text-xs text-muted">{{ l.input_tokens }}/{{ l.output_tokens }}</td>
|
||||
<td class="px-4 py-2.5 mono-num text-xs text-ink">{{ fmtCost(l.cost) }}</td>
|
||||
<td class="px-4 py-2.5 mono-num text-xs text-muted">{{ l.latency_ms }}ms</td>
|
||||
<td class="px-4 py-2.5">
|
||||
<Badge :variant="l.status === 'success' ? 'ok' : 'err'">{{ l.status }}</Badge>
|
||||
<Badge :variant="l.status === 'success' ? 'ok' : l.status === 'canceled' ? 'neutral' : 'err'">{{ l.status }}</Badge>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ fmtTime(l.created_at) }}</td>
|
||||
</tr>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { onMounted, ref } from 'vue'
|
||||
import { http, errMsg } from '@/api/client'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { fmtNum, fmtCost, fmtTime } from '@/lib/format'
|
||||
import { protocolName } from '@/lib/protocol'
|
||||
import { protocolShort } from '@/lib/protocol'
|
||||
import Badge from '@/components/ui/Badge.vue'
|
||||
import TrendChart from '@/components/ui/TrendChart.vue'
|
||||
import type { UsageLog } from '@/types'
|
||||
@@ -98,16 +98,40 @@ onMounted(load)
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="flex items-center justify-between border-b border-edge px-4 py-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 border-b border-edge px-4 py-3">
|
||||
<h2 class="text-sm font-semibold">请求明细</h2>
|
||||
<input
|
||||
v-model="modelFilter"
|
||||
placeholder="按模型过滤"
|
||||
class="h-8 w-48 rounded-md border border-edge2 bg-surface px-2.5 font-mono text-xs outline-none focus:border-accent"
|
||||
class="h-8 w-full rounded-md border border-edge2 bg-surface px-2.5 font-mono text-xs outline-none focus:border-accent sm:w-48"
|
||||
@keyup.enter="page = 1; load()"
|
||||
/>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
|
||||
<!-- 移动端:卡片列表 -->
|
||||
<div class="space-y-3 p-3 md:hidden">
|
||||
<div v-for="l in logs" :key="l.id" class="card p-3">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate font-mono text-sm text-ink">{{ l.model }}</p>
|
||||
<p class="mt-0.5 font-mono text-xs text-muted">{{ protocolShort(l.protocol) }}</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 flex-col items-end gap-1">
|
||||
<Badge :variant="l.status === 'success' ? 'ok' : l.status === 'canceled' ? 'neutral' : 'err'">{{ l.status }}</Badge>
|
||||
<span class="mono-num text-sm font-medium text-ink">{{ fmtCost(l.cost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 border-t border-edge pt-2 text-xs text-muted">
|
||||
<span class="mono-num">{{ l.input_tokens }}/{{ l.output_tokens }} tok</span>
|
||||
<span class="mono-num">{{ l.latency_ms }}ms</span>
|
||||
<span class="mono-num w-full">{{ fmtTime(l.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="logs.length === 0" class="card px-4 py-8 text-center text-sm text-muted">暂无请求记录</p>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端:表格 -->
|
||||
<div class="hidden overflow-x-auto md:block">
|
||||
<table class="w-full text-sm min-w-[640px]">
|
||||
<thead>
|
||||
<tr class="border-b border-edge text-left text-xs text-muted">
|
||||
@@ -123,11 +147,11 @@ onMounted(load)
|
||||
<tbody>
|
||||
<tr v-for="l in logs" :key="l.id" class="table-row">
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-ink">{{ l.model }}</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ protocolName(l.protocol) }}</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ protocolShort(l.protocol) }}</td>
|
||||
<td class="px-4 py-2.5 mono-num text-xs text-muted">{{ l.input_tokens }}/{{ l.output_tokens }}</td>
|
||||
<td class="px-4 py-2.5 mono-num text-xs text-ink">{{ fmtCost(l.cost) }}</td>
|
||||
<td class="px-4 py-2.5 mono-num text-xs text-muted">{{ l.latency_ms }}ms</td>
|
||||
<td class="px-4 py-2.5"><Badge :variant="l.status === 'success' ? 'ok' : 'err'">{{ l.status }}</Badge></td>
|
||||
<td class="px-4 py-2.5"><Badge :variant="l.status === 'success' ? 'ok' : l.status === 'canceled' ? 'neutral' : 'err'">{{ l.status }}</Badge></td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-muted">{{ fmtTime(l.created_at) }}</td>
|
||||
</tr>
|
||||
<tr v-if="logs.length === 0">
|
||||
|
||||
Reference in New Issue
Block a user