From 42214d6f5e9f9c03791a153adcd1e74c1607fac8 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Thu, 20 Aug 2026 01:58:02 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0=E7=94=A8?= =?UTF-8?q?=E9=87=8F=E7=BB=9F=E8=AE=A1=E6=94=AF=E6=8C=81=E6=8C=89=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=AD=9B=E9=80=89=EF=BC=9A=E7=94=A8=E6=88=B7=E5=90=8D?= =?UTF-8?q?=E8=BE=93=E5=85=A5=20+=20datalist=20=E8=87=AA=E5=8A=A8=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=EF=BC=8C=E7=B2=BE=E7=A1=AE=E5=8C=B9=E9=85=8D=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E4=B8=BA=20user=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - types: UsageLog 补 user_id 字段 - UsageView: 新增用户筛选输入框,防抖 300ms 拉取 admin/users 候选做下拉补全 - search 时将用户名经 admin/users 精确解析为 user_id 再过滤 admin/usage;未找到用户时 toast 报错 - 支持清除筛选一键还原 --- web/src/types.ts | 1 + web/src/views/admin/UsageView.vue | 76 +++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/web/src/types.ts b/web/src/types.ts index 656c3d1..0f4a1ab 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -94,6 +94,7 @@ export interface UsageLog { raw_response?: string created_at: string user?: string + user_id?: number } export interface Paged { diff --git a/web/src/views/admin/UsageView.vue b/web/src/views/admin/UsageView.vue index bcb5aa2..d42e940 100644 --- a/web/src/views/admin/UsageView.vue +++ b/web/src/views/admin/UsageView.vue @@ -7,13 +7,16 @@ import { fmtCost, fmtTime } from '@/lib/format' import Badge from '@/components/ui/Badge.vue' import Button from '@/components/ui/Button.vue' import Modal from '@/components/ui/Modal.vue' -import type { UsageLog } from '@/types' +import type { UsageLog, User } from '@/types' const toast = useToastStore() const logs = ref([]) const total = ref(0) const page = ref(1) const modelFilter = ref('') +const userFilter = ref('') +const userCandidates = ref([]) +let userSearchTimer: ReturnType | null = null const pageSize = 15 const viewing = ref(null) @@ -24,11 +27,47 @@ function openRaw(l: UsageLog) { viewTab.value = 'request' } +// 用户自动补全候选(防抖 300ms) +function onUserInput() { + if (userSearchTimer) clearTimeout(userSearchTimer) + const kw = userFilter.value.trim() + if (!kw) { + userCandidates.value = [] + return + } + userSearchTimer = setTimeout(async () => { + try { + const { data } = await http.get(`/admin/users?q=${encodeURIComponent(kw)}&page_size=10`) + userCandidates.value = data.data.items ?? [] + } catch { + userCandidates.value = [] + } + }, 300) +} + +// 用户名 → user_id(精确匹配),查不到返回 null +async function resolveUserId(username: string): Promise { + const { data } = await http.get(`/admin/users?q=${encodeURIComponent(username)}&page_size=10`) + const items: User[] = data.data.items ?? [] + const hit = items.find((u) => u.username === username) + return hit ? hit.id : null +} + async function load() { try { - const { data } = await http.get( - `/admin/usage?page=${page.value}&page_size=${pageSize}${modelFilter.value ? '&model=' + modelFilter.value : ''}`, - ) + const params = new URLSearchParams() + params.set('page', String(page.value)) + params.set('page_size', String(pageSize)) + if (modelFilter.value.trim()) params.set('model', modelFilter.value.trim()) + if (userFilter.value.trim()) { + const uid = await resolveUserId(userFilter.value.trim()) + if (uid === null) { + toast.err(`未找到用户:${userFilter.value.trim()}`) + return + } + params.set('user_id', String(uid)) + } + const { data } = await http.get(`/admin/usage?${params.toString()}`) logs.value = data.data.items total.value = data.data.total } catch (e) { @@ -41,6 +80,12 @@ function search() { load() } +function clearUser() { + userFilter.value = '' + userCandidates.value = [] + search() +} + function goPage(p: number) { page.value = p load() @@ -56,7 +101,28 @@ onMounted(load)

用量统计

全局请求明细与成本

-
+
+
+ + +
+ +